Vous êtes sur la page 1sur 7522

Parlez-nous de l’expérience de téléchargement de PDF.

Documentation d’ASP.NET
Apprenez à utiliser ASP.NET Core pour créer des applications et des services web rapides,
sécurisés, interplateformes et basés sur le cloud. Parcourez des tutoriels, des exemples de code,
des bases, des informations de référence sur les API et plus encore.

BIEN DÉMARRER NOUVEAUTÉS


Créer une application ASP.NET Envoyer des commentaires sur la
Core sur n’importe quelle documentation sur ASP.NET
plateforme en 5 minutes

VUE D’ENSEMBLE TÉLÉCHARGER


Vue d’ensemble d’ASP.NET Core Télécharger .NET

BIEN DÉMARRER BIEN DÉMARRER


Créer votre première interface Créer votre première API web
utilisateur web

BIEN DÉMARRER VUE D’ENSEMBLE


Créer votre première application Documentation ASP.NET 4.x
web en temps réel

Développer des applications ASP.NET Core


Choisir des applications web interactives, des API web, des applications basées sur un modèle MVC, des
applications en temps réel, etc.

Applications Blazor Applications d’API Interface utilisateur


côté client interactives HTTP web orientée page avec
Développer des services HTTP Razor Pages
avec ASP.NET Core


Développer avec des b Créer une API web Développer des applications
composants d’interface minimale avec ASP.NET web orientées page avec une
utilisateur réutilisables pouvant Core séparation claire des
tirer parti de WebAssembly… responsabilités
d Créer une API web avec
e Vue d’ensemble des contrôleurs ASP.NET b Créer votre première
Core application web Razor
b Créer votre première
g Générer des pages d’aide Pages
application Blazor
sur une API web avec g Créer une interface
b Créer votre première Swagger/OpenAPI utilisateur web orientée
application Blazor avec des
p Types de retour des actions page qui consomme une
composants réutilisables
du contrôleur API web
p Modèles d’hébergement
p Mettre en forme les p Syntaxe Razor
Blazor
données de la réponse p Filtres
p Gérer les erreurs p Routage
g Appeler une API web p Applications web ASP.NET
ASP.NET Core avec Core accessibles
JavaScript

Interface utilisateur Applications web en Applications utilisant


web orientée page avec temps réel avec SignalR l’appel de procédure
MVC Ajouter des fonctionnalités distante (RPC) -…
Développer des applications temps réel à votre application Développer des services
web à l’aide du modèle de web, permettre au code côté « contrat en premier » à hautes
conception MVC (modèle-vue- serveur d’envoyer (push) du… performances avec gRPC dans
contrôleur) ASP.NET Core
e Vue d’ensemble
e Vue d’ensemble b Créer votre première e Vue d’ensemble
b Créer votre première application SignalR b Créer un client et un
application MVC ASP.NET g SignalR avec Blazor serveur gRPC
Core WebAssembly p Concepts des services
p Les vues g SignalR avec TypeScript gRPC dans C#
p Vues partielles s Exemples s Exemples
p Contrôleurs p Hubs p Comparer les services
gRPC avec les API HTTP
p Routage vers les actions du p Fonctionnalités du client
contrôleur SignalR g Ajouter un service gRPC à
une application ASP.NET
p Test unitaire p Hôte et mise à l’échelle Core
g Appeler les services gRPC
avec le client .NET
g Utiliser gRPC dans les
applications de navigateur

Applications web Versions précédentes Tutoriels vidéo sur


pilotées par les du framework ASP.NET ASP.NET Core
données
Créer des applications web Explorer les versions q Série de vidéos ASP.NET
pilotées par les données dans précédentes du framework Core 101
ASP.NET Core ASP.NET : présentations,
q Série de vidéos Entity
tutoriels, concepts…
g SQL avec ASP.NET Core Framework Core 101 avec
p ASP.NET 4.x .NET Core et ASP.NET Core
p Liaison de données dans
ASP.NET Core Blazor q Architecture de
microservice avec ASP.NET
g SQL Server Express et Core
Razor Pages
q Série de vidéos Pleins feux
g Entity Framework Core sur Blazor
avec Razor Pages
q Canal .NET
g Entity Framework Core
avec ASP.NET Core MVC
g Stockage Azure
g Stockage Blob
p Azure Table Storage
p Scénarios Microsoft Graph
pour ASP.NET Core

Concepts et fonctionnalités

Informations de référence sur les API Serveurs


pour ASP.NET Core
Vue d’ensemble
Navigateur d’API .NET Kestrel
IIS
HTTP.sys

Héberger et déployer Sécurité et identité


Vue d’ensemble Vue d’ensemble
Déployer sur Azure App Service Choisir une solution d’identité
DevOps pour les développeurs ASP.NET Core l’authentification,
Linux avec Apache Autorisation
Linux avec Nginx Cours : Sécuriser une application web ASP.NET Core
avec l’infrastructure Identity
Kestrel
Protection des données
IIS
Gestion des secrets
HTTP.sys
Appliquer le protocole HTTPS
Docker
Héberger Docker avec HTTPS

Globalisation et localisation Tester, déboguer et résoudre les


problèmes
Vue d’ensemble
Localisation d’objet portable Tests unitaires Pages Razor

Extensibilité de la localisation Débogage à distance

Résoudre des problèmes Débogage d’instantané


Tests d’intégration
Tests de charge et de contrainte
Résolution des problèmes et débogage
Journalisation
Charger des applications web Azure de test avec
Azure DevOps

Azure et ASP.NET Core Performances


Déployer une application web ASP.NET Core Vue d’ensemble
ASP.NET Core et Docker Mémoire et garbage collection
Héberger une application web avec Azure App Mise en cache des réponses
Service
Compression des réponses
App Service et Azure SQL Database
Outils de diagnostic
Identité managée avec ASP.NET Core et Azure SQL
Tests de charge et de contrainte
Database
API web avec CORS dans Azure App Service
Capturer les journaux des applications web avec la
journalisation des diagnostics d’App Service

Fonctionnalités avancées Migration


Liaison de données ASP.NET Core 5.0 vers 6.0
Validation du modèle Exemples de code ASP.NET Core 5.0 vers le modèle
d’hébergement minimal 6.0
Middleware Écriture
ASP.NET Core 3.1 vers 5.0
Opérations de demande et de réponse
ASP.NET Core 3.0 vers 3.1
ASP.NET Core 2.2 vers 3.0
ASP.NET Core 2.1 vers 2.2
ASP.NET Core 2.0 vers 2.1
ASP.NET Core 1.x vers 2.0
Réécriture d’URL D’ASP.NET vers ASP.NET Core

Architecture
Choisir entre des applications web traditionnelles et
des applications monopages (SPA)
Principes de l’architecture
Architectures courantes des applications web
Technologies web courantes côté client
Processus de développement pour Azure

Contribuez à la documentation ASP.NET Core. Lisez notre guide du contributeur .


Vue d’ensemble d’ASP.NET Core
Article • 30/11/2023

Par Daniel Roth , Rick Anderson et Shaun Luttin

ASP.NET Core est un framework multiplateforme à hautes performances et open


source pour créer des applications cloud modernes et connectées à Internet.

Avec ASP.NET Core, vous pouvez :

Créer des applications et services web, des applications Internet des objets (IoT)
et des back-ends mobiles.
Utiliser vos outils de développement préférés sur Windows, macOS et Linux.
Déployer dans le cloud ou localement.
Exécutez sur .NET Core.

Pourquoi utiliser ASP.NET Core ?


Des millions de développeurs utilisent ou ont utilisé ASP.NET 4.x pour créer des
applications web. ASP.NET Core est une refonte d’ASP.NET 4.x incluant des
modifications d’architecture qui aboutissent à un framework plus léger et modulaire.

ASP.NET Core offre les avantages suivants :

Un scénario unifié pour créer une interface utilisateur web et des API web.
Architecturé pour la testabilité.
Razor Pages permet de coder des scénarios orientés page de façon plus simple et
plus productive.
Blazor vous permet d’utiliser C# dans le navigateur en même temps que JavaScript.
Partagez les logiques d’applications côté serveur et côté client écrites avec .NET.
Capacité à développer et à exécuter sur Windows, macOS et Linux.
Open source et centré sur la communauté .
Intégration de frameworks modernes côté client et de workflows de
développement.
Prise en charge de l’hébergement des services d’appel de procédure distante (RPC)
à l’aide de gRPC.
Un système de configuration prêt pour le cloud et basé sur les environnements.
Injection de dépendances intégrée.
Un pipeline des requêtes HTTP léger, à hautes performances et modulaire.
Capacité d’hébergement sur les éléments suivants :
Kestrel
IIS
HTTP.sys
Nginx
Apache
Docker
Contrôle de version côte à côte.
Outils qui simplifient le développement web moderne.

Créer des API web et une interface utilisateur


web en utilisant le modèle MVC d’ASP.NET Core
Le modèle MVC d’ASP.NET Core fournit des fonctionnalités pour créer des API web et
des applications web :

Le modèle MVC (Modèle-Vue-Contrôleur) permet de rendre vos API web et vos


applications web testables.
Razor Pages est un modèle de programmation basé sur les pages qui rend la
création d’une interface utilisateur web plus facile et plus productive.
Le balisage Razor fournit une syntaxe efficace pour les pages Razor et les vues
MVC.
Les Tag Helpers permettent au code côté serveur de participer à la création et au
rendu des éléments HTML dans les fichiers Razor.
La prise en charge intégrée de plusieurs formats de données et de la négociation
de contenu permet à vos API web d’être utilisées par un large éventail de clients,
notamment des navigateurs et des appareils mobiles.
Le principe de la liaison de modèle permet le mappage automatiquement des
données des requêtes HTTP aux paramètres des méthodes d’action.
La validation de modèle effectue automatiquement la validation côté client et côté
serveur.

Développement côté client


ASP.NET Core inclut Blazor pour la création d’une interface utilisateur web riche et
interactive, et s’intègre également à d’autres frameworks JavaScript front-end courants,
comme Angular, React, Vue et Bootstrap . Pour plus d’informations, consultez Blazor
ASP.NET Core et les rubriques connexes sous Développement côté client.

Frameworks cibles ASP .NET Core


ASP.NET Core 3.x ou version ultérieure ne peut cibler que .NET Core. D’une façon
générale, ASP.NET Core est composé de bibliothèques .NET Standard. Les bibliothèques
écrites avec .NET Standard 2.0 s’exécutent sur n’importe quelle plateforme .NET qui
implémente .NET Standard 2.0.

Le ciblage de .NET Core présente plusieurs avantages, qui sont plus nombreux à chaque
version. Voici certains avantages de .NET Core par rapport à .NET Framework :

Multiplateforme. S’exécute sur Windows, macOS et Linux.


Amélioration des performances
Contrôle de versions côte à côte
Nouvelles API
Open source

Parcours d’apprentissage recommandé


Nous vous recommandons la séquence de tutoriels suivante comme introduction au
développement des applications ASP.NET Core :

1. Suivez un tutoriel pour le type d’application que vous souhaitez développer ou


gérer.

Type d’application Scénario Didacticiel

Application web Nouveau développement de l’interface Prise en main des


utilisateur web côté serveur pages Razor

Application web Pour maintenir une application MVC Bien démarrer avec
MVC

Application web Développement de l’interface Commencer à utiliser


utilisateur web côté client Blazor

API Web RESTservices HTTP ful Créer une API web†

Application d’appel de Services contrat-first utilisant des Bien démarrer avec


procédure distante mémoires tampons de protocole un service gRPC

Application en temps Communication bidirectionnelle entre Commencer à utiliser


réel les serveurs et les clients connectés SignalR

2. Suivez un tutoriel expliquant comment exécuter l’accès aux données de base.

Scénario Didacticiel

Nouveau développement Pages Razor avec Entity Framework Core


Scénario Didacticiel

Pour maintenir une application MVC MVC avec Entity Framework Core

3. Lisez une présentation des principes fondamentaux d’ASP.NET Core qui


s’appliquent à tous les types d’application.

4. Parcourez la table des matières pour d’autres rubriques qui vous intéressent.

†Vous trouverez aussi un tutoriel d’API web interactif. Aucune installation locale des
outils de développement n’est requise. Le code s’exécute dans Azure Cloud Shell dans
votre navigateur, et curl est utilisé à des fins de test.

Migrer à partir de .NET Framework


Pour obtenir un guide de référence sur la migration d’applications ASP.NET 4.x vers
ASP.NET Core, consultez Mettre à jour depuis ASP.NET vers ASP.NET Core.

Comment télécharger un exemple


La plupart des articles et tutoriels contiennent des liens vers des exemples de code.

1. Téléchargez le fichier zip du référentiel ASP.NET .


2. Décompressez le fichier AspNetCore.Docs-main.zip .
3. Pour accéder à l’exemple d’application d’un article dans le référentiel décompressé,
utilisez l’URL du lien de l’exemple d’article pour vous aider à accéder au dossier de
l’exemple. En règle générale, l’exemple de lien d’un article apparaît en haut de
l’article avec le texte de lien Afficher ou télécharger l’exemple de code.

Directives de préprocesseur dans l’exemple de code


Pour illustrer plusieurs scénarios, les exemples d’applications utilisent les directives de
préprocesseur #define et #if-#else/#elif-#endif pour compiler et exécuter différentes
sections de l’exemple de code de manière sélective. Pour ces exemples qui utilisent cette
approche, définissez la directive #define en haut des fichiers C# pour définir le symbole
associé au scénario que vous souhaitez exécuter. Certains exemples exigent que vous
définissiez le symbole en haut de plusieurs fichiers afin d’exécuter un scénario.

Par exemple, la liste des symboles #define suivante indique que les quatre scénarios
sont disponibles (un scénario par symbole). La configuration actuelle de l’exemple
exécute le scénario TemplateCode :
C#

#define TemplateCode // or LogFromMain or ExpandDefault or FilterInCode

Pour que l’exemple exécute le scénario ExpandDefault , définissez le symbole


ExpandDefault et laissez les symboles restants commentés :

C#

#define ExpandDefault // TemplateCode or LogFromMain or FilterInCode

Pour plus d’informations sur l’utilisation des directives de préprocesseur C# pour


compiler de façon sélective des sections de code, consultez #define (Référence C#) et #if
(Référence C#) .

Changements cassants et avis de sécurité


Les changements cassants et les avis de sécurité sont signalés dans le dépôt
Annonces . Les annonces peuvent être limitées à une version spécifique en
sélectionnant un filtre Étiquette.

Étapes suivantes
Pour plus d’informations, consultez les ressources suivantes :

Bien démarrer avec ASP.NET Core


Publier une application ASP.NET Core sur Azure avec Visual Studio
Notions de base d’ASP.NET Core
Le point hebdomadaire de la communauté ASP.NET couvre l’avancement et les
plans des équipes de développement. Il comprend de nouveaux blogs et des
logiciels de tiers.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre  Indiquer des commentaires sur
guide du contributeur. le produit
Choisir entre ASP.NET 4.x et ASP.NET
Core
Article • 30/11/2023

ASP.NET Core est une refonte d’ASP.NET 4.x. Cet article liste les différences existant
entre eux.

ASP.NET Core
ASP.NET Core est un framework open source multiplateforme qui permet de créer des
applications web cloud modernes sur Windows, macOS et Linux.

ASP.NET Core offre les avantages suivants :

Un scénario unifié pour créer une interface utilisateur web et des API web.
Architecturé pour la testabilité.
Razor Pages permet de coder des scénarios orientés page de façon plus simple et
plus productive.
Blazor vous permet d’utiliser C# dans le navigateur en même temps que JavaScript.
Partagez les logiques d’applications côté serveur et côté client écrites avec .NET.
Capacité à développer et à exécuter sur Windows, macOS et Linux.
Open source et centré sur la communauté .
Intégration de frameworks modernes côté client et de workflows de
développement.
Prise en charge de l’hébergement des services d’appel de procédure distante (RPC)
à l’aide de gRPC.
Un système de configuration prêt pour le cloud et basé sur les environnements.
Injection de dépendances intégrée.
Un pipeline des requêtes HTTP léger, à hautes performances et modulaire.
Capacité d’hébergement sur les éléments suivants :
Kestrel
IIS
HTTP.sys
Nginx
Apache
Docker
Contrôle de version côte à côte.
Outils qui simplifient le développement web moderne.
ASP.NET 4.x
ASP.NET 4.x est un framework abouti qui offre les services nécessaires pour créer sur
Windows des applications web, basées sur serveur et destinées à l’entreprise.

Sélection du Framework
Le tableau suivant compare ASP.NET Core à ASP.NET 4.x.

ASP.NET Core ASP.NET 4.x

Générer pour Windows, macOS ou Linux Générer pour Windows

Nous vous recommandons d’utiliser Razor Pages pour créer Utilisez Web Forms, SignalR,
une interface utilisateur web à partir d’ASP.NET Core 2. Voir MVC, Web API, WebHooks ou
aussi MVC, API web et SignalR. Pages web

Plusieurs versions par machine Une seule version par machine

Développer avec Visual Studio , Visual Studio pour Mac ou Développer avec Visual Studio
Visual Studio Code en utilisant C# ou F# en utilisant C#, VB ou F#

Performances supérieures à celles d’ASP.NET 4.x Bonnes performances

Utiliser le runtime .NET Core Utiliser le runtime .NET


Framework

Consultez ASP.NET Core ciblant le .NET Framework pour plus d’informations sur la prise
en charge d’ASP.NET Core 2.x sur le .NET Framework.

Scénarios ASP.NET Core


Sites web
API
Temps réel
Déployer une application ASP.NET Core sur Azure

Scénarios ASP.NET 4.x


Sites web
API
Temps réel
Créer une application web ASP.NET 4.x dans Azure
Ressources supplémentaires
Présentation d’ASP.NET
Introduction à ASP.NET Core
Déployer des applications ASP.NET Core sur Azure App Service

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
.NET et .NET Framework pour les
applications serveur
Article • 03/06/2023

Deux implémentations de .NET sont prises en charge pour la création d’applications


côté serveur.

ノ Agrandir le tableau

Implémentation Versions incluses

.NET .NET Core 1.0 - 3.1, .NET 5 et versions ultérieures de .NET

.NET Framework .NET Framework 1.0 - 4.8

Toutes deux partagent de nombreux composants et vous pouvez partager du code


entre les deux. Toutefois, il existe des différences fondamentales entre les deux et votre
choix dépend de ce que vous souhaitez accomplir. Cet article fournit des conseils sur
l’utilisation de chacune.

Utilisez .NET pour votre application serveur quand :

vous avez des besoins multiplateformes ;


Vous ciblez des microservices
Vous utilisez des conteneurs Docker
Vous avez besoin de systèmes scalables et hautes performances.
Vous avez besoin de versions .NET côte à côte par application.

Utilisez .NET Framework pour votre application serveur quand :

Votre application utilise le .NET Framework (nous vous recommandons de


privilégier l’extension à la migration).
Votre application utilise des packages NuGet ou des bibliothèques tiers non
disponibles pour .NET
Votre application utilise des technologies .NET Framework non disponibles pour
.NET
Votre application utilise une plateforme qui ne prend pas en charge .NET

Quand choisir .NET


Les sections suivantes donnent une explication plus détaillée des raisons indiquées
précédemment justifiant le choix de .NET par rapport à .NET Framework.

Besoins multiplateformes
Si votre application web ou de service doit s’exécuter sur plusieurs plateformes, par
exemple Windows, Linux et macOS, utilisez .NET.

.NET prend en charge les systèmes d’exploitation précédemment mentionnés comme


station de travail de développement. Visual Studio fournit un environnement de
développement intégré (IDE) pour Windows et macOS. Vous pouvez également utiliser
Visual Studio Code, qui s’exécute sur macOS, Linux et Windows. Visual Studio Code
prend en charge .NET, notamment IntelliSense et le débogage. La plupart des éditeurs
tiers, tels que Sublime, Emacs et VI, fonctionnent avec .NET. Ces éditeurs tiers
obtiennent l’éditeur IntelliSense en utilisant Omnisharp . Vous pouvez aussi vous
affranchir des éditeurs de code et utiliser directement l’interface CLI .NET, disponible
pour toutes les plateformes prises en charge.

Architecture de microservices
Une architecture en microservices permet une combinaison de technologies au-delà des
limites d’un service. Cette combinaison de technologies favorise l’adoption progressive
de .NET pour les nouveaux microservices qui utilisent d’autres microservices ou services.
Par exemple, vous pouvez combiner des microservices ou services développés avec .NET
Framework, Java, Ruby ou d’autres technologies monolithiques.

Il existe de nombreuses plateformes d’infrastructure. Azure Service Fabric est conçu


pour les systèmes de microservice volumineux et complexes. Azure App Service est un
bon choix pour les microservices sans état. Les alternatives aux microservices basées sur
Docker s’intègrent à toute approche des microservices, comme expliqué dans la section
Conteneurs. Toutes ces plateformes prennent en charge .NET et s’avèrent idéales pour
l’hébergement de vos microservices.

Pour plus d’informations sur l’architecture en microservices, consultez Microservices


.NET. Architecture des applications .NET conteneurisées.

Conteneurs
Les conteneurs sont couramment utilisés avec une architecture en microservices. Les
conteneurs peuvent également servir à mettre en conteneur des applications ou services
web qui suivent un modèle d’architecture. .NET Framework peut être utilisé sur des
conteneurs Windows. Cependant, de par sa nature légère et modulaire, .NET représente
un meilleur choix pour les conteneurs. Quand vous créez et déployez un conteneur, la
taille de son image est beaucoup plus petite avec .NET qu’avec .NET Framework. Grâce à
sa nature multiplateforme, vous pouvez déployer des applications serveur sur des
conteneurs Docker Linux.

Vous pouvez héberger des conteneurs Docker dans votre propre infrastructure Windows
ou Linux, ou dans un service cloud comme Azure Kubernetes Service . Azure
Kubernetes Service permet de gérer, d’orchestrer et de mettre à l’échelle des
applications sur conteneur dans le cloud.

Systèmes scalables et hautes performances


Quand votre système a besoin de performances et d’une scalabilité optimales, .NET et
ASP.NET Core sont vos meilleures options. Le runtime serveur hautes performances pour
Windows Server et Linux fait d’ASP.NET Core un framework web particulièrement
puissant, d’après les bancs d’essai TechEmpower .

Niveau de performance et scalabilité sont particulièrement pertinents pour les


architectures en microservices, où des centaines de microservices peuvent être en cours
d’exécution. Avec ASP.NET Core, les systèmes sont exécutés avec un nombre bien
inférieur de serveurs/machines virtuelles. Cette réduction engendre une baisse des coûts
d’infrastructure et d’hébergement.

Versions .NET côte à côte par niveau d’application


Pour installer des applications avec des dépendances sur différentes versions de .NET,
nous vous recommandons .NET. Cette implémentation prend en charge l’installation
côte à côte de différentes versions du runtime .NET sur le même ordinateur. Ainsi,
plusieurs services peuvent cohabiter sur le même serveur, chacun d’eux sur sa propre
version de .NET. De plus, les risques et les coûts liés aux opérations informatiques et aux
mises à niveau des applications s’en trouvent réduits.

L’installation côte à côte n’est pas possible avec .NET Framework. Il s’agit d’un
composant Windows et une seule version peut exister sur une machine à la fois. Chaque
version de .NET Framework remplace la version précédente. Si vous installez une
nouvelle application qui cible une version ultérieure de .NET Framework, vous risquez
d’arrêter les applications existantes qui s’exécutent sur l’ordinateur, car la version
précédente a été remplacée.

Quand choisir .NET Framework


.NET offre des avantages significatifs pour les nouvelles applications et les nouveaux
modèles d’application. Toutefois, .NET Framework demeure le choix évident pour de
nombreux scénarios existants. Il n’est donc pas remplacé par .NET pour toutes les
applications serveur.

Applications .NET Framework actuelles


Dans la plupart des cas, vous n’avez pas besoin de migrer vos applications existantes
vers .NET. Nous vous recommandons plutôt d’utiliser .NET quand vous étendez une
application existante, par exemple quand vous écrivez un nouveau service web dans
ASP.NET Core.

Bibliothèques ou packages NuGet tiers non disponibles


pour .NET
.NET Standard permet de partager du code sur toutes les implémentations de .NET, y
compris .NET Core/5 (et versions ultérieures). Avec .NET Standard 2.0, un mode de
compatibilité permet aux projets .NET Standard et .NET de référencer des bibliothèques
.NET Framework. Pour plus d’informations, consultez Prise en charge des bibliothèques
.NET Framework.

Vous devez utiliser .NET Framework seulement dans les cas où les bibliothèques ou les
packages NuGet utilisent des technologies qui ne sont pas disponibles dans
.NET Standard ou .NET.

Technologies .NET Framework non disponibles pour .NET


Certaines technologies .NET Framework ne sont pas disponibles dans .NET. La liste
suivante présente les technologies les plus courantes non disponibles dans .NET :

Applications ASP.NET Web Forms : ASP.NET Web Forms est disponible


uniquement dans .NET Framework. ASP.NET Core ne peut pas être utilisé pour
ASP.NET Web Forms.

Applications pages Web ASP.NET : le framework pages Web ASP.NET n’est pas
inclus dans ASP.NET Core.

Services liés aux workflows : Windows Workflow Foundation (WF), les services de
workflow (WCF + WF dans un seul service) et WCF Data Services (anciennement
« ADO.NET Data Services ») sont disponibles uniquement dans .NET Framework.
Prise en charge des langages : actuellement, Visual Basic et F# sont pris en charge
dans .NET, mais pas pour tous les types de projet. Pour obtenir la liste des modèles
de projet pris en charge, consultez Options de modèle pour dotnet new.

Pour plus d’informations, consultez Technologies .NET Framework non disponibles dans
.NET.

Plateformes ne prenant pas en charge .NET


Certaines plateformes Microsoft ou tierces ne prennent pas en charge .NET. Certains
services Azure fournissent un kit SDK qui n’est pas encore consommable sur .NET. Dans
ce cas, vous pouvez utiliser l’API REST équivalente au lieu du SDK client.

Voir aussi
Choisir entre ASP.NET et ASP.NET Core
ASP.NET Core ciblant .NET Framework
Frameworks cibles
Présentation de .NET
Portage de .NET Framework vers .NET 5
Introduction à .NET et à Docker
Implémentations de .NET
Microservices .NET. Architecture pour les applications .NET en conteneur

6 Collaborer avec nous sur Commentaires sur .NET


GitHub .NET est un projet open source.
La source de ce contenu se Sélectionnez un lien pour fournir des
trouve sur GitHub, où vous commentaires :
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : Bien démarrer avec ASP.NET
Core
Article • 30/11/2023

Ce tutoriel montre comment créer et exécuter une application web ASP.NET Core à
l’aide de l’interface CLI .NET Core.

Vous découvrirez comment effectuer les actions suivantes :

" Créer un projet application web.


" Approuver le certificat de développement.
" Exécutez l’application.
" Modifiez une page Razor.

À la fin du tutoriel, vous disposerez d’une application web qui fonctionne et s’exécute
sur votre machine locale.

Prérequis
Kit de développement logiciel (SDK) .NET 7.0

Créer un projet d’application web


Ouvrez un interpréteur de commandes, puis entrez la commande suivante :
CLI .NET

dotnet new webapp -o aspnetcoreapp

La commande précédente :

Crée une application web.


Le paramètre -o aspnetcoreapp crée un répertoire nommé aspnetcoreapp avec les
fichiers sources de l’application.

Approuver le certificat de développement


Approuvez le certificat de développement HTTPS :

Windows

CLI .NET

dotnet dev-certs https --trust

La commande précédente affiche la boîte de dialogue suivante :

Sélectionnez Oui si vous acceptez d’approuver le certificat de développement.


Pour plus d’informations, consultez Approuver le certificat de développement ASP.NET
Core HTTPS

Exécuter l’application
Exécutez les commandes suivantes :

CLI .NET

cd aspnetcoreapp
dotnet watch run

Une fois que l’interface de commande indique que l’application a démarré, accédez à
https://localhost:{port} , où {port} correspond au port aléatoire utilisé.

Modifier une page Razor


Ouvrez Pages/Index.cshtml , puis modifiez et enregistrez la page avec le balisage en
surbrillance suivant :

CSHTML

@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}

<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Hello, world! The time on the server is @DateTime.Now</p>
</div>

Accédez à https://localhost:{port} , actualisez la page et vérifiez que les modifications


sont affichées.

Étapes suivantes
Dans ce didacticiel, vous avez appris à :

" Créer un projet application web.


" Approuver le certificat de développement.
" Exécutez le projet .
" Apportez la modification souhaitée.

Pour en savoir plus sur ASP.NET Core, consultez l’article suivant :

Vue d’ensemble d’ASP.NET Core

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Nouveautés d’ASP.NET Core 9.0
Article • 15/02/2024

Cet article met en évidence les modifications les plus importantes dans ASP.NET Core
9.0 et fournit des liens vers la documentation appropriée.

Cet article sera mis à jour à mesure que de nouvelles versions préliminaires sont mises à
disposition.

Blazor
Cette section décrit les nouvelles fonctionnalités pour Blazor.

La préversion 1 n’inclut pas de nouvelles fonctionnalités Blazor. De nouvelles


fonctionnalités Blazor sont prévues pour la préversion 2 en mars 2024.

SignalR
Cette section décrit les nouvelles fonctionnalités pour SignalR.

Prise en charge du type polymorphe dans SignalR Hubs


Les méthodes Hub peuvent désormais accepter une classe de base au lieu de la classe
dérivée pour prendre en charge les scénarios polymorphes. Le type de base doit être
annoté pour autoriser le polymorphisme.

C#

public class MyHub : Hub


{
public void Method(JsonPerson person)
{
if (person is JsonPersonExtended)
{
}
else if (person is JsonPersonExtended2)
{
}
else
{
}
}
}
[JsonPolymorphic]
[JsonDerivedType(typeof(JsonPersonExtended), nameof(JsonPersonExtended))]
[JsonDerivedType(typeof(JsonPersonExtended2), nameof(JsonPersonExtended2))]
private class JsonPerson
{
public string Name { get; set; }
public Person Child { get; set; }
public Person Parent { get; set; }
}

private class JsonPersonExtended : JsonPerson


{
public int Age { get; set; }
}

private class JsonPersonExtended2 : JsonPerson


{
public string Location { get; set; }
}

API minimales
Cette section décrit les nouvelles fonctionnalités pour les API minimales.

Authentification et autorisation
Cette section décrit les nouvelles fonctionnalités pour l’authentification et l’autorisation.

Divers
Les sections suivantes décrivent diverses nouvelles fonctionnalités.

Améliorations du débogage des dictionnaires


L’affichage de débogage des dictionnaires et d’autres collections clé-valeur a une
disposition améliorée. La clé s’affiche dans la colonne clé du débogueur au lieu d’être
concaténée avec la valeur. Les images suivantes montrent l’ancien et le nouvel affichage
d’un dictionnaire dans le débogueur.

Avant :
Après :

ASP.NET Core a de nombreuses collections clé-valeur. Cette expérience de débogage


améliorée s’applique à :

En-têtes HTTP
Chaînes de requête
Formulaires
Cookies
Afficher les données
Données de routage
Fonctionnalités

Ressources supplémentaires

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Nouveautés d’ASP.NET Core 8.0
Article • 30/11/2023

Cet article met en évidence les modifications les plus importantes dans ASP.NET Core
8.0 et fournit des liens vers la documentation appropriée.

Blazor

Interface utilisateur web de pile complète


Avec la sortie de .NET 8, Blazor s'agit d'un framework d'interface utilisateur Web
complet permettant de développer des applications qui restituent du contenu au niveau
du composant ou de la page avec :

Rendu du serveur statique (également appelé rendu statique côté serveur, SSR
statique) pour générer du code HTML statique sur le serveur.
Rendu du serveur interactif (également appelé rendu interactif côté serveur, SSR
interactif) pour générer des composants interactifs avec le prérendu sur le serveur.
Rendu WebAssembly interactif (également appelé rendu côté client, CSR, qui est
toujours supposé être interactif) pour générer des composants interactifs sur le
client avec le prérendu sur le serveur.
Rendu automatique interactif (automatique) pour utiliser initialement le runtime
ASP.NET Core côté serveur pour le rendu et l’interactivité du contenu. Le runtime
.NET WebAssembly sur le client est utilisé pour le rendu et l’interactivité qui suivent
après le téléchargement du pack Blazor et l’activation du runtime WebAssembly. Le
rendu automatique interactif offre généralement l’expérience de démarrage
d’application la plus rapide.

Les modes de rendu interactifs pré-affichent également le contenu par défaut.

Pour plus d’informations, consultez les articles suivants :

Notions de base ASP.NET Core Blazor : de nouvelles sections sur le rendu et les
concepts statiques/interactifs apparaissent en haut de l’article.
Modes de rendu ASP.NET Core Blazor
Couverture de la migration : migrer d’ASP.NET Core 7.0 vers 8.0

Des exemples de la documentation Blazor ont été mis à jour pour une utilisation dans
Web Apps Blazor. Les exemples Blazor Server restent dans le contenu avec version pour
.NET 7 ou version antérieure.
Nouvel article sur les bibliothèques de classes avec rendu
statique côté serveur (SSR statique)
Nous avons ajouté un nouvel article qui aborde la création d’une bibliothèque de
composants dans des bibliothèques de classes (RCL) Razor avec le rendu statique côté
serveur (SSR statique).

Pour obtenir plus d’informations, voir les bibliothèques de classe ASP.NET Core Razor
(RCL) avec rendu statique côté serveur (SSR statique).

Nouvel article sur les problèmes de mise en cache HTTP


Nous avons ajouté un nouvel article qui aborde certains des problèmes courants de
mise en cache HTTP qui peuvent se produire lors de la mise à niveau d’applications
Blazor dans des versions principales et comment résoudre des problèmes de mise en
cache HTTP.

Si vous souhaitez obtenir plus d’informations, consultez Éviter les problèmes de mise en
cache HTTP lors de la mise à niveau d’applicationsBlazor ASP.NET Core.

Nouveau modèle d'application Web Blazor


Nous avons introduit un nouveau modèle de projet Blazor : le modèle d’application web
Blazor. Le nouveau modèle fournit un point de départ unique pour utiliser des
composants Blazor afin de créer n'importe quel style d'interface utilisateur Web. Le
modèle associe les atouts des modèles existants Blazor Server et Blazor WebAssembly
d’hébergement aux nouvelles fonctionnalités Blazor ajoutées dans .NET 8 : rendu côté
serveur statique, rendu de diffusion en continu, navigation et gestion des formulaires
améliorés, et possibilité d’ajouter l’interactivité en utilisant Blazor Server ou Blazor
WebAssembly par composant.

Dans le cadre de l'unification des différents modèles d'hébergement Blazor en un seul


modèle dans .NET 8, nous consolidons également le nombre de modèles de projet
Blazor. Nous avons supprimé le modèle Blazor Server et l'option ASP.NET Core Hosted a
été supprimée du modèle Blazor WebAssembly. Ces deux scénarios sont représentés par
des options lors de l’utilisation du modèle Web App Blazor.

7 Notes

Les applications existantes Blazor Server et Blazor WebAssembly restent prises en


charge dans .NET 8. En option, ces applications peuvent être mises à jour pour
utiliser les nouvelles fonctionnalités de l'interface utilisateur Blazor Web complète.

Pour plus d’informations sur le nouveau modèle Web App Blazor, consultez les articles
suivants :

Outils pour ASP.NET Core Blazor


Structure de projet Blazor ASP.NET Core

Nouveaux initialiseurs JS pour Blazor Web Apps


Pour Blazor Server, Blazor WebAssemblyet Blazor Hybrid les applications :

beforeWebStart est utilisé pour les tâches telles que la personnalisation du

processus de chargement, le niveau de journalisation et d’autres options.


afterWebStarted est utilisé pour des tâches telles que l’inscription d’écouteurs

d’événements Blazor et de types d’événements personnalisés.

Les initialiseurs hérités JS précédents ne sont pas appelés par défaut dans une
application web Blazor. Pour Blazor Web Apps, un nouvel ensemble d’initialiseurs JS est
utilisé : beforeWebStart , afterWebStarted , beforeServerStart , afterServerStarted ,
beforeWebAssemblyStart et afterWebAssemblyStarted .

Pour plus d’informations, consultez Démarrage ASP.NET Core Blazor.

Fractionnement de l’assistance en matière de prérendu et


d’intégration
Pour les versions antérieures de .NET, nous avons abordé le prérendu et l’intégration
dans un seul article. Pour simplifier et concentrer notre portée, nous avons fractionné les
sujets dans les articles suivants qui ont été mis à jour pour .NET 8 :

Prérendu des composants Razor ASP.NET Core


Intégrer des composants ASP.NET Core Razor aux applications ASP.NET Core

Conserver l'état du composant dans une application Web


Blazor
Vous pouvez conserver et lire l'état du composant dans une application Web Blazor à
l'aide du service existant PersistentComponentState. Ceci est utile pour conserver l'état
des composants pendant le prérendu.
Blazor Web Apps conservent automatiquement tout état enregistré au niveau de
l’application créé pendant le prérendu, éliminant ainsi le besoin du Tag Helper d’état du
composant persistant.

Gestion des formulaires et liaison de modèles


les composants Blazor peuvent désormais gérer les requêtes de formulaire soumises, y
compris la liaison de modèle et la validation des données de la requête. Les composants
peuvent implémenter des formulaires avec des gestionnaires de formulaires distincts en
utilisant la balise HTML standard <form> ou en utilisant le composant existant EditForm .

La liaison du modèle de formulaire dans Blazor honore les attributs du contrat de


données (par exemple, [DataMember] et [IgnoreDataMember] ) pour personnaliser la façon
dont les données du formulaire sont liées au modèle.

Une nouvelle prise en charge anti-contrefaçon est incluse dans .NET 8. Un nouveau
composant AntiforgeryToken restitue un jeton anti-contrefaçon sous forme de champ
caché et le nouvel attribut [RequireAntiforgeryToken] active la protection anti-
contrefaçon. Si une vérification anti-contrefaçon échoue, une réponse 400 (Bad Request)
est renvoyée sans traitement du formulaire. Les nouvelles fonctionnalités anti-
contrefaçon sont activées par défaut pour les formulaires basés sur des formulaires
HTML standard sur Editform et peuvent être appliquées manuellement à ceux-ci.

Pour plus d’informations, consultez Vue d’ensemble des formulaires Blazor ASP.NET
Core.

Navigation améliorée et gestion des formulaires


Le rendu statique côté serveur (SSR statique) effectue généralement une actualisation
complète de la page chaque fois que l’utilisateur accède à une nouvelle page ou soumet
un formulaire. Dans .NET 8, Blazor peut améliorer la navigation dans les pages et la
gestion des formulaires en interceptant la requête et en exécutant une requête de
récupération à la place. Blazor gère ensuite le contenu de la réponse rendue en le
corrigeant dans le DOM du navigateur. La navigation améliorée et la gestion des
formulaires évitent le besoin d'une actualisation complète de la page et préservent
davantage l'état de la page, de sorte que les pages se chargent plus rapidement et plus
facilement. La navigation améliorée est activée par défaut lorsque le script Blazor
( blazor.web.js ) est chargé. La gestion améliorée des formulaires peut éventuellement
être activée pour des formulaires spécifiques.
La nouvelle API de navigation améliorée vous permet d'actualiser la page actuelle en
appelant NavigationManager.Refresh(bool forceLoad = false) .

Pour obtenir plus d’informations, consultez les sections suivantes de l’article


BlazorRoutage et navigation :

Navigation et gestion des formulaires améliorées


Modifications d’emplacement

Nouvel article sur le rendu statique avec navigation


améliorée pour l’interopérabilité JS
Certaines applications peuvent dépendre de l’interopérabilité JS pour effectuer des
tâches d’initialisation spécifiques à chaque page. Lorsque vous utilisez la fonctionnalité
de navigation améliorée de Blazor avec des pages rendues statiquement qui effectuent
des tâches d’initialisation d’interopérabilité JS, il est possible que JS spécifique à la page
ne soit pas réexécuté comme prévu chaque fois qu’une navigation de page améliorée se
produit. Un nouvel article explique comment résoudre ce scénario dans Blazor Web
Apps :

Javascript Blazor ASP.NET Core avec rendu de serveur statique Blazor

Rendu en streaming
Vous pouvez désormais diffuser des mises à jour de contenu sur le flux de réponse
lorsque vous utilisez le rendu statique côté serveur (SSR statique) avec Blazor. Le rendu
en streaming peut améliorer l'expérience utilisateur pour les pages qui effectuent des
tâches asynchrones de longue durée afin d'obtenir un rendu complet en rendant le
contenu dès qu'il est disponible.

Par exemple, pour afficher une page, vous devrez peut-être effectuer une requête de
base de données durables ou un appel API. Normalement, les tâches asynchrones
exécutées dans le cadre du rendu d'une page doivent être terminées avant que la
réponse rendue ne soit envoyée, ce qui peut retarder le chargement de la page. Le
rendu en streaming restitue initialement la page entière avec un contenu d'espace
réservé pendant l'exécution des opérations asynchrones. Une fois les opérations
asynchrones terminées, le contenu mis à jour est envoyé au client sur la même
connexion de réponse et corrigé dans le DOM. L'avantage de cette approche est que la
mise en page principale de l'application s'affiche le plus rapidement possible et que la
page est mise à jour dès que le contenu est prêt.

Pour plus d’informations, consultez le rendu de composants Razor ASP.NET Core.


Injecter des services à clé dans les composants
Blazor prend désormais en charge l'injection de services à clé à l'aide de l'attribut
[Inject] . Les clés permettent de définir la portée de l'enregistrement et de la

consommation des services lors de l'utilisation de l'injection de dépendances. Utilisez la


nouvelle propriété InjectAttribute.Key pour spécifier la clé que le service doit injecter :

C#

[Inject(Key = "my-service")]
public IMyService MyService { get; set; }

La directive @inject Razor ne prend pas en charge les services à clé pour cette version,
mais le travail est suivi par Update @inject pour prendre en charge les services à clé
(dotnet/razor #9286) pour une future version .NET.

Pour plus d’informations, consultez Injection de dépendances Blazor ASP.NET Core.

Accès HttpContext en tant que paramètre en cascade


Vous pouvez désormais accéder au courant HttpContext en tant que paramètre en
cascade à partir d'un composant serveur statique :

C#

[CascadingParameter]
public HttpContext? HttpContext { get; set; }

Il est possible que l’accès à HttpContext à partir d’un composant de serveur statique soit
utile pour inspecter et modifier des en-têtes ou d’autres propriétés.

Pour obtenir un exemple qui transmet l’état HttpContext, l’accès et les jetons
d’actualisation aux composants, consultez les scénarios de sécurité supplémentaires du
côté serveur ASP.NET Core Blazor.

Restituer des composants Razor en dehors de ASP.NET


Core
Vous pouvez désormais restituer les composants Razor en dehors du contexte d'une
requête HTTP. Vous pouvez restituer des composants Razor au format HTML
directement dans une chaîne ou un flux indépendamment de l’environnement
d’hébergement ASP.NET Core. Ceci est pratique pour les scénarios dans lesquels vous
souhaitez générer des fragments HTML, par exemple pour générer un e-mail ou du
contenu de site statique.

Pour plus d’informations, consultez Rendre les composants Razor en dehors de ASP.NET
Core.

Prise en charge des sections


Les nouveaux composants SectionOutlet et SectionContent ajoutent la prise de Blazor
en charge de la spécification de sorties pour le contenu qui peut être rempli
ultérieurement. Les sections sont souvent utilisées pour définir des espaces réservés
dans des mises en page qui sont ensuite remplies par des pages spécifiques. Les
sections sont référencées soit par un nom unique, soit par un ID d'objet unique.

Pour plus d’informations, consultez les sections ASP.NET CoreBlazor.

Prise en charge des pages d'erreur


Les applications Web Blazor peuvent définir une page d'erreur personnalisée à utiliser
avec le middleware de gestion des exceptions ASP.NET Core. Le modèle de projet Web
App Blazor inclut une page d'erreur par défaut ( Components/Pages/Error.razor ) avec un
contenu similaire à celui utilisé dans les applications MVC et Pages Razor. Lorsque la
page d'erreur est rendue en réponse à une requête du Exception Handling Middleware,
la page d'erreur s'affiche toujours en tant que composant de serveur statique, même si
l'interactivité est par ailleurs activée.

Error.razor dans une source de référence 8.0

QuickGrid
Le composant QuickGrid Blazor n'est plus expérimental et fait désormais partie du
framework de .NET 8 Blazor.

QuickGrid est un composant de grille haute performance permettant d'afficher des


données sous forme de tableau. QuickGrid est conçu pour être un moyen simple et
pratique d'afficher vos données, tout en offrant des fonctionnalités puissantes, telles
que le tri, le filtrage, la pagination et la virtualisation.

Pour plus d’informations, consultez Composant QuickGrid ASP.NET Core Blazor.

Itinéraire vers les éléments nommés


Blazor prend désormais en charge l'utilisation du routage côté client pour accéder à un
élément HTML spécifique sur une page à l'aide de fragments d'URL standard. Si vous
spécifiez un identifiant pour un élément HTML à l'aide de l'attribut standard id , Blazor
fait défiler correctement jusqu'à cet élément lorsque le fragment d'URL correspond à
l'identifiant de l'élément.

Pour plus d’informations, consultez Routage et navigation ASP.NET Core Blazor.

Valeurs en cascade du niveau racine


Les valeurs en cascade au niveau racine peuvent être inscrites pour la hiérarchie
complète des composants. Les valeurs et abonnements nommés en cascade pour les
notifications de mise à jour sont pris en charge.

Pour plus d’informations, consultez Valeurs et paramètres en cascade de Blazor ASP.NET


Core.

Virtualiser le contenu vide


Utilisez le nouveau paramètre EmptyContent sur le composant Virtualize pour fournir
du contenu lorsque le composant est chargé et qu'il Items est vide ou
ItemsProviderResult<T>.TotalItemCount est nul.

Pour plus d’informations, consultez Virtualisation des composants ASP.NET Core Razor.

Fermer les circuits lorsqu'il ne reste plus de composants


de serveur interactifs
Les composants du serveur interactif gèrent les événements de l'interface utilisateur
Web à l'aide d'une connexion en temps réel avec le navigateur appelée circuit. Un circuit
et son état associé sont configurés lorsqu’un composant de serveur interactif racine est
rendu. Le circuit est fermé lorsqu’il ne reste plus de composants serveur interactifs sur la
page, ce qui libère des ressources serveur.

Surveiller l'activité du circuit SignalR


Vous pouvez désormais surveiller l'activité des circuits entrants dans les applications
côté serveur à l'aide de la nouvelle méthode CreateInboundActivityHandler sur
CircuitHandler . L’activité du circuit entrant est toute activité envoyée du navigateur au

serveur, comme les événements d’interface utilisateur ou les appels d’interopérabilité


JavaScript-to-.NET.
Pour plus d’informations, consultez Conseils relatifs à ASP.NET Core BlazorSignalR.

Performances d'exécution plus rapides avec le Jiterpreter


Jiterpreter est une nouvelle fonctionnalité d'exécution de .NET 8 qui permet une prise en
charge partielle de la compilation juste-à-temps (JIT) lors de l'exécution sur
WebAssembly afin d'obtenir des performances d'exécution améliorées.

Pour plus d’informations, consultez Héberger et déployer ASP.NET Core Blazor


WebAssembly.

SIMD anticipé (AOT) et gestion des exceptions


La compilation anticipée (AOT) Blazor WebAssembly utilise désormais par défaut le
SIMD à largeur fixe WebAssembly et la gestion des exceptions WebAssembly pour
améliorer les performances d'exécution.

Pour plus d’informations, consultez les articles suivants :

AOA : Single Instruction Multiple Data (SIMD)


AOA : gestion des exceptions

Emballage Webcil adapté au Web


Webcil est un package Web d'assemblys .NET qui supprime le contenu spécifique à
l'exécution native de Windows pour éviter les problèmes lors du déploiement dans des
environnements bloquant le téléchargement ou l'utilisation de fichiers .dll . Webcil est
activé par défaut pour les applications Blazor WebAssembly.

Pour plus d’informations, consultez Héberger et déployer ASP.NET Core Blazor


WebAssembly.

7 Notes

Avant la mise en production de .NET 8, des conseils dans la Disposition du


déploiement pour les applications Blazor WebAssembly hébergées ASP.NET Core
abordent les environnements qui empêchent les clients de télécharger et
d’exécuter des DLL avec une approche de regroupement de plusieurs parties. Dans
.NET 8 ou version ultérieure, Blazor utilise le format de fichier Webcil pour résoudre
ce problème. Le regroupement de plusieurs parties en utilisant le package NuGet
expérimental décrit dans l’article Disposition du déploiement WebAssembly n’est pas
pris en charge pour les applications Blazor dans .NET 8 ou ultérieur. Pour obtenir
plus d’informations, consultez Améliorer un package
Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle pour définir
un format de package personnalisé (dotnet/aspnetcore #36978) . Si vous
souhaitez continuer à utiliser un package groupé de plusieurs parties dans des
applications .NET 8 ou ultérieures, vous pouvez utiliser les conseils de l’article pour
créer votre propre package groupé NuGet de plusieurs parties, mais il ne sera pas
pris en charge par Microsoft.

Améliorations du débogage de Blazor WebAssembly


Lors du débogage de .NET sur WebAssembly, le débogueur télécharge désormais les
données de symboles à partir des emplacements de symboles configurés dans les
préférences de Visual Studio. Cela améliore l’expérience de débogage pour les
applications qui utilisent des packages NuGet.

Vous pouvez désormais déboguer des applications Blazor WebAssembly à l'aide de


Firefox. Le débogage des applications Blazor WebAssembly nécessite la configuration du
navigateur pour le débogage à distance, puis la connexion au navigateur à l'aide des
outils de développement du navigateur via le proxy de débogage .NET WebAssembly.
Le débogage de Firefox à partir de Visual Studio n'est pas pris en charge pour le
moment.

Pour plus d’informations, consultez Déboguer des applications Blazor ASP.NET Core.

Compatibilité avec Azure Policy de sécurité du contenu


(CSP)
Blazor WebAssembly ne nécessite plus d'activer la source du script unsafe-eval lors de
la spécification Azure Policy de sécurité du contenu (CSP).

Pour plus d’informations, consultez Appliquer une stratégie de sécurité de contenu pour
ASP.NET Core Blazor.

Gérer les exceptions interceptées en dehors du cycle de


vie d’un composant Razor
Utilisez ComponentBase.DispatchExceptionAsync dans un composant Razor pour traiter
les exceptions levées en dehors de la pile des appels de cycle de vie du composant. Cela
permet au code du composant de traiter les exceptions comme si elles sont des
exceptions de méthode de cycle de vie. Par la suite, les mécanismes de gestion des
erreurs de Blazor, tels que les limites d'erreur, peuvent traiter les exceptions.

Pour plus d’informations, consultez Gérer les erreurs dans les applications ASP.NET Core
Blazor.

Configurer le runtime .NET WebAssembly


Le runtime .NET WebAssembly peut désormais être configuré pour le démarrage Blazor.

Pour plus d’informations, consultez Démarrage ASP.NET Core Blazor.

Configuration des délais de connexion dans


HubConnectionBuilder

Les solutions de contournement précédentes pour la configuration des délais


d'expiration de connexion au hub peuvent être remplacées par une configuration
formelle du délai d'expiration du générateur de connexion au hub SignalR.

Pour plus d’informations, consultez les rubriques suivantes :

Conseils pour BlazorSignalR ASP.NET Core


Héberger et déployer ASP.NET Core Blazor WebAssembly
Héberger et déployer des applications Blazor ASP.NET Core côté serveur

Les modèles de projet sont abandonnés Open Iconic


Les modèles de projet Blazor ne dépendent plus d'Open Iconic pour les icônes.

Prise en charge des événements d'annulation et de


fermeture de dialogue
Blazor prend désormais en charge les événements cancel et close sur l'élément
HTML dialog .

Dans l’exemple suivant :

OnClose est appelé lorsque la boîte de dialogue my-dialog est fermée avec le

bouton Fermer.
OnCancel est appelé lorsque la boîte de dialogue est annulée avec la touche Échap .

Lorsqu’une boîte de dialogue HTML est fermée avec la touche Échap , les
événements cancel et close sont déclenchés.
razor

<div>
<p>Output: @message</p>

<button onclick="document.getElementById('my-dialog').showModal()">
Show modal dialog
</button>

<dialog id="my-dialog" @onclose="OnClose" @oncancel="OnCancel">


<p>Hi there!</p>

<form method="dialog">
<button>Close</button>
</form>
</dialog>
</div>

@code {
private string? message;

private void OnClose(EventArgs e) => message += "onclose, ";

private void OnCancel(EventArgs e) => message += "oncancel, ";


}

BlazorIdentity Interface utilisateur


Blazor prend en charge la génération d'une Blazorinterface utilisateur complète Identity
lorsque vous choisissez l'option d'authentification pour les comptes individuels. Vous
pouvez soit sélectionner l’option Comptes individuels dans la boîte de dialogue
Nouveau projet pour Blazor Web à partir de Visual Studio, soit transmettre l’option -
au|--auth définie sur Individual à partir de la ligne de commande lorsque vous créez

un projet.

Pour plus d’informations, consultez les ressources suivantes :

Applications Blazor sécurisées côté serveur ASP.NET Core


Nouveautés de l’identité dans .NET 8 (billet de blog)

Sécuriser Blazor WebAssembly avec ASP.NET Core


Identity
La documentation Blazor héberge un nouvel article et un exemple d’application pour
couvrir la sécurisation d’une application Blazor WebAssembly autonome avec ASP.NET
Core Identity.
Pour plus d’informations, consultez les ressources suivantes :

Sécuriser ASP.NET Core Blazor WebAssembly avec ASP.NET Core Identity


Nouveautés de l’identité dans .NET 8 (billet de blog)

Blazor Server avec le routage Yarp


Le routage et le lien profond pour Blazor Server avec Yarp fonctionnent correctement
dans .NET 8.

Pour plus d’informations, consultez Migrer de ASP.NET Core 7.0 vers 8.0.

Blazor Hybrid
Les articles suivants décrire les modifications pour Blazor Hybrid .NET 8 :

Résoudre des problèmes ASP.NET Core Blazor Hybrid: un nouvel article explique
comment utiliser la journalisation BlazorWebView.
Générer une application .NET MAUIBlazor Hybrid : le nom du modèle de projet
.NET MAUI Blazor a changé en .NET MAUI Blazor Hybrid.
ASP.NET Core Blazor Hybrid: BlazorWebView obtient une méthode
TryDispatchAsync qui appelle un Action<ServiceProvider> spécifié de manière

asynchrone et transmet les services délimités disponibles dans les composants


Razor. Cela permet au code de l’interface utilisateur native d’accéder à des services
délimités tels que NavigationManager .
Routage et navigation ASP.NET Core Blazor Hybrid : utilisez la propriété
BlazorWebView.StartPath pour obtenir ou définir le chemin de navigation initial
dans le contexte de navigation Blazor quand le composant Razor est chargé.

SignalR

Nouvelle approche pour définir le délai d’expiration du


serveur et l’intervalle Keep-Alive
ServerTimeout (valeur par défaut : 30 secondes) et KeepAliveInterval (valeur par défaut :
15 secondes) peuvent être définies directement dans HubConnectionBuilder.

Approche précédente pour les clients JavaScript


L’exemple suivant montre l’affectation de valeurs au double des valeurs par défaut dans
ASP.NET Core 7.0 ou versions antérieures :

JavaScript

var connection = new signalR.HubConnectionBuilder()


.withUrl("/chatHub")
.build();

connection.serverTimeoutInMilliseconds = 60000;
connection.keepAliveIntervalInMilliseconds = 30000;

Nouvelle approche pour les clients JavaScript

L’exemple suivant montre la nouvelle approche permettant d’attribuer des valeurs au


double des valeurs par défaut dans ASP.NET Core 8.0 ou version ultérieure :

JavaScript

var connection = new signalR.HubConnectionBuilder()


.withUrl("/chatHub")
.withServerTimeoutInMilliseconds(60000)
.withKeepAliveIntervalInMilliseconds(30000)
.build();

Approche précédente pour le client JavaScript d’une application


Blazor Server

L’exemple suivant montre l’affectation de valeurs au double des valeurs par défaut dans
ASP.NET Core 7.0 ou versions antérieures :

JavaScript

Blazor.start({
configureSignalR: function (builder) {
let c = builder.build();
c.serverTimeoutInMilliseconds = 60000;
c.keepAliveIntervalInMilliseconds = 30000;
builder.build = () => {
return c;
};
}
});
Nouvelle approche pour l’application JavaScript Blazor côté client
ou serveur

L’exemple suivant montre la nouvelle approche permettant d’attribuer des valeurs au


double des valeurs par défaut dans ASP.NET Core 8.0 ou version ultérieure pour les
applications web Blazor et Blazor Server.

Application webBlazor :

JavaScript

Blazor.start({
circuit: {
configureSignalR: function (builder) {
builder.withServerTimeout(60000).withKeepAliveInterval(30000);
}
}
});

Blazor Server:

JavaScript

Blazor.start({
configureSignalR: function (builder) {
builder.withServerTimeout(60000).withKeepAliveInterval(30000);
}
});

Approche précédente pour les clients .NET


L’exemple suivant montre l’affectation de valeurs au double des valeurs par défaut dans
ASP.NET Core 7.0 ou versions antérieures :

C#

var builder = new HubConnectionBuilder()


.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();

builder.ServerTimeout = TimeSpan.FromSeconds(60);
builder.KeepAliveInterval = TimeSpan.FromSeconds(30);

builder.On<string, string>("ReceiveMessage", (user, message) => ...

await builder.StartAsync();
Nouvelle approche pour les clients .NET
L’exemple suivant montre la nouvelle approche permettant d’attribuer des valeurs au
double des valeurs par défaut dans ASP.NET Core 8.0 ou version ultérieure :

C#

var builder = new HubConnectionBuilder()


.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.WithServerTimeout(TimeSpan.FromSeconds(60))
.WithKeepAliveInterval(TimeSpan.FromSeconds(30))
.Build();

builder.On<string, string>("ReceiveMessage", (user, message) => ...

await builder.StartAsync();

Reconnexion avec état de SignalR


La reconnexion avec état de SignalR réduit le temps d’arrêt perçu des clients en cas de
déconnexion temporaire de leur connexion réseau, par exemple lors du basculement de
connexions réseau ou d’une brève perte temporaire d’accès.

La reconnexion avec état permet cela en :

Mettant temporairement en mémoire tampon des données sur le serveur et le


client.
Accusant réception des messages reçus (procédure « ACK-ing ») par le serveur et
le client.
Reconnaissant lorsqu’une connexion est renvoyée et en relisant des messages qui
ont pu être envoyés pendant l’arrêt de la connexion.

La reconnexion avec état est disponible dans ASP.NET Core 8.0 et versions ultérieures.

Optez pour la reconnexion avec état au point de terminaison du hub de serveur et au


client :

Mettez à jour la configuration du point de terminaison du hub de serveur pour


activer l’option AllowStatefulReconnects :

C#

app.MapHub<MyHub>("/hubName", options =>


{
options.AllowStatefulReconnects = true;
});

Si vous le souhaitez, la taille maximale de la mémoire tampon en octets autorisée


par le serveur peut être définie globalement ou pour un hub spécifique avec
l’option StatefulReconnectBufferSize :

Option StatefulReconnectBufferSize définie globalement :

C#

builder.AddSignalR(o => o.StatefulReconnectBufferSize = 1000);

Option StatefulReconnectBufferSize définie pour un hub spécifique :

C#

builder.AddSignalR().AddHubOptions<MyHub>(o =>
o.StatefulReconnectBufferSize = 1000);

L’option StatefulReconnectBufferSize est facultative avec une valeur par défaut de


100 000 octets.

Mettez à jour le code client JavaScript ou TypeScript pour activer l’option


withStatefulReconnect :

JavaScript

const builder = new signalR.HubConnectionBuilder()


.withUrl("/hubname")
.withStatefulReconnect({ bufferSize: 1000 }); // Optional, defaults
to 100,000
const connection = builder.build();

L’option bufferSize est facultative avec une valeur par défaut de 100 000 octets.

Mettez à jour le code client .NET pour activer l’option WithStatefulReconnect :

C#

var builder = new HubConnectionBuilder()


.WithUrl("<hub url>")
.WithStatefulReconnect();
builder.Services.Configure<HubConnectionOptions>(o =>
o.StatefulReconnectBufferSize = 1000);
var hubConnection = builder.Build();

L’option StatefulReconnectBufferSize est facultative avec une valeur par défaut de


100 000 octets.

Pour plus d’informations, consultez Configurer la reconnexion avec état.

API minimales
Cette section décrit les nouvelles fonctionnalités pour les API minimales. Consultez
également la section sur l’AOT natif pour plus d’informations sur les API minimales.

Remplacement de culture de l’utilisateur


À compter de ASP.NET Core 8.0, la propriété
RequestLocalizationOptions.CultureInfoUseUserOverride permet à l’application de
décider s’il faut ou non utiliser les paramètres Windows autres que ceux par défaut pour
les propriétés CultureInfoDateTimeFormat et NumberFormat. Cela n’a aucun impact sur
Linux. Cela correspond directement à UseUserOverride.

C#

app.UseRequestLocalization(options =>
{
options.CultureInfoUseUserOverride = false;
});

Liaison aux formulaires


La liaison explicite aux valeurs de formulaire à l’aide de l’attribut [FromForm] est
désormais prise en charge. Les paramètres liés à la requête avec [FromForm] incluent un
jeton anti-falsification. Le jeton anti-falsification est validé lors du traitement de la
requête.

La liaison inférée aux formulaires à l’aide des types IFormCollection, IFormFileet


IFormFileCollection est également prise en charge. Les métadonnées OpenAPI sont
inférées pour les paramètres de formulaire afin de prendre en charge l’intégration à
l’interface utilisateur Swagger.

Pour plus d'informations, voir :


Liaison explicite à partir des valeurs de formulaire.
Liaison aux formulaires avec IFormCollection, IFormFile et IFormFileCollection.
Liaison de formulaire dans des API minimales

La liaison à partir de formulaires est désormais prise en charge pour :

Les collections, par exemple Liste et Dictionnaire


Les types complexes, par exemple, Todo ou Project

Pour plus d’informations, consultez Liaison à des collections et à des types complexes à
partir de formulaires.

Anti-contrefaçon avec des API minimales


Cette version ajoute un intergiciel pour valider les jetons anti-falsification qui sont
utilisés pour atténuer les attaques de falsification de requête intersites. Appelez
AddAntiforgery pour inscrire des services d’antifalsification dans DI.
WebApplicationBuilder ajoute automatiquement l’intergiciel lorsque les services d’anti-

falsification ont été enregistrés dans le conteneur DI. Les jetons d’antifalsification sont
utilisés pour atténuer les attaques de falsification de requête intersites.

C#

var builder = WebApplication.CreateBuilder();

builder.Services.AddAntiforgery();

var app = builder.Build();

app.UseAntiforgery();

app.MapGet("/", () => "Hello World!");

app.Run();

L’intergiciel d’antifalsification :

Ne court-circuite pas l’exécution du reste du pipeline de requête.


Définit l’IAntiforgeryValidationFeature dans le HttpContext.Features de la
requête actuelle.

Le jeton d’antifalsification n’est validé que si :

Le point de terminaison contient des métadonnées implémentant


IAntiforgeryMetadata où RequiresValidation=true .
La méthode HTTP associée au point de terminaison est une méthode HTTP
pertinente. Les méthodes pertinentes sont toutes les méthodes HTTP , à
l’exception de TRACE, OPTIONS, HEAD et GET.
La requête est associée à un point de terminaison valide.

Pour plus d’informations, consultez Anti-falsification avec des API minimales.

Nouvelle interface IResettable dans ObjectPool


Microsoft.Extensions.ObjectPool prend en charge le regroupement d’instances d’objet
en mémoire. Les applications peuvent utiliser un pool d’objets si l’allocation ou
l’initialisation des valeurs est coûteuse.

Dans cette version, nous avons simplifié l’utilisation du pool d’objets en ajoutant
l’interface IResettable. Les types réutilisables doivent souvent être réinitialisés à un état
par défaut entre les utilisations. Les types IResettable sont automatiquement
réinitialisés lorsqu’ils sont retournés à un pool d’objets.

Pour plus d’informations, consultez l’exemple ObjectPool.

AOT natif
La prise en charge d’AOT (ahead-of-time) natif .NET a été ajoutée. Les applications
publiées à l’aide d’AOT peuvent avoir de significativement meilleures performances : une
taille d’application et une utilisation de mémoire plus faibles et un démarrage plus
rapide. L’AOT natif est actuellement pris en charge par les applications gRPC, API
minimale et de service worker. Pour plus d’informations, consultez Prise en charge
d’ASP.NET Core pour AOT natif et Tutoriel : Publier une application ASP.NET Core à
l’aide d’AOT natif. Pour plus d’informations sur les problèmes connus liés à la
compatibilité d’ASP.NET Core avec l’AOT natif, consultez le problème GitHub
dotnet/core #8288 .

Bibliothèques et AOT natif


La plupart des bibliothèques populaires utilisées dans les projets ASP.NET Core
présentent actuellement des problèmes de compatibilité lorsqu’elles sont utilisées dans
un projet ciblant l’AOT native, par exemple :

Utilisation de la réflexion pour inspecter et découvrir des types.


Chargement conditionnel de bibliothèques au moment de l’exécution.
Génération de code à la volée pour implémenter des fonctionnalités.
Les bibliothèques utilisant ces fonctionnalités dynamiques doivent être mises à jour
pour fonctionner avec l’AOT native. Elles peuvent être mises à jour à l’aide d’outils tels
que les générateurs de sources Roslyn.

Les auteurs de bibliothèque qui espèrent prendre en charge l’AOT native sont
encouragés à :

Découvrir les exigences de compatibilité d’AOT native.


Préparer le découpage de la bibliothèque.

Nouveau modèle de projet


Le nouveau modèle de projet API Web ASP.NET Core (AOT natif) (nom court
webapiaot ) crée un projet avec la publication AOT activée. Pour plus d’informations,

consultez Le modèle d’API Web (AOT natif).

Nouvelle méthode CreateSlimBuilder


La méthode CreateSlimBuilder() utilisée dans le modèle d’API web (compilation AOT
native) initialise WebApplicationBuilder avec les fonctionnalités ASP.NET Core minimales
nécessaires à l’exécution d’une application. La méthode CreateSlimBuilder comprend
les fonctionnalités suivantes, qui sont généralement nécessaires à une expérience de
développement efficace :

JSConfiguration du fichier ON pour appsettings.json et appsettings.


{EnvironmentName}.json .

Configuration des secrets utilisateur.


Journalisation de la console.
Configuration de la journalisation.

Pour plus d'informations, voir La méthode CreateSlimBuilder.

Nouvelle méthode CreateEmptyBuilder


Il existe une autre nouvelle méthode de fabrique WebApplicationBuilder pour générer
de petites applications qui contiennent uniquement les fonctionnalités nécessaires :
WebApplication.CreateEmptyBuilder(WebApplicationOptions options) . Ce
WebApplicationBuilder est créé sans comportement intégré. L’application qu’elle génère

contient uniquement les services et les middlewares (intergiciels) explicitement


configurés.

Voici un exemple d’utilisation de cette API pour créer une petite application web :
C#

var builder = WebApplication.CreateEmptyBuilder(new


WebApplicationOptions());
builder.WebHost.UseKestrelCore();

var app = builder.Build();

app.Use(async (context, next) =>


{
await context.Response.WriteAsync("Hello, World!");
await next(context);
});

Console.WriteLine("Running...");
app.Run();

La publication de ce code avec compilation AOT native à l’aide de .NET 8 Preview 7 sur
une machine linux-x64 permet d’obtenir un exécutable natif autonome d’environ
8,5 Mo.

Taille d’application réduite avec prise en charge HTTPS


configurable
Nous avons réduit davantage la taille du binaire AOT natif pour les applications qui
n’ont pas besoin de la prise en charge HTTPS ou HTTP/3. Il est courant de ne pas utiliser
le protocole HTTPS ou HTTP/3 pour les applications qui s’exécutent derrière un proxy de
terminaison TLS, par exemple, hébergées sur Azure. La nouvelle méthode
WebApplication.CreateSlimBuilder omet cette fonctionnalité par défaut. Elle peut être

ajoutée en appelant builder.WebHost.UseKestrelHttpsConfiguration() pour HTTPS ou


builder.WebHost.UseQuic() pour HTTP/3. Pour plus d'informations, voir La méthode

CreateSlimBuilder.

JSSérialisation ON des types IAsyncEnumerable<T> générés


par le compilateur
De nouvelles fonctionnalités ont été ajoutées à System.Text.Json pour mieux prendre en
charge l’AOT natif. Ces nouvelles fonctionnalités ajoutent des fonctionnalités pour le
mode de génération source de System.Text.Json , car la réflexion n’est pas prise en
charge par l’AOT.

L’une des nouvelles fonctionnalités est la prise en charge de la JSsérialisation ON des


implémentations IAsyncEnumerable<T> implémentées par le compilateur C#. Cette
prise en charge ouvre leur utilisation dans les projets ASP.NET Core configurés pour
publier l’AOT natif.

Cette API est utile dans les scénarios où un gestionnaire de routage utilise yield return
pour retourner de façon asynchrone une énumération. Par exemple, pour matérialiser
des lignes à partir d’une requête de base de données. Pour plus d’informations,
consultez la Prise en charge du type indicible dans l’annonce de la préversion 4 de
.NET 8.

Pour plus d’informations sur les autres améliorations apportées à la génération de


source System.Text.Json , consultez Améliorations apportées à la sérialisation dans .NET
8.

API de niveau supérieur annotées pour les avertissements


de découpage
Les points d’entrée principaux vers les sous-systèmes qui ne fonctionnent pas de
manière fiable avec l’AOT natif sont désormais annotés. Lorsque ces méthodes sont
appelées à partir d’une application avec l’AOT natif activé, un avertissement est fourni.
Par exemple, le code suivant génère un avertissement lors de l’appel de AddControllers ,
car cette API n’est pas sécurisée pour le découpage et n’est pas prise en charge par
l’AOT natif.

Générateur de délégués de requête


Pour rendre les API minimales compatibles avec l’AOT natif, nous introduisons le
générateur de délégués de requête (RDG). Le RDG est un générateur de source qui fait
la même chose que le RequestDelegateFactory (RDF). Autrement dit, il transforme les
différents appels MapGet() . MapPost() et les appels semblables dans des instances
RequestDelegate associées aux itinéraires spécifiés. Mais plutôt que de le faire en
mémoire dans une application au démarrage, le RDG le fait au moment de la
compilation et génère du code C# directement dans le projet. Le RDG :

Supprime la génération à l’exécution de ce code.


Garantit que les types utilisés dans les API sont statiquement analysables par la
chaîne d’outils d’AOT natif.
Permet de garantir que le code requis n’est pas découpé.

Nous travaillons à nous assurer que le plus grand nombre possible de fonctionnalités de
l’API minimale sont prises en charge par le RDG et donc compatibles avec l’AOT natif.

Le RDG est activé automatiquement dans un projet lors de la publication avec l’AOT
natif activé. Le RDG peut être activé manuellement même si vous n’utilisez pas l’AOT
natif en définissant
<EnableRequestDelegateGenerator>true</EnableRequestDelegateGenerator> dans le fichier

projet. Cela peut être utile lors de l’évaluation initiale de la préparation d’un projet pour
l’AOT native ou pour accélérer le démarrage d’une application.

Amélioration des performances à l’aide d’intercepteurs


Le Générateur de délégués de requête utilise la nouvelle fonctionnalité du compilateur
d’intercepteurs C# 12 pour prendre en charge l’interception des appels à des méthodes
de Mappage d’API minimales avec des variantes générées de manière statique au
moment de l’exécution. L’utilisation d’intercepteurs permet d’améliorer les
performances de démarrage des applications compilées avec PublishAot .

Journalisation et gestion des exceptions dans les API


minimales générées au moment de la compilation
Les API minimales générées au moment de l’exécution prennent en charge la
journalisation automatique (ou la levée d’exceptions dans les environnements de
développement) lorsque la liaison de paramètre échoue. .NET 8 introduit la même prise
en charge des API générées au moment de la compilation via le générateur de délégués
de requête (RDG). Pour plus d’informations, consultez Journalisation et gestion des
exceptions dans les API minimales générées au moment de la compilation .

AOT et System.Text.Json
Les API minimales sont optimisées pour la réception et le retour JSde charges utiles ON
à l’aide de System.Text.Json , de sorte que les exigences de compatibilité pour JSON et
l’AOT natif s’appliquent également. La compatibilité avec l’AOT natif nécessite
l’utilisation du générateur de source System.Text.Json . Tous les types acceptés en tant
que paramètres ou retournés par les délégués de requête dans les API minimales
doivent être configurés sur un JsonSerializerContext inscrit via l’injection de
dépendances d’ASP.NET Core, par exemple :

C#

// Register the JSON serializer context with DI


builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

...

// Add types used in the minimal API app to source generated JSON serializer
content
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

Pour plus d’informations sur l’API TypeInfoResolverChain, consultez les ressources


suivantes :

JsonSerializerOptions.TypeInfoResolverChain
Générateurs de sources de chaîne
Modifications nécessaires à la prise en charge de la génération de source

Bibliothèques et AOT natif


De nombreuses bibliothèques courantes disponibles pour les projets ASP.NET Core ont
aujourd’hui des problèmes de compatibilité si elles sont utilisées dans un projet ciblant
l’AOT natif. Les bibliothèques populaires s’appuient souvent sur les fonctionnalités
dynamiques de la réflexion .NET pour inspecter et découvrir des types, charger
conditionnellement des bibliothèques au moment de l’exécution et générer du code à la
volée pour implémenter leurs fonctionnalités. Ces bibliothèques doivent être mises à
jour pour fonctionner avec l’AOT natif à l’aide d’outils tels que les générateurs de source
Roslyn.
Les auteurs de bibliothèques qui souhaitent en savoir plus sur la préparation de leurs
bibliothèques pour l’AOT natif sont encouragés à commencer par préparer leur
bibliothèque pour le découpage et en savoir plus sur les exigences de compatibilité avec
l’AOT natif.

Serveurs Kestrel et HTTP.sys


Il existe plusieurs nouvelles fonctionnalités pour Kestrel et HTTP.sys.

Prise en charge des canaux nommés dans Kestrel


Les canaux nommés sont une technologie populaire pour la création d’une
communication entre processus (IPC) entre les applications Windows. Vous pouvez
maintenant créer un serveur IPC à l’aide de .NET, Kestrel et des canaux nommés.

C#

var builder = WebApplication.CreateBuilder(args);


builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ListenNamedPipe("MyPipeName");
});

Pour plus d’informations sur cette fonctionnalité et sur l’utilisation de .NET et gRPC pour
créer un serveur et un client IPC, consultez Communication entre processus avec gRPC.

Améliorations apportées aux performances du transport


de canaux nommés
Nous avons amélioré les performances de connexion des canaux nommés. Le transport
de canal nommé de Kestrel accepte désormais les connexions en parallèle et réutilise les
instances NamedPipeServerStream.

Durée de création de 100 000 connexions :

Avant : 5,916 secondes


Après : 2,374 secondes

Prise en charge de HTTP/2 sur TLS (HTTPS) sur macOS


dans Kestrel
.NET 8 ajoute la prise en charge d’ALPN (Application-Layer Protocol Negotiation) à
macOS. ALPN est une fonctionnalité TLS utilisée pour négocier le protocole HTTP
qu’une connexion utilise. Par exemple, ALPN permet aux navigateurs et à d’autres clients
HTTP de demander une connexion HTTP/2. Cette fonctionnalité est particulièrement
utile pour les applications gRPC, qui nécessitent HTTP/2. Pour plus d’informations,
consultez Utiliser HTTP/2 avec le serveur web ASP.NET Core Kestrel.

Avertissement lorsque les protocoles HTTP spécifiés ne


sont pas utilisés
Si TLS est désactivé et HTTP/1.x est disponible, HTTP/2 et HTTP/3 sont désactivés, même
s’ils ont été spécifiés. Cela peut entraîner des surprises désagréables, c’est pourquoi
nous avons ajouté une sortie d’avertissement pour vous informer quand cela se produit.

Clés de configuration HTTP_PORTS et HTTPS_PORTS


Les applications et les conteneurs ne reçoivent souvent qu’un port à écouter, comme le
port 80, sans contraintes supplémentaires telles que l’hôte ou le chemin d’accès.
HTTP_PORTS et HTTPS_PORTS sont de nouvelles clés de configuration qui permettent de

spécifier les ports d’écoute pour les serveurs Kestrel et HTTP.sys. Celles-ci peuvent être
définies avec les préfixes de variables d’environnement DOTNET_ ou ASPNETCORE_ , ou
spécifiées directement via une autre entrée de configuration comme appsettings.json. Il
s’agit d’une liste délimitée par des points-virgules de valeurs de port. Exemple :

cli

ASPNETCORE_HTTP_PORTS=80;8080
ASPNETCORE_HTTPS_PORTS=443;8081

Il s’agit d’un raccourci pour les éléments suivants, qui spécifie le schéma (HTTP ou
HTTPS) ainsi que l’hôte ou l’adresse IP :

cli

ASPNETCORE_URLS=http://*:80/;http://*:8080/;https://*:443/;https://*:8081/

Pour plus d’informations, consultez Configurer des points de terminaison pour


l’implémentation du serveur web ASP.NET Core Kestrel et HTTP.sys dans ASP.NET Core.

Nom d’hôte SNI dans ITlsHandshakeFeature


Le nom d’hôte SNI (Indication du nom du serveur) est désormais exposé dans la
propriété HostName de l’interface ITlsHandshakeFeature.

SNI fait partie du processus d’établissement d'une liaison TLS . Il permet aux clients de
spécifier le nom d’hôte auquel ils tentent de se connecter lorsque le serveur héberge
plusieurs hôtes virtuels ou domaines. Pour présenter le certificat de sécurité correct
pendant le processus d’établissement d'une liaison, le serveur doit connaître le nom
d’hôte sélectionné pour chaque requête.

Normalement, le nom d’hôte est géré uniquement dans la pile TLS et est utilisé pour
sélectionner le certificat correspondant. Mais en l’exposant, d’autres composants d’une
application peuvent utiliser ces informations à des fins de diagnostics, de limitation du
débit, de routage et de facturation.

L’exposition du nom d’hôte est utile pour les services à grande échelle qui gèrent des
milliers de liaisons SNI. Cette fonctionnalité peut améliorer considérablement l’efficacité
du débogage pendant les escalades clients. La transparence accrue permet une
résolution plus rapide des problèmes et une fiabilité accrue du service.

Pour plus d’informations, consultez ITlsHandshakeFeature.HostName .

IHttpSysRequestTimingFeature
IHttpSysRequestTimingFeature fournit des informations de minutage détaillées pour
les requêtes lors de l’utilisation du serveur HTTP.sys et de l’hébergement in-process avec
IIS :

Les horodatages sont obtenus à l’aide de QueryPerformanceCounter.


La fréquence d’horodatage peut être obtenue via QueryPerformanceFrequency.
L’index du minutage peut être converti en HttpSysRequestTimingType pour
savoir ce que représente le minutage.
La valeur peut être 0 si le minutage n’est pas disponible pour la requête actuelle.

IHttpSysRequestTimingFeature.TryGetTimestamp récupère l’horodatage pour le type


de minutage fourni :

C#

using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.HttpSys;
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.UseHttpSys();

var app = builder.Build();


app.Use((context, next) =>
{
var feature =
context.Features.GetRequiredFeature<IHttpSysRequestTimingFeature>();

var loggerFactory =
context.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger("Sample");

var timingType = HttpSysRequestTimingType.RequestRoutingEnd;

if (feature.TryGetTimestamp(timingType, out var timestamp))


{
logger.LogInformation("Timestamp {timingType}: {timestamp}",
timingType, timestamp);
}
else
{
logger.LogInformation("Timestamp {timingType}: not available for the
"
+ "current request",
timingType);
}

return next(context);
});

app.MapGet("/", () => Results.Ok());

app.Run();

Pour plus d’informations, consultez Obtenir des informations détaillées sur le minutage
avec IHttpSysRequestTimingFeature et Informations de minutage et hébergement in-
process avec IIS.

HTTP.sys : prise en charge de l’activation de la mise en


mémoire tampon de réponse en mode noyau
Dans certains scénarios, des volumes élevés d’écritures de petite taille avec une latence
élevée peuvent avoir un impact significatif sur les performances de HTTP.sys . Cet impact
est dû à l’absence d’une mémoire tampon Pipe dans l’implémentation HTTP.sys . Pour
améliorer les performances dans ces scénarios, la prise en charge de la mise en mémoire
tampon de réponse a été ajoutée à HTTP.sys . Activez la mise en mémoire tampon en
définissant HttpSysOptions.EnableKernelResponseBuffering sur true .

La mise en mémoire tampon de réponse doit être activée par une application qui
effectue des E/S synchrones ou des E/S asynchrones avec pas plus d’une écriture en
attente à la fois. Dans ces scénarios, la mise en mémoire tampon de réponse peut
améliorer considérablement le débit sur les connexions à latence élevée.

Les applications qui utilisent des E/S asynchrones et qui peuvent avoir plusieurs
écritures en attente à la fois ne doivent pas utiliser cet indicateur. L’activation de cet
indicateur peut entraîner une utilisation plus élevée du processeur et de la mémoire par
HTTP.Sys.

Authentification et autorisation
ASP.NET Core 8 ajoute de nouvelles fonctionnalités à l’authentification et à
l’autorisation.

Points de terminaison de l’API Identity


MapIdentityApi<TUser> est une nouvelle méthode d’extension qui ajoute deux points
de terminaison d’API ( /register et /login ). L’objectif principal de MapIdentityApi est
de permettre aux développeurs d’utiliser facilement ASP.NET Core Identity pour
l’authentification dans des applications monopage (SPA) basées sur JavaScript ou des
applications Blazor. Au lieu d’utiliser l’interface utilisateur par défaut fournie par
ASP.NET Core Identity, basée sur Razor Pages, MapIdentityApi ajoute JSdes points de
terminaison d’API ON plus adaptés aux applications SPA et aux applications non-
navigateur. Pour plus d’informations, consultez Points de terminaison de l’API Identity .

IAuthorizationRequirementData
Avant ASP.NET Core 8, ajouter une stratégie d’autorisation paramétrable à un point de
terminaison requérait l’implémentation de :

AuthorizeAttribute pour chaque stratégie.


AuthorizationPolicyProvider pour traiter une stratégie personnalisée à partir d’un

contrat basé sur des chaînes.


AuthorizationRequirement pour la stratégie.

AuthorizationHandler pour chaque exigence.

Par exemple, considérez l’exemple suivant écrit pour ASP.NET Core 7.0 :

C#

using AuthRequirementsData.Authorization;
using Microsoft.AspNetCore.Authorization;
var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
builder.Services.AddControllers();
builder.Services.AddSingleton<IAuthorizationPolicyProvider,
MinimumAgePolicyProvider>();
builder.Services.AddSingleton<IAuthorizationHandler,
MinimumAgeAuthorizationHandler>();

var app = builder.Build();

app.MapControllers();

app.Run();

C#

using Microsoft.AspNetCore.Mvc;

namespace AuthRequirementsData.Controllers;

[ApiController]
[Route("api/[controller]")]
public class GreetingsController : Controller
{
[MinimumAgeAuthorize(16)]
[HttpGet("hello")]
public string Hello() => $"Hello {(HttpContext.User.Identity?.Name ??
"world")}!";
}

C#

using Microsoft.AspNetCore.Authorization;
using System.Globalization;
using System.Security.Claims;

namespace AuthRequirementsData.Authorization;

class MinimumAgeAuthorizationHandler :
AuthorizationHandler<MinimumAgeRequirement>
{
private readonly ILogger<MinimumAgeAuthorizationHandler> _logger;

public
MinimumAgeAuthorizationHandler(ILogger<MinimumAgeAuthorizationHandler>
logger)
{
_logger = logger;
}
// Check whether a given MinimumAgeRequirement is satisfied or not for a
particular
// context.
protected override Task
HandleRequirementAsync(AuthorizationHandlerContext context,
MinimumAgeRequirement
requirement)
{
// Log as a warning so that it's very clear in sample output which
authorization
// policies(and requirements/handlers) are in use.
_logger.LogWarning("Evaluating authorization requirement for age >=
{age}",

requirement.Age);

// Check the user's age


var dateOfBirthClaim = context.User.FindFirst(c => c.Type ==

ClaimTypes.DateOfBirth);
if (dateOfBirthClaim != null)
{
// If the user has a date of birth claim, check their age
var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value,
CultureInfo.InvariantCulture);
var age = DateTime.Now.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Now.AddYears(-age))
{
// Adjust age if the user hasn't had a birthday yet this
year.
age--;
}

// If the user meets the age criterion, mark the authorization


requirement
// succeeded.
if (age >= requirement.Age)
{
_logger.LogInformation("Minimum age authorization
requirement {age} satisfied",
requirement.Age);
context.Succeed(requirement);
}
else
{
_logger.LogInformation("Current user's DateOfBirth claim
({dateOfBirth})" +
" does not satisfy the minimum age authorization
requirement {age}",
dateOfBirthClaim.Value,
requirement.Age);
}
}
else
{
_logger.LogInformation("No DateOfBirth claim present");
}

return Task.CompletedTask;
}
}

L’exemple de code complet se trouve ici dans le référentiel


AspNetCore.Docs.Samples .

ASP.NET Core 8 introduit l’interface IAuthorizationRequirementData. L’interface


IAuthorizationRequirementData permet à la définition d’attribut de spécifier les

exigences associées à la stratégie d’autorisation. À l’aide de


IAuthorizationRequirementData , le code de stratégie d’autorisation personnalisé
précédent peut être écrit avec moins de lignes de code. Le fichier Program.cs mis à
jour :

diff

using AuthRequirementsData.Authorization;
using Microsoft.AspNetCore.Authorization;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
builder.Services.AddControllers();
- builder.Services.AddSingleton<IAuthorizationPolicyProvider,
MinimumAgePolicyProvider>();
builder.Services.AddSingleton<IAuthorizationHandler,
MinimumAgeAuthorizationHandler>();

var app = builder.Build();

app.MapControllers();

app.Run();

Le MinimumAgeAuthorizationHandler mis à jour :

diff

using Microsoft.AspNetCore.Authorization;
using System.Globalization;
using System.Security.Claims;

namespace AuthRequirementsData.Authorization;

- class MinimumAgeAuthorizationHandler :
AuthorizationHandler<MinimumAgeRequirement>
+ class MinimumAgeAuthorizationHandler :
AuthorizationHandler<MinimumAgeAuthorizeAttribute>
{
private readonly ILogger<MinimumAgeAuthorizationHandler> _logger;

public
MinimumAgeAuthorizationHandler(ILogger<MinimumAgeAuthorizationHandler>
logger)
{
_logger = logger;
}

// Check whether a given MinimumAgeRequirement is satisfied or not for a


particular
// context
protected override Task
HandleRequirementAsync(AuthorizationHandlerContext context,
- MinimumAgeRequirement
requirement)
+ MinimumAgeAuthorizeAttribute
requirement)
{
// Remaining code omitted for brevity.

L’exemple complet mis à jour est disponible ici .

Consultez Stratégies d’autorisation personnalisées avec IAuthorizationRequirementData


pour un examen détaillé du nouvel exemple.

Sécurisation des points de terminaison de l’interface


utilisateur Swagger
Les points de terminaison de l'interface utilisateur Swagger peuvent désormais être
sécurisés dans les environnements de production en appelant
MapSwagger().RequireAuthorization. Si vous souhaitez obtenir plus d’informations, voir
Sécurisation des points de terminaison de l’interface utilisateur Swagger

Divers
Les sections suivantes décrivent diverses nouvelles fonctionnalités dans ASP.NET Core 8.

Prise en charge des services à clé dans l’injection de


dépendances
Les services à clé désigne un mécanisme d’inscription et de récupération des services
d’injection de dépendances (DI) à l’aide de clés. Un service est associé à une clé en
appelant AddKeyedSingleton (ou AddKeyedScoped ou AddKeyedTransient ) pour l’inscrire.
Accédez à un service inscrit en spécifiant la clé avec l’attribut [FromKeyedServices]. Le
code suivant montre comment utiliser les services à clé :

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) =>


bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>

smallCache.Get("date"));

app.MapControllers();

app.Run();

public interface ICache


{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache


{
public object Get(string key) => $"Resolving {key} from small cache.";
}

[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big-cache")]
public ActionResult<object> GetOk([FromKeyedServices("big")] ICache
cache)
{
return cache.Get("data-mvc");
}
}

public class MyHub : Hub


{
public void Method([FromKeyedServices("small")] ICache cache)
{
Console.WriteLine(cache.Get("signalr"));
}
}

Modèles de projet Visual Studio pour les applications SPA


avec backend ASP.NET Core
Les modèles de projet Visual Studio constituent désormais le moyen recommandé pour
créer des applications monopage (SPA) dotées d'un backend ASP.NET Core. Des
modèles sont fournis pour créer des applications basées sur les frameworks JavaScript
Angular , React et Vue . Ces modèles :

Créent une solution Visual Studio avec un projet front-end et un projet back-end.
Utilisent le type de projet Visual Studio pour JavaScript et TypeScript (.esproj) pour
le front-end.
Utilisent un projet ASP.NET Core pour le back-end.

Pour plus d’informations sur les modèles Visual Studio et sur la façon d’accéder aux
modèles hérités, consultez Présentation des applications à page unique (SPA) dans
ASP.NET Core

Prise en charge des attributs génériques


Les attributs qui nécessitaient précédemment un paramètre Type sont désormais
disponibles dans des variantes génériques plus propres. Cela est rendu possible par la
prise en charge des attributs génériques dans C# 11. Par exemple, la syntaxe permettant
d’annoter le type de réponse d’une action peut être modifiée comme suit :

diff

[ApiController]
[Route("api/[controller]")]
public class TodosController : Controller
{
[HttpGet("/")]
- [ProducesResponseType(typeof(Todo), StatusCodes.Status200OK)]
+ [ProducesResponseType<Todo>(StatusCodes.Status200OK)]
public Todo Get() => new Todo(1, "Write a sample", DateTime.Now, false);
}

Les variantes génériques sont prises en charge pour les attributs suivants :

[ProducesResponseType<T>]

[Produces<T>]
[MiddlewareFilter<T>]

[ModelBinder<T>]

[ModelMetadataType<T>]
[ServiceFilter<T>]

[TypeFilter<T>]

Analyse du code dans les applications ASP.NET Core


Les nouveaux analyseurs indiqués dans le tableau suivant sont disponibles dans
ASP.NET Core 8.0.

ID de Cassant ou Description
diagnostic non

ASP0016 Non cassant Ne pas retourner de valeur à partir de RequestDelegate

ASP0019 Non cassant Suggérer l’utilisation d’IHeaderDictionary.Append ou de


l’indexeur

ASP0020 Non cassant Les types complexes référencés par les paramètres d’itinéraire
doivent être analysables

ASP0021 Non cassant Le type de retour de la méthode BindAsync doit être


ValueTask<T>

ASP0022 Non cassant Conflit d’itinéraire détecté entre les gestionnaires d’itinéraires

ASP0023 Non cassant MVC : Conflit d’itinéraire détecté entre les gestionnaires
d’itinéraires

ASP0024 Non cassant Le gestionnaire d’itinéraires a plusieurs paramètres avec


l’attribut [FromBody]

ASP0025 Non cassant Utiliser AddAuthorizationBuilder

Outils de routage
ASP.NET Core repose sur le routage. Les API minimales, les API Web, Razor Pages et
Blazor utilisent tous des itinéraires pour personnaliser le mappage des requêtes HTTP au
code.

Dans .NET 8, nous avons investi dans une suite de nouvelles fonctionnalités pour faciliter
l’apprentissage et l’utilisation du routage. Ces nouvelles fonctionnalités sont notamment
les suivantes :

Mise en évidence de la syntaxe de routage


Saisie semi-automatique des noms de paramètres et de routage
Saisie semi-automatique des contraintes de routage
Analyseurs et correcteurs de routage
Analyseur de syntaxe de routage
Analyseur et correcteur de spécification de paramètre incompatible
Analyseur d’itinéraires d’API minimale et d’API Web ambigus
Prise en charge des API minimales, des API Web et Blazor

Pour plus d’informations, consultez Outils de routage dans .NET 8 .

Métriques ASP.NET Core


Les métriques sont des mesures signalées au fil du temps et sont les plus souvent
utilisées pour surveiller l’intégrité d’une application et générer des alertes. Par exemple,
un compteur qui signale des requêtes HTTP ayant échoué peut s’afficher dans des
tableaux de bord ou générer des alertes lorsque les échecs passent un seuil donné.

Cette préversion ajoute de nouvelles métriques dans ASP.NET Core à l’aide de


System.Diagnostics.Metrics. Metrics est une API moderne pour la création de rapports
et la collecte d’informations sur les applications.

Les métriques offrent plusieurs améliorations par rapport aux compteurs d’événements
existants :

Nouveaux types de mesures avec compteurs, jauges et histogrammes.


Création de rapports puissants avec des valeurs multidimensionnelles.
Intégration à l’écosystème natif cloud plus large en s’alignant sur les normes
OpenTelemetry.

Les métriques ont été ajoutées pour l’hébergement ASP.NET Core, Kestrel et SignalR.
Pour plus d’informations, consultez System.Diagnostics.Metrics.

IExceptionHandler
IExceptionHandler est une nouvelle interface qui donne au développeur un rappel
pour la gestion des exceptions connues dans un emplacement central.

Les implémentations IExceptionHandler sont inscrites en appelant


IServiceCollection.AddExceptionHandler<T> . Plusieurs implémentations peuvent être
ajoutées et elles sont appelées dans l’ordre inscrit. Si un gestionnaire d’exceptions gère
une requête, il peut renvoyer true pour arrêter le traitement. Si une exception n’est
gérée par aucun gestionnaire d’exceptions, le contrôle revient au comportement et aux
options par défaut de l’intergiciel.

Pour plus d’informations, consultez IExceptionHandler.

Expérience de débogage améliorée


Des attributs de personnalisation de débogage ont été ajoutés aux types tels que
HttpContext , HttpRequest , HttpResponse , ClaimsPrincipal et WebApplication . Les

affichages de débogueur améliorés pour ces types facilitent la recherche d’informations


importantes dans le débogueur d’un IDE. Les captures d’écran suivantes montrent la
différence due à ces attributs dans l’affichage du débogueur de HttpContext .

.NET 7 :

.NET 8 :
L’affichage du débogueur pour WebApplication met en évidence des informations
importantes telles que les points de terminaison configurés, les intergiciels et les valeurs
IConfiguration .

.NET 7 :

.NET 8 :

Pour obtenir plus d’informations sur les améliorations apportées au débogage dans
.NET 8, consultez :

Améliorations apportées au débogage dans .NET 8


Problème GitHub dotnet/aspnetcore 48205

IPNetwork.Parse et TryParse

Les nouvelles méthodes Parse et TryParse sur IPNetwork ajoutent la prise en charge de
la création d’un IPNetwork en notation CIDR ou « notation barre oblique ».

Voici des exemples IPv4 :


C#

// Using Parse
var network = IPNetwork.Parse("192.168.0.1/32");

C#

// Using TryParse
bool success = IPNetwork.TryParse("192.168.0.1/32", out var network);

C#

// Constructor equivalent
var network = new IPNetwork(IPAddress.Parse("192.168.0.1"), 32);

Voici des exemples pour IPv6 :

C#

// Using Parse
var network = IPNetwork.Parse("2001:db8:3c4d::1/128");

C#

// Using TryParse
bool success = IPNetwork.TryParse("2001:db8:3c4d::1/128", out var network);

C#

// Constructor equivalent
var network = new IPNetwork(IPAddress.Parse("2001:db8:3c4d::1"), 128);

Mise en cache de sortie basée sur Redis


ASP.NET Core 8 ajoute la prise en charge de l’utilisation de Redis en tant que cache
distribué pour la mise en cache de sortie. La mise en cache de sortie est une
fonctionnalité qui permet à une application de mettre en cache la sortie d’un point de
terminaison d’API minimale, d’une action de contrôleur ou de Razor Page. Pour plus
d’informations, consultez Cache de sortie.

Intergiciel de court-circuit après routage


Lorsque le routage trouve un point de terminaison, il laisse généralement le reste du
pipeline d’intergiciels s’exécuter avant d’appeler la logique de point de terminaison. Les
services peuvent réduire l’utilisation des ressources en filtrant les requêtes connues tôt
dans le pipeline. Utilisez la méthode d’extension ShortCircuit pour laisser le routage
appeler immédiatement la logique de point de terminaison, puis mettre fin à la requête.
Par exemple, un itinéraire donné n’a peut-être pas besoin de passer par
l’authentification ou l’intergiciel CORS. L’exemple suivant montre comment court-
circuiter les requêtes qui correspondent à l’itinéraire /short-circuit :

C#

app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();

Utilisez la méthode MapShortCircuit pour configurer le court-circuit pour plusieurs


itinéraires à la fois, en lui transmettant un tableau de paramètres ou des préfixes d’URL.
Par exemple, les navigateurs et les bots sondent souvent des serveurs pour trouver des
chemins connus comme robots.txt et favicon.ico . Si l’application n’a pas ces fichiers,
une ligne de code peut configurer les deux itinéraires :

C#

app.MapShortCircuit(404, "robots.txt", "favicon.ico");

Pour plus d’informations, consultez Court-circuiter l’intergiciel après le routage.

Extensibilité de l’intergiciel de journalisation HTTP


L’intergiciel de journalisation HTTP offre plusieurs nouvelles fonctionnalités :

HttpLoggingFields.Duration : lorsqu’il est activé, l’intergiciel émet un nouveau


journal à la fin de la requête et de la réponse qui mesure le temps total nécessaire
pour le traitement. Ce nouveau champ a été ajouté à l’ensemble
HttpLoggingFields.All.
HttpLoggingOptions.CombineLogs : lorsqu’il est activé, l’intergiciel consolide tous
ses journaux activés pour une requête et une réponse dans un journal à la fin. Un
message de journal unique inclut la requête, le corps de la requête, la réponse, le
corps de la réponse et la durée.
IHttpLoggingInterceptor : nouvelle interface d’un service qui peut être
implémentée et inscrite (en utilisant AddHttpLoggingInterceptor) pour gérer les
rappels par requête et par réponse pour personnaliser les détails enregistrés. Tous
les paramètres de journal spécifiques au point de terminaison sont appliqués en
premier et peuvent ensuite être remplacés dans ces rappels. Une implémentation
peut :
Inspectez une requête et une réponse.
Activer ou désactiver un HttpLoggingFields quelconque.
Ajuster la quantité du corps de la requête ou de la réponse journalisée.
Ajouter des champs personnalisés aux journaux.

Pour obtenir plus d’informations, consultez Journalisation HTTP dans .NET Core et
ASP.NET Core.

Nouvelles API dans ProblemDetails pour prendre en


charge des intégrations plus résilientes
Dans .NET 7, le service ProblemDetails a été introduit pour améliorer l’expérience de
génération de réponses d’erreur conformes à la spécification ProblemDetails . Dans
.NET 8, une nouvelle API a été ajoutée pour faciliter l’implémentation du comportement
de rappel si IProblemDetailsService n’est pas en mesure de générer ProblemDetails.
L’exemple suivant montre un exemple d’utilisation de la nouvelle API TryWriteAsync :

C#

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async httpContext =>
{
var pds =
httpContext.RequestServices.GetService<IProblemDetailsService>();
if (pds == null
|| !await pds.TryWriteAsync(new() { HttpContext = httpContext
}))
{
// Fallback behavior
await httpContext.Response.WriteAsync("Fallback: An error
occurred.");
}
});
});

app.MapGet("/exception", () =>
{
throw new InvalidOperationException("Sample Exception");
});
app.MapGet("/", () => "Test by calling /exception");

app.Run();

Pour plus d’informations, consultez secours IProblemDetailsService

Ressources supplémentaires
Annonce de ASP.NET Core dans .NET 8 (billet de blog)
Changements cassants et annonces ASP.NET Core (référentiel GitHub
aspnet/Announcements)
Changements cassants et annonces .NET (référentiel GitHub
dotnet/Announcements)

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Nouveautés d’ASP.NET Core 7.0
Article • 26/12/2023

Cet article met en évidence les modifications les plus importantes dans ASP.NET Core
7.0 et fournit des liens vers la documentation appropriée.

Intergiciel de réécriture d’URL dans ASP.NET


Core
L’intergiciel Microsoft.AspNetCore.RateLimiting fournit un intergiciel de limitation de
débit. Les applications configurent des stratégies de limitation du débit, puis attachent
les stratégies aux points de terminaison. Pour plus d’informations, consultez Intergiciel
de limitation du débit dans ASP.NET Core.

L’authentification utilise un schéma unique


comme DefaultScheme
Dans le cadre du travail de simplification de l’authentification, lorsqu’un seul schéma
d’authentification est inscrit, il est automatiquement utilisé comme DefaultScheme et n’a
pas besoin d’être spécifié. Pour plus d’informations, consultez DefaultScheme.

MVC et Razor Pages

Prise en charge des modèles nullables dans les vues MVC


et Razor Pages
Les modèles de page ou de vue nullables sont pris en charge pour améliorer
l’expérience lors de l’utilisation de la vérification de l’état Null avec les applications
ASP.NET Core :

C#

@model Product?

Lier avec IParsable<T>.TryParse dans MVC et les


contrôleurs d’API
L’API IParsable<TSelf>.TryParse prend en charge les valeurs de paramètre d’action du
contrôleur de liaison. Pour plus d’informations, consultez Lier avec
IParsable<T>.TryParse.

Personnaliser la valeur de consentement cookie


Dans les versions antérieures d’ASP.NET Core antérieures à 7, la validation du
consentement cookie utilise la valeur cookie yes pour indiquer le consentement. Vous
pouvez maintenant spécifier la valeur qui représente le consentement. Par exemple,
vous pouvez utiliser true au lieu de yes :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
options.ConsentCookieValue = "true";
});

var app = builder.Build();

Pour plus d’informations, consultez Personnaliser la valeur de consentement cookie.

Contrôleurs d’API

Liaison de paramètres avec la DI dans les contrôleurs


d’API
La liaison de paramètre pour les actions de contrôleur d’API lie des paramètres via
l’injection de dépendance quand le type est configuré en tant que service. Cela signifie
qu’il n’est plus nécessaire d’appliquer explicitement l’attribut [FromServices] à un
paramètre. Dans le code suivant, les deux actions retournent l’heure :

C#

[Route("[controller]")]
[ApiController]
public class MyController : ControllerBase
{
public ActionResult GetWithAttribute([FromServices] IDateTime dateTime)
=> Ok(dateTime.Now);

[Route("noAttribute")]
public ActionResult Get(IDateTime dateTime) => Ok(dateTime.Now);
}

Dans de rares cas, l’injection de dépendances automatique peut interrompre les


applications qui présentent un type dans l’injection de dépendances qui est également
accepté dans les méthodes d’action d’un contrôleur d’API. Il n’est pas courant d’avoir un
type dans l’injection de dépendances et comme argument dans une action du
contrôleur d’API. Pour désactiver la liaison automatique de paramètres, définissez
DisableImplicitFromServicesParameters

C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.DisableImplicitFromServicesParameters = true;
});

var app = builder.Build();

app.MapControllers();

app.Run();

Dans ASP.NET Core 7.0, les types dans l’injection de dépendance sont vérifiés au
démarrage de l’application avec IServiceProviderIsService pour déterminer si un
argument dans une action de contrôleur d’API provient de l’injection de dépendances
ou d’autres sources.

Le nouveau mécanisme permettant de déduire la source de liaison des paramètres


d’action du contrôleur d’API utilise les règles suivantes :

1. Un BindingInfo.BindingSource spécifié précédemment n’est jamais remplacé.


2. BindingSource.Services est affecté à un paramètre de type complexe, inscrit dans le
conteneur d’injection de dépendances.
3. BindingSource.Body est affecté à un paramètre de type complexe, qui n’est pas
inscrit dans le conteneur d’injection de dépendances.
4. BindingSource.Path est affecté à un paramètre avec un nom qui apparaît en tant
que valeur de routage dans n’importe quel modèle de routage.
5. Tous les autres paramètres sont BindingSource.Query.

Noms de propriétés JSON dans les erreurs de validation


Par défaut, lorsqu’une erreur de validation se produit, la validation du modèle génère un
ModelStateDictionary avec le nom de propriété comme clé d’erreur. Certaines
applications, comme les applications monopages, tirent parti de l’utilisation de noms de
propriétés JSON pour les erreurs de validation générées à partir des API web. Le code
suivant configure la validation pour utiliser SystemTextJsonValidationMetadataProvider
afin d’utiliser les noms de propriété JSON :

C#

using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
options.ModelMetadataDetailsProviders.Add(new
SystemTextJsonValidationMetadataProvider());
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Le code suivant configure la validation pour utiliser


NewtonsoftJsonValidationMetadataProvider afin d’utiliser les noms de propriété JSON
lors de l’utilisation de Json.NET :

C#

using Microsoft.AspNetCore.Mvc.NewtonsoftJson;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddControllers(options =>
{
options.ModelMetadataDetailsProviders.Add(new
NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Pour plus d’informations, consultez Utiliser les noms de propriété JSON dans les erreurs
de validation.

API minimales

Filtres dans les applications d’API minimales


Les filtres d’API minimaux permettent aux développeurs d’implémenter une logique
métier qui prend en charge :

Exécution du code avant et après le gestionnaire de routes.


Inspection et modification des paramètres fournis lors d’un appel de gestionnaire
de routes.
Interception du comportement de réponse d’un gestionnaire de routes.

Les filtres peuvent être utiles dans les scénarios suivants :

Validation des paramètres et du corps de la requête envoyés à un point de


terminaison.
Journalisation des informations sur la requête et la réponse.
Validation qu’une requête cible une version d’API prise en charge.

Pour plus d’informations, consultez Filtres dans les applications API minimales

Lier des tableaux et des valeurs de chaîne à partir d’en-


têtes et de chaînes de requête
Dans ASP.NET 7, la liaison de chaînes de requête à un tableau de types primitifs, de
tableaux de chaînes et de StringValues est prise en charge :

C#

// Bind query string values to a primitive type array.


// GET /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
$"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");

// Bind to a string array.


// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
$"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
$"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

La liaison de chaînes de requête ou de valeurs d’en-tête à un tableau de types


complexes est prise en charge lorsque le type implémente TryParse . Pour plus
d’informations, consultez Lier des tableaux et des valeurs de chaîne à partir d’en-têtes et
de chaînes de requête.

Pour plus d’informations, consultez Ajouter un résumé ou une description de point de


terminaison.

Lier le corps de la requête en tant que Stream ou


PipeReader

Le corps de la requête peut être lié en tant que Stream ou PipeReader pour prendre en
charge efficacement les scénarios où l’utilisateur doit traiter des données et :

Stockez les données dans le stockage d’objets blob ou placez les données en file
d’attente dans un fournisseur de file d’attente.
Traitez les données stockées avec un processus Worker ou une fonction cloud.

Par exemple, les données peuvent être mises en file d’attente pour le Stockage File
d’attente Azure ou stockées dans le Stockage Blob Azure.

Pour plus d’informations, consultez Lier le corps de la requête en tant que Stream ou
PipeReader

Nouvelles surcharges Results.Stream


Nous avons introduit de nouvelles surcharges de Results.Stream pour prendre en charge
les scénarios qui nécessitent d’accéder au flux de réponse HTTP sous-jacent sans mettre
en mémoire tampon. Ces surcharges améliorent également les cas où une API diffuse
des données vers le flux de réponse HTTP, comme à partir du Stockage Blob Azure.
L’exemple suivant utilise ImageSharp pour retourner une taille réduite de l’image
spécifiée :

C#

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/process-image/{strImage}", (string strImage, HttpContext http,


CancellationToken token) =>
{
http.Response.Headers.CacheControl = $"public,max-age=
{TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(stream => ResizeImageAsync(strImage, stream,
token), "image/jpeg");
});

async Task ResizeImageAsync(string strImage, Stream stream,


CancellationToken token)
{
var strPath = $"wwwroot/img/{strImage}";
using var image = await Image.LoadAsync(strPath, token);
int width = image.Width / 2;
int height = image.Height / 2;
image.Mutate(x =>x.Resize(width, height));
await image.SaveAsync(stream, JpegFormat.Instance, cancellationToken:
token);
}

Pour plus d’informations, consultez Exemples de diffusion en continu

Résultats typés pour les API minimales


Dans .NET 6, l’interface IResult a été introduite pour représenter les valeurs retournées
par les API minimales qui n’utilisent pas la prise en charge implicite de la sérialisation
JSON de l’objet retourné dans la réponse HTTP. La classe statique Results est utilisée
pour créer différents objets IResult qui représentent différents types de réponses. Par
exemple, la définition du code d’état de la réponse ou la redirection vers une autre URL.
Toutefois, les types de framework implémentant IResult retournés par ces méthodes
étaient internes, ce qui rendait difficile la vérification du type IResult spécifique
retourné par les méthodes dans un test unitaire.

Dans .NET 7, les types implémentant IResult sont publics, ce qui permet les assertions
de type lors des tests. Par exemple :

C#

[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}

Testabilité unitaire améliorée pour les gestionnaires de


routes minimaux
Les types d’implémentation IResult sont désormais disponibles publiquement dans
l’espace de noms Microsoft.AspNetCore.Http.HttpResults. Les types d’implémentation
IResult peuvent être utilisés pour tester unitairement des gestionnaires de routes

minimaux lors de l’utilisation de méthodes nommées au lieu de lambdas.

Le code suivant utilise la classe Ok<TValue> :

C#

[Fact]
public async Task GetTodoReturnsTodoFromDatabase()
{
// Arrange
await using var context = new MockDb().CreateDbContext();

context.Todos.Add(new Todo
{
Id = 1,
Title = "Test title",
Description = "Test description",
IsDone = false
});

await context.SaveChangesAsync();

// Act
var result = await TodoEndpointsV1.GetTodo(1, context);

//Assert
Assert.IsType<Results<Ok<Todo>, NotFound>>(result);

var okResult = (Ok<Todo>)result.Result;

Assert.NotNull(okResult.Value);
Assert.Equal(1, okResult.Value.Id);
}

Pour plus d’informations, consultez Types d’implémentation IResult.

Nouvelles interfaces HttpResult


Les interfaces suivantes dans l’espace de noms Microsoft.AspNetCore.Http permettent
de détecter le type IResult au moment de l’exécution, ce qui est un modèle courant
dans les implémentations de filtre :

IContentTypeHttpResult
IFileHttpResult
INestedHttpResult
IStatusCodeHttpResult
IValueHttpResult
IValueHttpResult<TValue>

Pour plus d’informations, consultez Interfaces IHttpResult.

Améliorations d’OpenAPI pour les API minimales

package NuGet Microsoft.AspNetCore.OpenApi

Le package Microsoft.AspNetCore.OpenApi autorise les interactions avec les


spécifications OpenAPI pour les points de terminaison. Le package agit comme un lien
entre les modèles OpenAPI définis dans le package Microsoft.AspNetCore.OpenApi et les
points de terminaison définis dans les API minimales. Le package fournit une API qui
examine les paramètres, les réponses et les métadonnées d’un point de terminaison
pour construire un type d’annotation OpenAPI utilisé pour décrire un point de
terminaison.

C#

app.MapPost("/todoitems/{id}", async (int id, Todo todo, TodoDb db) =>


{
todo.Id = id;
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


})
.WithOpenApi();

Appeler WithOpenApi avec des paramètres


La méthode WithOpenApi accepte une fonction qui peut être utilisée pour modifier
l’annotation OpenAPI. Par exemple, dans le code suivant, une description est ajoutée au
premier paramètre du point de terminaison :

C#

app.MapPost("/todo2/{id}", async (int id, Todo todo, TodoDb db) =>


{
todo.Id = id;
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


})
.WithOpenApi(generatedOperation =>
{
var parameter = generatedOperation.Parameters[0];
parameter.Description = "The ID associated with the created Todo";
return generatedOperation;
});

Fournir des descriptions et des résumés des points de terminaison


Les API minimales prennent désormais en charge les opérations d’annotation avec des
descriptions et des résumés pour la génération de spécifications OpenAPI. Vous pouvez
appeler des méthodes d’extension WithDescription et WithSummary ou utiliser des
attributs [EndpointDescription] et [EndpointSummary]).

Pour plus d’informations, consultez OpenAPI dans les applications API minimales.

Chargements de fichiers à l’aide d’IFormFile et


IFormFileCollection
Les API minimales prennent désormais en charge le chargement de fichiers avec
IFormFile et IFormFileCollection . Le code suivant utilise IFormFile et
IFormFileCollection pour charger le fichier :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapPost("/upload", async (IFormFile file) =>


{
var tempFile = Path.GetTempFileName();
app.Logger.LogInformation(tempFile);
using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
});

app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>


{
foreach (var file in myFiles)
{
var tempFile = Path.GetTempFileName();
app.Logger.LogInformation(tempFile);
using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
}
});

app.Run();

Les requêtes de chargement de fichiers authentifiés sont prises en charge à l’aide d’un
en-tête d’autorisation , d’un certificat client ou d’un en-tête cookie.

Il n’y a pas de prise en charge intégrée de l’antifalsification. Toutefois, elle peut être
implémentée à l’aide du service IAntiforgery.

L’attribut [AsParameters] active la liaison de paramètres


pour les listes d’arguments
L’attribut [AsParameters] active la liaison de paramètres pour les listes d’arguments.
Pour plus d’informations, consultez Liaison de paramètres pour les listes d’arguments
avec [AsParameters].

API minimales et contrôleurs d’API

Nouveau service de détails du problème


Le service de détails du problème implémente l’interface IProblemDetailsService, qui
prend en charge la création de Détails du problème pour les API HTTP .

Pour plus d’informations, consultez Service de détails du problème.

Groupes d’itinéraires
La méthode d’extension MapGroup permet d’organiser des groupes de points de
terminaison avec un préfixe commun. Cela réduit le code répétitif et permet de
personnaliser des groupes entiers de points de terminaison avec un seul appel à des
méthodes comme RequireAuthorization et WithMetadata, qui ajoutent des
métadonnées de point de terminaison.

Par exemple, le code suivant crée deux groupes de points de terminaison similaires :

C#

app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");

app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();

EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext
factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;

foreach (var argument in factoryContext.MethodInfo.GetParameters())


{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}

// Skip filter if the method doesn't have a TodoDb parameter.


if (dbContextIndex < 0)
{
return next;
}

return async invocationContext =>


{
var dbContext = invocationContext.GetArgument<TodoDb>
(dbContextIndex);
dbContext.IsPrivate = true;

try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise
reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}

C#

public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)


{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);

return group;
}

Dans ce scénario, vous pouvez utiliser une adresse relative pour l’en-tête Location dans
le résultat 201 Created :

C#

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb


database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();

return TypedResults.Created($"{todo.Id}", todo);


}

Le premier groupe de points de terminaison correspond uniquement aux requêtes


précédées de /public/todos , accessibles sans authentification. Le second groupe de
points de terminaison correspond uniquement aux requêtes préfixées par
/private/todos , qui nécessitent une authentification.
La QueryPrivateTodos fabrique de filtres de point de terminaison est une fonction locale
qui modifie les paramètres TodoDb du gestionnaire de routes pour autoriser l’accès aux
données de tâche privées et les stocker.

Les groupes de routage prennent également en charge les groupes imbriqués et les
modèles de préfixe complexes avec des contraintes et des paramètres de routage. Dans
l’exemple suivant, un gestionnaire de routage mappé au groupe user peut capturer les
paramètres de routage {org} et {group} définis dans les préfixes de groupe externe.

Le préfixe peut également être vide. Cela peut être utile pour ajouter des métadonnées
ou des filtres de point de terminaison à un groupe de points de terminaison sans
modifier le modèle de routage.

C#

var all = app.MapGroup("").WithOpenApi();


var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

L’ajout de filtres ou de métadonnées à un groupe se comporte de la même façon que si


vous les ajoutiez individuellement à chaque point de terminaison avant d’ajouter des
filtres ou des métadonnées supplémentaires qui ont pu être ajoutés à un groupe interne
ou à un point de terminaison spécifique.

C#

var outer = app.MapGroup("/outer");


var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("/inner group filter");
return next(context);
});

outer.AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("/outer group filter");
return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("MapGet filter");
return next(context);
});
Dans l’exemple ci-dessus, le filtre externe enregistre la requête entrante avant le filtre
interne, même si elle a été ajoutée en deuxième. Étant donné que les filtres ont été
appliqués à différents groupes, l’ordre dans lequel ils ont été ajoutés les uns par rapport
aux autres n’a pas d’importance. Les filtres d’ordre ajoutés sont importants s’ils sont
appliqués au même groupe ou au même point de terminaison spécifique.

Une requête sur /outer/inner/ journalisera les éléments suivants :

CLI .NET

/outer group filter


/inner group filter
MapGet filter

gRPC

Transcodage JSON
le transcodage JSON gRPC est une extension pour ASP.NET Core qui crée des API
RESTful JSON pour les services gRPC. Le transcodage JSON gRPC permet :

Aux applications d’appeler des services gRPC avec des concepts HTTP familiers.
Aux applications gRPC ASP.NET Core de prendre en charge à la fois les API gRPC
et JSON RESTful sans répliquer de fonctionnalités.
La prise en charge expérimentale de la génération d’OpenAPI à partir d’API RESTful
transcodées grâce à l’intégration à Swashbuckle.

Pour plus d’informations, consultez Transcodage JSON gRPC dans les applications gRPC
ASP.NET Core et Utiliser OpenAPI avec les applications transcodées JSON gRPC ASP.NET
Core.

Contrôles d’intégrité gRPC dans ASP.NET Core


Le protocole de contrôle d’intégrité gRPC est une norme permettant de signaler
l’intégrité des applications serveur gRPC. Une application expose des contrôles
d’intégrité en tant que service gRPC. Ils sont généralement utilisés avec un service de
supervision externe pour vérifier l’état d’une application.

ASP.NET Core gRPC a ajouté la prise en charge intégrée des vérifications d’intégrité
gRPC avec le package Grpc.AspNetCore.HealthChecks . Les résultats des contrôles
d’intégrité .NET sont signalés aux appelants.
Pour plus d’informations, consultez Contrôles d’intégrité gRPC dans ASP.NET Core.

Amélioration de la prise en charge des informations


d’identification d’appel
Les informations d’identification d’appel sont la méthode recommandée pour configurer
un client gRPC afin d’envoyer un jeton d’authentification au serveur. Les clients gRPC
prennent en charge deux nouvelles fonctionnalités pour faciliter l’utilisation des
informations d’identification d’appel :

Prise en charge des informations d’identification d’appel avec les connexions en


texte clair. Auparavant, un appel gRPC envoyait uniquement les informations
d’identification d’appel si la connexion était sécurisée avec TLS. Un nouveau
paramètre sur GrpcChannelOptions , appelé
UnsafeUseInsecureChannelCallCredentials , permet de personnaliser ce

comportement. Il existe des implications en matière de sécurité si vous ne


sécurisez pas une connexion avec TLS.
Une nouvelle méthode appelée AddCallCredentials est disponible avec la fabrique
de clients gRPC. AddCallCredentials est un moyen rapide de configurer les
informations d’identification d’appel pour un client gRPC, et s’intègre bien à
l’injection de dépendances (DI).

Le code suivant configure la fabrique de clients gRPC pour envoyer des métadonnées
Authorization :

C#

builder.Services
.AddGrpcClient<Greeter.GreeterClient>(o =>
{
o.Address = new Uri("https://localhost:5001");
})
.AddCallCredentials((context, metadata) =>
{
if (!string.IsNullOrEmpty(_token))
{
metadata.Add("Authorization", $"Bearer {_token}");
}
return Task.CompletedTask;
});

Pour plus d’informations, consultez Configurer un jeton de porteur avec la fabrique de


clients gRPC.
SignalR

Résultats du client
Le serveur prend désormais en charge la demande d’un résultat à partir d’un client. Cela
nécessite que le serveur utilise ISingleClientProxy.InvokeAsync et que le client retourne
un résultat à partir de son gestionnaire .On . Les hubs fortement typés peuvent
également retourner des valeurs à partir de méthodes d’interface.

Pour plus d’informations, consultez Résultats du client

Injection de dépendances pour les méthodes hub SignalR


Les méthodes de hub SignalR prennent désormais en charge l’injection de services via
l’injection de dépendances (DI).

Les constructeurs de hub peuvent accepter les services de DI en tant que paramètres,
qui peuvent être stockés dans des propriétés sur la classe pour une utilisation dans une
méthode de hub. Pour plus d’informations, consultez Injecter des services dans un hub

Blazor

Gérer les événements de changement d’emplacement et


l’état de navigation
Dans .NET 7, Blazor prend en charge les événements de changement d’emplacement et
le maintien de l’état de navigation. Cela vous permet d’avertir les utilisateurs concernant
le travail non enregistré ou d’effectuer des actions associées lorsque l’utilisateur effectue
une navigation sur une page.

Pour plus d’informations, consultez les sections suivantes de l’article Routage et


navigation :

Options de navigation
Gérer/empêcher les changements d’emplacement

Modèles de projet Blazor vides


Blazor propose deux nouveaux modèles de projet pour commencer à partir de zéro. Les
nouveaux modèles Application Blazor Server vide et Application Blazor WebAssembly
vide sont semblables à leurs équivalents non vides, mais sans exemple de code. Ces
modèles vides incluent uniquement une page d’accueil de base, et nous avons supprimé
Bootstrap pour que vous puissiez commencer avec un autre framework CSS.

Pour plus d’informations, consultez les articles suivants :

Outils pour ASP.NET Core Blazor


Structure de projet ASP.NET Core Blazor

Éléments personnalisés Blazor


Le package Microsoft.AspNetCore.Components.CustomElements permet de créer des
éléments DOM personnalisés basés sur des normes à l’aide de Blazor.

Pour plus d’informations, consultez Composants ASP.NET Core Razor.

Modificateurs de liaison ( @bind:after , @bind:get ,


@bind:set )

) Important

Les fonctionnalités @bind:after / @bind:get / @bind:set reçoivent actuellement


d’autres mises à jour. Pour tirer parti des dernières mises à jour, vérifiez que vous
avez installé la dernière version du SDK .

L’utilisation d’un paramètre de rappel d’événement ( [Parameter] public


EventCallback<string> ValueChanged { get; set; } ) n’est pas prise en charge. Au

lieu de cela, passez une retournant Action ou Task à @bind:set / @bind:after .

Pour plus d’informations, consultez les ressources suivantes :

Blazor@bind:after ne fonctionne pas sur la version RTM de .NET 7


(dotnet/aspnetcore #44957)
Exemple d’application BindGetSetAfter701 (dépôt GitHub
javiercn/BindGetSetAfter701)

Dans .NET 7, vous pouvez exécuter une logique asynchrone une fois qu’un événement
de liaison est terminé à l’aide du nouveau modificateur @bind:after . Dans l’exemple
suivant, la méthode asynchrone PerformSearch s’exécute automatiquement après la
détection des modifications apportées au texte de recherche :
razor

<input @bind="searchText" @bind:after="PerformSearch" />

@code {
private string searchText;

private async Task PerformSearch()


{
...
}
}

Dans .NET 7, il est également plus facile de configurer la liaison pour les paramètres de
composant. Les composants peuvent prendre en charge la liaison de données
bidirectionnelle en définissant une paire de paramètres :

@bind:get : spécifie la valeur à lier.

@bind:set : spécifie un rappel au moment où la valeur change.

Les modificateurs @bind:get et @bind:set sont toujours utilisés ensemble.

Exemples :

razor

@* Elements *@

<input type="text" @bind="text" @bind:after="() => { }" />

<input type="text" @bind:get="text" @bind:set="(value) => { }" />

<input type="text" @bind="text" @bind:after="AfterAsync" />

<input type="text" @bind:get="text" @bind:set="SetAsync" />

<input type="text" @bind="text" @bind:after="() => { }" />

<input type="text" @bind:get="text" @bind:set="(value) => { }" />

<input type="text" @bind="text" @bind:after="AfterAsync" />

<input type="text" @bind:get="text" @bind:set="SetAsync" />

@* Components *@

<InputText @bind-Value="text" @bind-Value:after="() => { }" />

<InputText @bind-Value:get="text" @bind-Value:set="(value) => { }" />

<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />


<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />

<InputText @bind-Value="text" @bind-Value:after="() => { }" />

<InputText @bind-Value:get="text" @bind-Value:set="(value) => { }" />

<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />

<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />

@code {
private string text = "";

private void After(){}


private void Set() {}
private Task AfterAsync() { return Task.CompletedTask; }
private Task SetAsync(string value) { return Task.CompletedTask; }
}

Pour plus d’informations sur le composant InputText , consultez Composants d’entrée


Blazor ASP.NET Core.

Améliorations du rechargement à chaud


Dans .NET 7, la prise en charge du rechargement à chaud comprend les éléments
suivants :

Les composants réinitialisent leurs paramètres à leurs valeurs par défaut lorsqu’une
valeur est supprimée.
Blazor WebAssembly:
Ajout de nouveaux types.
Ajout des classes imbriquées.
Ajout de méthodes statiques et d’instance aux types existants.
Ajout de champs et de méthodes statiques aux types existants.
Ajout de lambdas statiques aux méthodes existantes.
Ajout de lambdas qui capturent this pour les méthodes existantes qui
capturaient déjà this précédemment.

Demandes d’authentification dynamique avec MSAL dans


Blazor WebAssembly
Nouveauté de .NET 7, Blazor WebAssembly prend en charge la création de requêtes
d’authentification dynamique au moment de l’exécution avec des paramètres
personnalisés pour gérer les scénarios d’authentification avancés.
Pour plus d’informations, consultez les articles suivants :

Sécuriser ASP.NET Core Blazor WebAssembly


Autres scénarios de sécurité ASP.NET Core Blazor WebAssembly

Améliorations du débogage de Blazor WebAssembly


Le débogage de Blazor WebAssembly présente les améliorations suivantes :

Prise en charge du paramètre Uniquement mon code pour afficher ou masquer les
membres de type qui ne proviennent pas du code utilisateur.
Prise en charge de l’inspection des tableaux multidimensionnels.
La pile d’appels affiche désormais le nom correct pour les méthodes asynchrones.
Amélioration de l’évaluation des expressions.
Gestion correcte du mot clé new sur les membres dérivés.
Prise en charge des attributs liés au débogueur dans System.Diagnostics .

Prise en charge de System.Security.Cryptography sur


WebAssembly
.NET 6 prenait en charge la famille SHA d’algorithmes de hachage lors de l’exécution sur
WebAssembly. .NET 7 propose davantage d’algorithmes de chiffrement en tirant parti de
SubtleCrypto , lorsque cela est possible, et en revenant à une implémentation .NET
quand SubtleCrypto ne peut pas être utilisé. Les algorithmes suivants sont pris en
charge sur WebAssembly dans .NET 7 :

SHA1
SHA256
SHA384
SHA512
HMACSHA1
HMACSHA256
HMACSHA384
HMACSHA512
AES-CBC
PBKDF2
HKDF

Pour plus d’informations, consultez Les développeurs ciblant browser-wasm peuvent


utiliser les API de chiffrement web (dotnet/runtime #40074) .
Injection de services dans des attributs de validation
personnalisés
Vous pouvez maintenant injecter des services dans des attributs de validation
personnalisés. Blazor configure le ValidationContext afin qu’il puisse être utilisé en tant
que fournisseur de services.

Pour plus d’informations, consultez Validation des formulaires Blazor ASP.NET Core.

Composants Input* en dehors d’un


EditContext / EditForm

Les composants d’entrée intégrés sont désormais pris en charge en dehors d’un
formulaire dans le balisage de composant Razor.

Pour obtenir plus d’informations, consultez Composants d’entrée Blazor ASP.NET Core.

Changements au modèle de projet


Lorsque .NET 6 a été publié l’année dernière, le balisage HTML de la page _Host
( Pages/_Host.chstml ) a été fractionné entre la page _Host et une nouvelle page _Layout
( Pages/_Layout.chstml ) dans le modèle de projet Blazor Server .NET 6.

Dans .NET 7, le balisage HTML a été combiné avec la page _Host dans les modèles de
projet.

Plusieurs modifications supplémentaires ont été apportées aux modèles de projet


Blazor. Il n’est pas possible de répertorier chaque modification apportée aux modèles
dans la documentation. Pour migrer une application vers .NET 7 afin d’adopter toutes
les modifications, consultez Migrer d’ASP.NET Core 6.0 vers 7.0.

Composant QuickGrid expérimental


Le nouveau composant QuickGrid fournit un composant de grille de données pratique
pour les exigences les plus courantes et en tant qu’architecture de référence et base de
référence pour toute personne qui crée des composants de grille de données Blazor.

Pour plus d’informations, consultez Composant QuickGrid ASP.NET Core Blazor.

Démonstration en direct : QuickGrid pour l’exemple d’application Blazor


Améliorations apportées à la virtualisation
Améliorations apportées à la virtualisation dans .NET 7 :

Le composant Virtualize prend en charge l’utilisation du document lui-même


comme racine de défilement, comme alternative à l’utilisation d’un autre élément
avec overflow-y: scroll appliqué.
Si le composant Virtualize est placé à l’intérieur d’un élément qui nécessite un
nom de balise enfant spécifique, SpacerElement vous permet d’obtenir ou de
définir le nom de la balise d’espaceur de virtualisation.

Pour plus d’informations, consultez les sections suivantes de l’article Virtualisation :

Virtualisation au niveau racine


Contrôle du nom de balise de l’élément d’espacement

Mises à jour de MouseEventArgs


MovementX et MovementY ont été ajoutés à MouseEventArgs .

Pour plus d’informations, consultez Gestion des événements ASP.NET Core Blazor.

Nouvelle page de chargement Blazor


Le modèle de projet Blazor WebAssembly a une nouvelle interface utilisateur de
chargement qui indique la progression du chargement de l’application.

Pour plus d’informations, consultez Démarrage ASP.NET Core Blazor.

Amélioration des diagnostics pour l’authentification dans


Blazor WebAssembly
Pour diagnostiquer les problèmes d’authentification dans les applications Blazor
WebAssembly, la journalisation détaillée est disponible.

Pour plus d’informations, consultez Journalisation ASP.NET Core Blazor.

Interopérabilité JavaScript sur WebAssembly


L’API d’interopérabilité [JSImport] / [JSExport] JavaScript est un nouveau mécanisme
de bas niveau permettant d’utiliser .NET dans Blazor WebAssembly et les applications
JavaScript. Avec cette nouvelle fonctionnalité d’interopérabilité JavaScript, vous pouvez
appeler du code .NET à partir de JavaScript à l’aide du runtime WebAssembly .NET et
appeler la fonctionnalité JavaScript à partir de .NET sans dépendance sur le modèle de
composant d’interface utilisateur Blazor.

Pour plus d’informations :

Interopérabilité Javascript JSImportation/JSExportation avec ASP.NET Core Blazor :


concerne uniquement les applications Blazor WebAssembly.
Exécuter .NET à partir de JavaScript : concerne uniquement les applications
JavaScript qui ne dépendent pas du modèle de composant d’interface utilisateur
Blazor.

Inscription conditionnelle du fournisseur d’état


d’authentification
Avant la publication de .NET 7, AuthenticationStateProvider était inscrit dans le
conteneur de service avec AddScoped . Cela rendait difficile le débogage d’applications,
en imposant un ordre spécifique d’inscriptions de service lors de la fourniture d’une
implémentation personnalisée. En raison des changements du framework interne au fil
du temps, il n’est plus nécessaire d’inscrire AuthenticationStateProvider auprès de
AddScoped .

Dans le code du développeur, apportez la modification suivante à l’inscription du service


de fournisseur d’état d’authentification :

diff

- builder.Services.AddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
+ builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();

Dans l’exemple précédent, ExternalAuthStateProvider est l’implémentation du service


du développeur.

Améliorations apportées aux outils de génération de


WebAssembly .NET
Nouvelles fonctionnalités de la charge de travail wasm-tools pour .NET 7 qui permettent
d’améliorer les performances et de gérer les exceptions :
Prise en charge de Single Instruction, Multiple Data (SIMD) WebAssembly
(uniquement avec AOT, non pris en charge par Apple Safari)
Prise en charge de la gestion des exceptions WebAssembly

Pour plus d’informations, consultez Outils pour ASP.NET Core Blazor.

Blazor Hybrid

URL externes
Une option a été ajoutée pour permettre l’ouverture de pages web externes dans le
navigateur.

Pour plus d’informations, consultez Routage et navigation ASP.NET Core Blazor Hybrid.

Sécurité
De nouvelles instructions sont disponibles pour les scénarios de sécurité Blazor Hybrid.
Pour plus d’informations, consultez les articles suivants :

Authentification et autorisation avec ASP.NET Core Blazor Hybrid


Considérations relatives à la sécurité avec ASP.NET Core Blazor Hybrid

Performances

Intergiciel de mise en cache de sortie


La mise en cache de sortie est un nouvel intergiciel qui stocke les réponses d’une
application web et les sert à partir d’un cache au lieu de les calculer à chaque fois. La
mise en cache de sortie diffère de la mise en cache des réponses des manières
suivantes :

Le comportement de mise en cache est configurable sur le serveur.


Les entrées de cache peuvent être invalidées par programmation.
Le verrouillage des ressources atténue le risque de tamponnement du cache et
de troupeau tonitruant .
La revalidation du cache signifie que le serveur peut retourner un code d’état HTTP
304 Not Modified au lieu d’un corps de réponse mis en cache.

Le support de stockage du cache est extensible.


Pour plus d’informations, consultez Vue d’ensemble de la mise en cache et Intergiciel de
mise en cache de sortie.

Améliorations de HTTP/3
Cette version :

Rend HTTP/3 entièrement pris en charge par ASP.NET Core, la prise en charge n’est
plus expérimentale.
Améliore la prise en charge de Kestrel pour HTTP/3. Les deux principaux domaines
d’amélioration sont la parité des fonctionnalités avec HTTP/1.1 et HTTP/2 et les
performances.
Fournit une prise en charge complète de UseHttps(ListenOptions, X509Certificate2)
avec HTTP/3. Kestrel offre des options avancées pour la configuration des
certificats de connexion, comme le raccordement à l’indication du nom du serveur
(SNI) .
Ajoute la prise en charge de HTTP/3 sur HTTP.sys et IIS.

L’exemple suivant montre comment utiliser un rappel SNI pour résoudre les options
TLS :

C#

using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);


builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(8080, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
listenOptions.UseHttps(new TlsHandshakeCallbackOptions
{
OnConnection = context =>
{
var options = new SslServerAuthenticationOptions
{
ServerCertificate =

MyResolveCertForHost(context.ClientHelloInfo.ServerName)
};
return new ValueTask<SslServerAuthenticationOptions>
(options);
},
});
});
});

Un travail important a été effectué dans .NET 7 pour réduire les allocations HTTP/3. Vous
pouvez voir certaines de ces améliorations dans les tirages GitHub suivants :

HTTP/3 : Éviter les allocations de jeton d’annulation par requête


HTTP/3 : Éviter les allocations ConnectionAbortedException
HTTP/3 : Regroupement de ValueTask

Améliorations des performances de HTTP/2


.NET 7 repense la façon dont Kestrel traite les requêtes HTTP/2. Les applications
ASP.NET Core avec des connexions HTTP/2 occupées noteront une utilisation réduite du
processeur et un débit plus élevé.

Auparavant, l’implémentation du multiplexage HTTP/2 s’appuyait sur un verrou


contrôlant la requête pouvant écrire dans la connexion TCP sous-jacente. Une file
d’attente thread-safe remplace le verrou d’écriture. Maintenant, plutôt que de se
battre pour déterminer quel thread peut utiliser le verrou d’écriture, les requêtes sont
maintenant mises en file d’attente, et un consommateur dédié les traite. Les ressources
processeur précédemment gaspillées sont disponibles pour le reste de l’application.

L’un des endroits où ces améliorations peuvent être remarquées est dans gRPC, une
infrastructure RPC populaire qui utilise HTTP/2. Les benchmarks Kestrel + gRPC
montrent une amélioration spectaculaire :
Des modifications ont été apportées au code d’écriture de trame HTTP/2, améliorant les
performances lorsque plusieurs flux tentent d’écrire des données sur une seule
connexion HTTP/2. Nous répartissons maintenant le travail TLS dans le pool de threads
et publions plus rapidement un verrou d’écriture que d’autres flux peuvent acquérir
pour écrire leurs données. La réduction des temps d’attente peut entraîner des
améliorations significatives des performances dans les cas où il existe une contention
pour ce verrou d’écriture. Un benchmark gRPC avec 70 flux sur une seule connexion
(avec TLS) a montré une amélioration d’environ 15 % des requêtes par seconde (RPS)
avec cette modification.

Prise en charge de WebSockets Http/2


.NET 7 introduit la prise en charge de WebSockets sur HTTP/2 pour Kestrel, le client
JavaScript SignalR et SignalR avec Blazor WebAssembly.

L’utilisation de WebSockets sur HTTP/2 tire parti des nouvelles fonctionnalités, dont les
suivantes :

Compression des en-têtes.


Multiplexage, qui réduit le temps et les ressources nécessaires lors de l’envoi de
plusieurs demandes au serveur.

Ces fonctionnalités prises en charge sont disponibles dans Kestrel sur toutes les
plateformes prenant en charge HTTP/2. La négociation de version étant automatique
dans les navigateurs et Kestrel, aucune nouvelle API n’est nécessaire.

Pour plus d’informations, consultez Prise en charge de WebSockets Http/2.

Améliorations des performances de Kestrel sur les


machines à nombreux cœurs
Kestrel utilise ConcurrentQueue<T> à de nombreuses fins. L’un des objectifs est de
planifier les opérations d’E/S dans le transport de socket par défaut de Kestrel. Le
partitionnement de la ConcurrentQueue en fonction du socket associé réduit la
contention et augmente le débit sur les machines avec de nombreux cœurs de
processeur.

Le profilage sur des machines à nombreux cœurs sur .NET 6 a montré une contention
significative dans l’une des autres instances ConcurrentQueue de Kestrel, le
PinnedMemoryPool que Kestrel utilise pour mettre en cache des mémoires tampons

d’octets.

Dans .NET 7, le pool de mémoire de Kestrel est partitionné de la même façon que sa file
d’attente d’E/S, ce qui entraîne une contention beaucoup plus faible et un débit plus
élevé sur les machines à nombreux cœurs. Sur les machines virtuelles ARM64 à 80
cœurs, nous constatons une amélioration de plus de 500 % des réponses par seconde
(RPS) dans le benchmark en texte clair TechEmpower. Sur les machines virtuelles AMD à
48 cœurs, l’amélioration est de près de 100 % dans notre benchmark JSON HTTPS.

Événement ServerReady pour mesurer le temps de


démarrage
Les applications utilisant EventSource peuvent mesurer le temps de démarrage pour
comprendre et optimiser les performances de démarrage. Le nouvel événement
ServerReady dans Microsoft.AspNetCore.Hosting représente le point où le serveur est
prêt à répondre aux requêtes.

Serveur

Nouvel événement ServerReady pour mesurer le temps


de démarrage
L’événement ServerReady a été ajouté pour mesurer le temps de démarrage des
applications ASP.NET Core.
IIS

Cliché instantané dans IIS


La prise de clichés instantanés des assemblys d’application dans le module ASP.NET
Core (ANCM) pour IIS peut offrir une meilleure expérience utilisateur que l’arrêt de
l’application en déployant un fichier App Offline.

Pour plus d’informations, consultez Clichés instantanés dans IIS.

Divers

Améliorations de la chaîne de certificats complète de


Kestrel
HttpsConnectionAdapterOptions offre une nouvelle propriété ServerCertificateChain de
type X509Certificate2Collection, qui facilite la validation des chaînes de certificats en
autorisant la spécification d’une chaîne complète incluant des certificats intermédiaires.
Pour plus d’informations, consultez dotnet/aspnetcore#21513 .

dotnet watch run

Sortie de console améliorée pour dotnet watch

La sortie de console de dotnet watch a été améliorée pour mieux s’aligner sur la
journalisation d’ASP.NET Core et pour se distinguer avec des 😮emojis😍.

Voici un exemple de ce à quoi ressemble la nouvelle sortie :

Pour plus d’informations, consultez cette requête de tirage GitHub .

Configurer dotnet watch pour qu’il redémarre toujours


pour les modifications non applicables
Les modifications non applicables sont des modifications qui ne peuvent pas être
rechargées à chaud. Pour configurer dotnet watch pour qu’il redémarre toujours sans
invite de modifications non applicables, définissez la variable d’environnement
DOTNET_WATCH_RESTART_ON_RUDE_EDIT sur true .

Mode sombre pour la page d’exceptions du développeur


La prise en charge du mode sombre a été ajoutée à la page d’exceptions du
développeur, grâce à une contribution de Patrick Westerhoff . Pour tester le mode
sombre dans un navigateur, à partir de la page Outils de développement, définissez le
mode sur Sombre. Par exemple, dans Firefox :

Dans Chrome :
Option de modèle de projet permettant d’utiliser la
méthode Program.Main au lieu d’instructions de niveau
supérieur
Les modèles .NET 7 incluent une option permettant de ne pas utiliser d’instructions de
niveau supérieur et de générer un namespace et une méthode Main déclarée sur une
classe Program .

À l’aide de l’interface CLI .NET, utilisez l’option --use-program-main :

CLI .NET

dotnet new web --use-program-main

Avec Visual Studio, cochez la nouvelle case Ne pas utiliser d’instructions de niveau
supérieur lors de la création du projet :
Modèles Angular et React mis à jour
Le modèle de projet Angular a été mis à jour vers Angular 14. Le modèle de projet React
a été mis à jour vers React 18.2.

Gérer les jetons web JSON dans le développement avec


dotnet user-jwts
Le nouvel outil en ligne de commande dotnet user-jwts peut créer et gérer des jetons
web JSON (JWT) locaux spécifiques à l’application. Pour plus d’informations, consultez
Gérer les jetons web JSON en développement avec dotnet user-jwts.

Prise en charge des en-têtes de requête supplémentaires


dans W3CLogger
Vous pouvez maintenant spécifier des en-têtes de requête supplémentaires à consigner
lors de l’utilisation de l’enregistreur d’événements W3C en appelant
AdditionalRequestHeaders() sur W3CLoggerOptions :

C#

services.AddW3CLogging(logging =>
{
logging.AdditionalRequestHeaders.Add("x-forwarded-for");
logging.AdditionalRequestHeaders.Add("x-client-ssl-protocol");
});

Pour plus d’informations, consultez Options W3CLogger.

Décompression des requêtes


Nouvel Intergiciel de décompression de requête :

Permet aux points de terminaison d’API d’accepter les requêtes avec du contenu
compressé.
Utilise l’en-tête HTTP Content-Encoding pour identifier et décompresser
automatiquement les requêtes qui contiennent du contenu compressé.
Élimine la nécessité d’écrire du code pour gérer les requêtes compressées.

Pour plus d’informations, consultez Intergiciel de décompression de requête.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Nouveautés dans ASP.NET Core 6.0
Article • 21/12/2023

Cet article met en évidence les modifications les plus importantes dans ASP.NET
Core 6.0 et fournit des liens vers la documentation appropriée.

Améliorations MVC et Razor dans ASP.NET Core

API minimales
Les API minimales sont conçues pour créer des API HTTP ayant des dépendances
minimales. Elles sont idéales pour les microservices et les applications qui ne nécessitent
qu’un minimum de fichiers, de fonctionnalités et de dépendances dans ASP.NET Core.
Pour plus d'informations, consultez les pages suivantes :

Didacticiel : Créer une API minimale avec ASP.NET Core


Différences entre les API minimales et les API avec contrôleurs
Informations de référence rapides sur les API minimales
Exemples de code migré vers le nouveau modèle d’hébergement minimal dans la
version 6.0

SignalR

Balise d’activité durable pour les connexions SignalR


SignalR utilise le nouveau
Microsoft.AspNetCore.Http.Features.IHttpActivityFeature.Activity pour ajouter une balise
http.long_running à l’activité de requête. IHttpActivityFeature.Activity est utilisé par
les services APM comme Azure Monitor Application Insights pour filtrer les requêtes
SignalR de la création d’alertes de requête durables.

SignalRAmélioration des performances


Allouez des HubCallerClients une fois par connexion au lieu de le faire pour
chaque appel de méthode hub.
Évitez l’allocation de fermeture dans SignalR DefaultHubDispatcher.Invoke . L’état
est passé à une fonction statique locale par le biais de paramètres pour éviter une
allocation de fermeture. Pour plus d’informations, consultez cette demande de
tirage (pull request) GitHub .
Allouez un seul StreamItemMessage par flux plutôt que par élément de flux dans la
diffusion en continu du serveur à client. Pour plus d’informations, consultez cette
demande de tirage (pull request) GitHub .

Compilateur Razor

Compilateur Razor mis à jour pour utiliser des


générateurs sources
Le compilateur Razor est désormais basé sur des générateurs sources C#. Les
générateurs sources s’exécutent durant la compilation et inspectent les éléments
compilés afin de produire des fichiers supplémentaires qui sont compilés avec le reste
du projet. L’utilisation de générateurs sources simplifie le compilateur Razor et accélère
considérablement la durée de génération.

Le compilateur Razor ne produit plus un assembly


d’affichages distinct
Le compilateur Razor a précédemment utilisé un processus de compilation en deux
étapes qui a produit un assembly d’affichages distinct contenant les vues et les pages
générées (fichiers .cshtml ) définies dans l’application. Les types générés étaient publics
et sous l’espace de noms AspNetCore .

Le compilateur Razor mis à jour génère les types de vues et de pages dans l’assembly
du projet principal. Ces types sont désormais générés par défaut en tant que types
internes sealed dans l’espace de noms AspNetCoreGeneratedDocument . Cette modification
améliore les performances de génération, permet le déploiement de fichiers uniques et
permet à ces types de participer au Rechargement à chaud.

Pour plus d’informations sur cette modification, consultez le problème des annonces
associé sur GitHub.

Améliorations du niveau de performance et des


API sur ASP.NET Core
De nombreuses modifications ont été réalisées pour réduire les allocations et améliorer
le niveau de performance sur l’ensemble de la pile :
Méthode d’extension app.Use sans allocation. La nouvelle surcharge de app.Use
nécessite le passage du contexte dans next qui enregistre deux allocations
internes par requête requises lors de l’utilisation de l’autre surcharge.
Allocations de mémoire réduites au cours de l’accès à HttpRequest.Cookies. Pour
plus d’informations, consultez ce problème GitHub .
Utilisez LoggerMessage.Define pour le serveur web HTTP.sys uniquement sous
windows. Les appels de méthodes d’extension ILogger ont été remplacés par des
appels à LoggerMessage.Define .
Réduisez la surcharge par connexion dans SocketConnection d’environ 30 %.
Pour plus d’informations, consultez cette demande de tirage (pull request)
GitHub .
Réduisez les allocations en supprimant les délégués de journalisation des types
génériques. Pour plus d’informations, consultez cette demande de tirage (pull
request) GitHub .
Accès GET plus rapide (environ 50 %) aux fonctionnalités couramment utilisées
telles que IHttpRequestFeature, IHttpResponseFeature, IHttpResponseBodyFeature,
IRouteValuesFeature et IEndpointFeature. Pour plus d’informations, consultez cette
demande de tirage (pull request) GitHub .
Utilisez des chaînes d’instance unique pour les noms d’en-tête connus, même s’ils
ne figurent pas dans le bloc d’en-tête préservé. L’utilisation d’une chaîne
d’instance permet d’éviter d’avoir plusieurs doublons de la même chaîne dans les
connexions à longue durée de vie, par exemple dans
Microsoft.AspNetCore.WebSockets. Pour plus d’informations, consultez ce
problème GitHub .
Réutilisez HttpProtocol CancellationTokenSource dans Kestrel. Utilisez la
nouvelle méthode CancelTokenSource.TryReset sur CancellationTokenSource pour
réutiliser les jetons s’ils n’ont pas été annulés. Pour plus d’informations, consultez
ce problème GitHub et cette vidéo .
Implémentez et utilisez un AdaptiveCapacityDictionary dans
Microsoft.AspNetCore.Httpla collection de Cookierequêtes pour disposer d’un
accès plus efficace aux dictionnaires. Pour plus d’informations, consultez cette
demande de tirage (pull request) GitHub .

Empreinte mémoire réduite pour les connexions TLS


inactives
Pour les connexions TLS durables où les données ne sont envoyées que de temps en
temps dans les deux sens, nous avons considérablement réduit l’empreinte mémoire des
applications ASP.NET Core dans .NET 6. Cela devrait permettre d’améliorer la scalabilité
des scénarios tels que les serveurs WebSocket. Cela a été possible en raison des
nombreuses améliorations apportées à System.IO.Pipelines, SslStream et Kestrel. Les
sections suivantes présentent en détail quelques-unes des améliorations qui ont
contribué à réduire l’empreinte mémoire :

Réduire la taille de System.IO.Pipelines.Pipe


Pour chaque connexion établie, deux canaux sont alloués dans Kestrel :

La couche de transport vers l’application de la requête.


La couche d’application vers le transport de la réponse.

En réduisant la taille de System.IO.Pipelines.Pipe de 368 octets à 264 octets (une


réduction d’environ 28,2 %), il est possible d’économiser 208 octets par connexion
(104 octets par canal).

Pool SocketSender
Les objets SocketSender (de la sous-classe SocketAsyncEventArgs) sont d’environ
350 octets au moment de l’exécution. Au lieu d’allouer un nouvel objet SocketSender
par connexion, il est possible de les regrouper. Les objets SocketSender peuvent être
regroupés, car les envois sont généralement très rapides. Le regroupement réduit la
surcharge par connexion. Au lieu d’allouer 350 octets par connexion, ne payez que les
350 octets qui sont alloués par IOQueue . L’allocation est effectuée par file d’attente pour
éviter tout conflit. Notre serveur WebSocket avec 5 000 connexions inactives est passé
d’une allocation d’environ 1,75 Mo (350 octets * 5 000) à une allocation d’environ 2,8 Ko
(350 octets * 8) pour les objets SocketSender .

Lectures à zéro octet avec SslStream


Les lectures sans mémoire tampon sont une technique utilisée dans ASP.NET Core pour
éviter de louer la mémoire du pool de mémoires lorsqu’il n’y a aucune donnée
disponible sur le socket. Avant cette modification, notre serveur WebSocket à
5 000 connexions inactives avait besoin d’environ 200 Mo sans TLS, contre environ
800 Mo avec TLS. Certaines de ces allocations (4 000 par connexion) étaient dues à
Kestrel qui devait conserver une mémoire tampon ArrayPool<T> en attendant que les
lectures sur SslStream se terminent. Étant donné que ces connexions étaient inactives,
aucune des lectures ne s’est terminée et n’a renvoyé ses mémoires tampons au
ArrayPool , ce qui a forcé ArrayPool à allouer plus de mémoire. Les allocations restantes

étaient dans le SslStream : une mémoire tampon de 4 000 pour l’établissement des
liaisons TLS et une mémoire tampon de 32 000 pour les lectures normales. Dans .NET 6,
lorsque l’utilisateur effectue une lecture à zéro octet sur SslStream et qu’il n’a aucune
donnée disponible, SslStream effectue en interne une lecture à zéro octet sur le flux
sous-jacent inclus dans un wrapper. Dans le meilleur cas (connexion inactive), ces
modifications permettent d’économiser 40 Ko par connexion tout en permettant au
consommateur (Kestrel) d’être averti lorsque des données sont disponibles sans
conserver de mémoires tampons inutilisées.

Lectures à zéro octet avec PipeReader


Les lectures sans mémoire tampon étant prises en charge sur SslStream , une option
pour réaliser des lectures à zéro octet a été ajoutée à StreamPipeReader , le type interne
qui adapte un Stream dans un PipeReader . Dans Kestrel, un StreamPipeReader est utilisé
pour adapter le SslStream sous-jacent à un PipeReader . Par conséquent, il était
nécessaire d’exposer ces sémantiques de lecture à zéro octet sur le PipeReader .

Dorénavant, un PipeReader prenant en charge les lectures à zéro octet sur n’importe
quel Stream sous-jacent qui prend en charge la sémantique de lecture à zéro octet (par
exemple SslStream ,NetworkStream, etc.) peut être créé à l’aide de l’API suivante :

CLI .NET

var reader = PipeReader.Create(stream, new


StreamPipeReaderOptions(useZeroByteReads: true));

Supprimer des sections du SlabMemoryPool


Pour réduire la fragmentation du tas, Kestrel a utilisé une technique selon laquelle il a
alloué des sections de mémoire de 128 Ko dans le cadre de son pool de mémoires. Les
sections étaient ensuite divisées en blocs de 4 Ko qui étaient utilisés par Kestrel en
interne. Les sections devaient être supérieures à 85 Ko pour forcer l’allocation sur le tas
d’objets volumineux afin d’essayer d’empêcher le GC de déplacer ce groupe. Toutefois,
avec l’introduction du GC de nouvelle génération, le tas d’objets épinglés (POH), il
n’est plus logique d’allouer des blocs sur la section. Kestrel alloue désormais des blocs
directement sur le POH, ce qui réduit la complexité de la gestion du pool de mémoires.
Cette modification devrait faciliter l’exécution d’améliorations ultérieures, telles que la
réduction du pool de mémoires utilisé par Kestrel.

IAsyncDisposable pris en charge


IAsyncDisposable est désormais disponible pour les contrôleurs, Razor Pages et les
composants d’affichage. Des versions asynchrones ont été ajoutées aux interfaces
pertinentes dans les fabriques et les activateurs :

Les nouvelles méthodes offrent une implémentation d’interface par défaut qui
délègue à la version synchrone et appelle Dispose.
Les implémentations remplacent l’implémentation par défaut et gèrent les
implémentations IAsyncDisposable de suppression.
Les implémentations favorisent IAsyncDisposable sur IDisposable lorsque les deux
interfaces sont implémentées.
Les extendeurs doivent remplacer les nouvelles méthodes incluses pour prendre en
charge les instances IAsyncDisposable .

IAsyncDisposable est utile lors de l’utilisation de :

Énumérateurs asynchrones, par exemple, dans des flux asynchrones.


Ressources non gérées ayant des opérations d’E/S nécessitant beaucoup de
ressources à mettre en production.

Lors de l’implémentation de cette interface, utilisez la méthode DisposeAsync pour


mettre des ressources en production.

Envisagez d’utiliser un contrôleur qui crée et utilise un Utf8JsonWriter. Utf8JsonWriter


est une ressource IAsyncDisposable :

C#

public class HomeController : Controller, IAsyncDisposable


{
private Utf8JsonWriter? _jsonWriter;
private readonly ILogger<HomeController> _logger;

public HomeController(ILogger<HomeController> logger)


{
_logger = logger;
_jsonWriter = new Utf8JsonWriter(new MemoryStream());
}

IAsyncDisposable doit implémenter DisposeAsync :

C#

public async ValueTask DisposeAsync()


{
if (_jsonWriter is not null)
{
await _jsonWriter.DisposeAsync();
}

_jsonWriter = null;
}

Port Vcpkg pour le client C++ sur SignalR


Vcpkg est un gestionnaire de package de ligne de commande multiplateforme pour
les bibliothèques C et C++. Nous avons récemment ajouté un port vers vcpkg pour
ajouter la prise en charge native CMake du client C++ sur SignalR. vcpkg fonctionne
également avec MSBuild.

Le client SignalR peut être ajouté à un projet CMake avec l’extrait de code suivant
lorsque le vcpkg est inclus dans le fichier de chaîne d’outils :

CLI .NET

find_package(microsoft-signalr CONFIG REQUIRED)


link_libraries(microsoft-signalr::microsoft-signalr)

Avec l’extrait de code précédent, le client C++ sur SignalR est prêt à utiliser #include
et à être utilisé dans un projet sans configuration supplémentaire. Pour obtenir un
exemple complet d’une application C++ qui utilise le client C++ sur SignalR, consultez
le référentiel halter73/SignalR-Client-Cpp-Sample .

Blazor

Changements au modèle de projet


Plusieurs changements ont été apportés au modèle de projet pour les applications
Blazor, notamment l’utilisation du fichier Pages/_Layout.cshtml pour le contenu de
layout qui est apparu dans le fichier _Host.cshtml des applications Blazor Server
antérieures. Étudiez les changements en créant une application à partir d’un modèle de
projet 6.0 ou en accédant à la source de référence ASP.NET Core pour les modèles de
projet :

Blazor Server
Blazor WebAssembly
Prise en charge des dépendances Blazor WebAssembly
natives
Les applications Blazor WebAssembly peuvent utiliser des dépendances natives créées
pour s’exécuter sur WebAssembly. Pour plus d’informations, consultez Dépendances
natives ASP.NET Core Blazor WebAssembly.

Compilation Ahead Of Time (AOT) WebAssembly et


nouvelle liaison du runtime
Blazor WebAssembly prend en charge la compilation Ahead Of Time (AOT) qui vous
permet de compiler votre code .NET directement dans WebAssembly. La compilation
AOT permet d’améliorer les performances du runtime au détriment d’une plus grande
taille d’application. La nouvelle liaison du runtime WebAssembly .NET réduit le code
d’exécution inutilisé et améliore ainsi la vitesse de téléchargement. Pour plus
d’informations, consultez Compilation Ahead Of Time (AOT) et Nouvelle liaison du
runtime.

Conserver l’état prérendu


Blazor prend en charge l’état persistant dans une page prérendue pour qu’il ne soit pas
nécessaire de recréer l’état lorsque l’application est entièrement chargée. Pour plus
d’informations, consultez Prérendu et intégration des composants ASP.NET Core Razor.

Limites d’erreur
Les limites d’erreur fournissent une approche pratique pour la gestion des exceptions au
niveau de l’interface utilisateur. Pour plus d’informations, consultez Gérer les erreurs
dans les applications ASP.NET Core Blazor.

Prise en charge de SVG


L’élément <foreignObject> element est pris en charge pour afficher du code HTML
arbitraire dans un SVG. Pour plus d’informations, consultez Composants ASP.NET Core
Razor.

Prise en charge Blazor Server du transfert de tableau


d’octets dans l’interopérabilité JS
Blazor prend en charge l’interopérabilité JS des tableaux d’octets optimisés, qui évite
l’encodage et le décodage des tableaux d’octets en Base64. Pour plus d’informations,
consultez les ressources suivantes :

Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core
Blazor
Appeler des méthodes .NET à partir de fonctions JavaScript dans ASP.NET Core
Blazor

Améliorations de la chaîne de requête


La prise en charge de l’utilisation des chaînes de requête a été améliorée. Pour plus
d’informations, consultez Routage et navigation ASP.NET Core Blazor.

Liaison pour la sélection multiple


La liaison prend en charge la sélection de plusieurs options avec des éléments <input> .
Pour plus d’informations, consultez les ressources suivantes :

Liaison de données ASP.NET Core Blazor


Composants d’entrée ASP.NET Core Blazor

Contrôle de contenu d’en-tête ( <head> )


Les composants Razor peuvent modifier le contenu de l’élément HTML <head> d’une
page, notamment la définition du titre de la page (élément <title> ) et la modification
des métadonnées (éléments <meta> ). Pour plus d’informations, consultez Contrôle du
contenu <head> dans les applications ASP.NET Core Blazor.

Générer des composants Angular et React


Générez des composants JavaScript spécifiques à l’infrastructure à partir de composants
Razor pour les infrastructures web, comme Angular ou React. Pour plus d’informations,
consultez Composants ASP.NET Core Razor.

Générer le rendu de composants à partir de JavaScript


Générer le rendu de composants Razor dynamiquement à partir de JavaScript pour les
applications JavaScript existantes. Pour plus d’informations, consultez Composants
ASP.NET Core Razor.
Éléments personnalisés
La prise en charge expérimentale est disponible pour la création d’éléments
personnalisés qui utilisent des interfaces HTML standard. Pour plus d’informations,
consultez Composants ASP.NET Core Razor.

Déduire les types génériques des composants à partir de


composants ancêtres
Un composant ancêtre peut mettre en cascade un paramètre de type par nom à des
descendants à l’aide du nouvel attribut [CascadingTypeParameter] . Pour plus
d’informations, consultez Composants ASP.NET Core Razor.

Composants rendus dynamiquement


Utilisez le nouveau composant DynamicComponent intégré pour générer le rendu des
composants par type. Pour plus d’informations, consultez Composants ASP.NET Core
Razor rendus dynamiquement.

Accessibilité Blazor améliorée


Utilisez le nouveau composant FocusOnNavigate pour définir le focus de l’interface
utilisateur sur un élément basé sur un sélecteur CSS après avoir navigué d’une page à
l’autre. Pour plus d’informations, consultez Routage et navigation ASP.NET Core Blazor.

Prise en charge d’arguments d’événements personnalisés


Blazor prend en charge les arguments d’événements personnalisés, ce qui vous permet
de transmettre des données arbitraires aux gestionnaires d’événements .NET avec des
événements personnalisés. Pour plus d’informations, consultez Gestion des événements
ASP.NET CoreBlazor.

Paramètres obligatoires
Appliquez le nouvel attribut [EditorRequired] pour spécifier un paramètre de
composant obligatoire. Pour plus d’informations, consultez Composants ASP.NET Core
Razor.
Collocation de fichiers JavaScript avec des pages, des
vues et des composants
La collocation de fichiers JavaScript pour les pages, les vues et les composants Razor est
un moyen pratique d’organiser les scripts dans une application. Pour plus
d’informations, consultez Interopérabilité JavaScript et ASP.NET Core Blazor
(interopérabilité JS).

Initialiseurs JavaScript
Les initialiseurs JavaScript exécutent la logique avant et après le chargement d’une
application Blazor. Pour plus d’informations, consultez Interopérabilité JavaScript et
ASP.NET Core Blazor (interopérabilité JS).

Interopérabilité de JavaScript lors de la diffusion en


continu
Blazor prend désormais en charge la diffusion en continu de données directement entre
.NET et JavaScript. Pour plus d’informations, consultez les ressources suivantes :

Diffuser en continu de .NET vers JavaScript


Diffuser en continu de JavaScript vers .NET

Contraintes de type générique


Les paramètres de type générique sont désormais pris en charge. Pour plus
d’informations, consultez Composants ASP.NET Core Razor.

Disposition du déploiement WebAssembly


Utilisez une disposition de déploiement pour activer les téléchargements d’applications
Blazor WebAssembly dans des environnements à sécurité restreinte. Pour plus
d’informations, consultez Disposition de déploiement pour les applications Blazor
WebAssembly hébergées sur ASP.NET Core.

Nouveaux articles Blazor


Outre les fonctionnalités de Blazor décrites dans les sections précédentes, de nouveaux
articles Blazor sont disponibles sur les sujets suivants :
Téléchargements de fichiers ASP.NET Core Blazor : découvrez comment télécharger
un fichier à l’aide de l’interopérabilité de diffusion en continu native byte[] pour
garantir un transfert efficace vers le client.
Utilisation d’images dans ASP.NET Core Blazor : découvrez comment utiliser des
images dans les applications Blazor, notamment comment diffuser des données
d’image et afficher un aperçu d’une image.

Création d’applications Blazor Hybrid avec .NET


MAUI, WPF et Windows Forms
Utilisez Blazor Hybrid pour combiner des infrastructures clientes natives de bureau et
mobiles avec .NET et Blazor :

.NET Multi-platform App UI (.NET MAUI) : est une infrastructure multiplateforme


pour la création d’applications mobiles et de bureau natives en C# et XAML.
Les applications Blazor Hybrid peuvent être générées avec les infrastructures
Windows Presentation Foundation (WPF) et Windows Forms.

) Important

Blazor Hybrid est en préversion et ne doit pas être utilisé dans les applications de
production avant la version finale.

Pour plus d’informations, consultez les ressources suivantes :

Prévisualisation de la documentation ASP.NET CoreBlazor Hybrid


Qu’est-ce que .NET MAUI?
Blog Microsoft .NET (catégorie : « .NET MAUI »)

Kestrel
HTTP/3 est actuellement en état de brouillon et peut donc faire l’objet de
modifications. La prise en charge du protocole HTTP/3 dans ASP.NET Core n’est pas
publiée, il s’agit d’une fonctionnalité d’évaluation incluse dans .NET 6.

Kestrel prend désormais en charge le protocole HTTP/3. Pour plus d’informations,


consultez Utiliser le protocole HTTP/3 avec le serveur web ASP.NET Core Kestrel et
l’entrée de blog Prise en charge du protocole HTTP/3 dans .NET 6 .
Nouvelles catégories de journalisation de Kestrel pour la
journalisation sélectionnée
Avant cette modification, l’activation de la journalisation détaillée pour Kestrel était
extrêmement coûteuse, car tous les éléments de Kestrel partageaient le nom de la
catégorie de journalisation Microsoft.AspNetCore.Server.Kestrel . La catégorie
Microsoft.AspNetCore.Server.Kestrel est toujours disponible, mais les nouvelles sous-

catégories suivantes permettent de mieux contrôler la journalisation :

Microsoft.AspNetCore.Server.Kestrel (catégorie actuelle) : ApplicationError ,

ConnectionHeadResponseBodyWrite , ApplicationNeverCompleted , RequestBodyStart ,

RequestBodyDone , RequestBodyNotEntirelyRead , RequestBodyDrainTimedOut ,


ResponseMinimumDataRateNotSatisfied , InvalidResponseHeaderRemoved et

HeartbeatSlow .
Microsoft.AspNetCore.Server.Kestrel.BadRequests : ConnectionBadRequest ,

RequestProcessingError , RequestBodyMinimumDataRateNotSatisfied .

Microsoft.AspNetCore.Server.Kestrel.Connections : ConnectionAccepted ,
ConnectionStart , ConnectionStop , ConnectionPause , ConnectionResume ,

ConnectionKeepAlive , ConnectionRejected , ConnectionDisconnect ,


NotAllConnectionsClosedGracefully , NotAllConnectionsAborted ,

ApplicationAbortedConnection .

Microsoft.AspNetCore.Server.Kestrel.Http2 : Http2ConnectionError ,
Http2ConnectionClosing , Http2ConnectionClosed , Http2StreamError ,

Http2StreamResetAbort , HPackDecodingError , HPackEncodingError ,


Http2FrameReceived , Http2FrameSending , Http2MaxConcurrentStreamsReached .

Microsoft.AspNetCore.Server.Kestrel.Http3 : Http3ConnectionError ,
Http3ConnectionClosing , Http3ConnectionClosed , Http3StreamAbort ,

Http3FrameReceived , Http3FrameSending .

Les règles existantes fonctionnent toujours, mais vous pouvez désormais faire preuve
d’une plus grande sélectivité en matière des règles que vous activez. Par exemple, la
surcharge d’observabilité liée à l’activation de la journalisation Debug pour les requêtes
incorrectes est considérablement réduite et peut être activée à l’aide de la configuration
suivante :

XML

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.Kestrel.BadRequests": "Debug"
}
}

Le filtrage des journaux applique des règles comportant le préfixe de catégorie à


correspondance la plus longue. Pour plus d’informations, consultez Application des
règles de filtrage

Émission de KestrelServerOptions via l’événement


EventSource
Le KestrelEventSource émet un nouvel événement contenant le JSON sérialisé
KestrelServerOptions lorsqu’il est activé avec la verbosité EventLevel.LogAlways . Cet
événement facilite le raisonnement du comportement du serveur lors de l’analyse des
traces collectées. Le JSON suivant représente un exemple de la charge utile de
l’événement :

JSON

{
"AllowSynchronousIO": false,
"AddServerHeader": true,
"AllowAlternateSchemes": false,
"AllowResponseHeaderCompression": true,
"EnableAltSvc": false,
"IsDevCertLoaded": true,
"RequestHeaderEncodingSelector": "default",
"ResponseHeaderEncodingSelector": "default",
"Limits": {
"KeepAliveTimeout": "00:02:10",
"MaxConcurrentConnections": null,
"MaxConcurrentUpgradedConnections": null,
"MaxRequestBodySize": 30000000,
"MaxRequestBufferSize": 1048576,
"MaxRequestHeaderCount": 100,
"MaxRequestHeadersTotalSize": 32768,
"MaxRequestLineSize": 8192,
"MaxResponseBufferSize": 65536,
"MinRequestBodyDataRate": "Bytes per second: 240, Grace Period:
00:00:05",
"MinResponseDataRate": "Bytes per second: 240, Grace Period: 00:00:05",
"RequestHeadersTimeout": "00:00:30",
"Http2": {
"MaxStreamsPerConnection": 100,
"HeaderTableSize": 4096,
"MaxFrameSize": 16384,
"MaxRequestHeaderFieldSize": 16384,
"InitialConnectionWindowSize": 131072,
"InitialStreamWindowSize": 98304,
"KeepAlivePingDelay": "10675199.02:48:05.4775807",
"KeepAlivePingTimeout": "00:00:20"
},
"Http3": {
"HeaderTableSize": 0,
"MaxRequestHeaderFieldSize": 16384
}
},
"ListenOptions": [
{
"Address": "https://127.0.0.1:7030",
"IsTls": true,
"Protocols": "Http1AndHttp2"
},
{
"Address": "https://[::1]:7030",
"IsTls": true,
"Protocols": "Http1AndHttp2"
},
{
"Address": "http://127.0.0.1:5030",
"IsTls": false,
"Protocols": "Http1AndHttp2"
},
{
"Address": "http://[::1]:5030",
"IsTls": false,
"Protocols": "Http1AndHttp2"
}
]
}

Nouvel événement DiagnosticSource pour les requêtes


HTTP rejetées
Kestrel émet désormais un nouvel événement DiagnosticSource pour les requêtes HTTP
rejetées au niveau de la couche de serveur. Il n’existait aucun moyen d’observer ces
requêtes rejetées avant ce changement. Le nouvel événement
DiagnosticSource Microsoft.AspNetCore.Server.Kestrel.BadRequest contient un

IBadRequestExceptionFeature qui peut être utilisé pour l’introspection de la raison pour


laquelle la requête a été rejetée.

C#

using Microsoft.AspNetCore.Http.Features;
using System.Diagnostics;

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();
var diagnosticSource = app.Services.GetRequiredService<DiagnosticListener>
();
using var badRequestListener = new BadRequestEventListener(diagnosticSource,
(badRequestExceptionFeature) =>
{
app.Logger.LogError(badRequestExceptionFeature.Error, "Bad request
received");
});
app.MapGet("/", () => "Hello world");

app.Run();

class BadRequestEventListener : IObserver<KeyValuePair<string, object>>,


IDisposable
{
private readonly IDisposable _subscription;
private readonly Action<IBadRequestExceptionFeature> _callback;

public BadRequestEventListener(DiagnosticListener diagnosticListener,


Action<IBadRequestExceptionFeature>
callback)
{
_subscription = diagnosticListener.Subscribe(this!, IsEnabled);
_callback = callback;
}
private static readonly Predicate<string> IsEnabled = (provider) =>
provider switch
{
"Microsoft.AspNetCore.Server.Kestrel.BadRequest" => true,
_ => false
};
public void OnNext(KeyValuePair<string, object> pair)
{
if (pair.Value is IFeatureCollection featureCollection)
{
var badRequestFeature =
featureCollection.Get<IBadRequestExceptionFeature>();

if (badRequestFeature is not null)


{
_callback(badRequestFeature);
}
}
}
public void OnError(Exception error) { }
public void OnCompleted() { }
public virtual void Dispose() => _subscription.Dispose();
}

Pour plus d’informations, consultez Journalisation et diagnostics dans Kestrel.


Création d’un ConnectionContext à partir d’un socket
Accept
Le nouveau SocketConnectionContextFactory permet de créer un ConnectionContext à
partir d’un socket accepté. Cela permet de générer un IConnectionListenerFactory
personnalisé basé sur un socket sans perdre tout le travail de performances et le
regroupement qui se produisent dans SocketConnection .

Consultez cet exemple d’un IConnectionListenerFactory personnalisé qui présente la


manière d’utiliser ce SocketConnectionContextFactory .

Kestrel est le profil de lancement par défaut pour Visual


Studio
Kestrel est le profil de lancement par défaut pour tous les nouveaux projets web dotnet.
Le démarrage de Kestrel est beaucoup plus rapide et favorise une expérience plus
réactive lors du développement d’applications.

IIS Express est toujours disponible en tant que profil de lancement pour des scénarios
tels que l’authentification Windows ou le partage de ports.

Les ports localhost pour Kestrel sont aléatoires


Pour plus d’informations, consultez Ports générés par un modèle pour Kestrel dans le
présent document.

Authentification et autorisation

Serveurs d’authentification
.NET 3 à .NET 5 utilisaient IdentityServer4 dans le cadre de notre modèle pour prendre
en charge l’émission de jetons JWT pour les applications monopage et Blazor. Les
modèles utilisent désormais le DuendeIdentity Server .

Si vous êtes en train d’étendre les modèles d’identité et de mettre à jour des projets
existants, vous devez mettre à jour les espaces de noms dans votre code de
IdentityServer4.IdentityServer à Duende.IdentityServer et suivre leurs instructions de

migration .

Le modèle de licence pour Duende Identity Server est passé à une licence réciproque,
dont l’utilisation commerciale en production peut entraîner des frais. Pour plus
d’informations, consultez la page de licence Duende .

Négociation différée du certificat client


Les développeurs peuvent désormais choisir d’utiliser la négociation différée du
certificat client en spécifiant ClientCertificateMode.DelayCertificate sur le
HttpsConnectionAdapterOptions. Cela fonctionne uniquement avec les
connexions HTTP/1.1, car le protocole HTTP/2 interdit la renégociation différée des
certificats. L’appelant de cette API doit mettre le corps de la demande en mémoire
tampon avant de demander le certificat client :

C#

using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.WebUtilities;

var builder = WebApplication.CreateBuilder(args);


builder.WebHost.UseKestrel(options =>
{
options.ConfigureHttpsDefaults(adapterOptions =>
{
adapterOptions.ClientCertificateMode =
ClientCertificateMode.DelayCertificate;
});
});

var app = builder.Build();


app.Use(async (context, next) =>
{
bool desiredState = GetDesiredState();
// Check if your desired criteria is met
if (desiredState)
{
// Buffer the request body
context.Request.EnableBuffering();
var body = context.Request.Body;
await body.DrainAsync(context.RequestAborted);
body.Position = 0;

// Request client certificate


var cert = await context.Connection.GetClientCertificateAsync();

// Disable buffering on future requests if the client doesn't


provide a cert
}
await next(context);
});
app.MapGet("/", () => "Hello World!");
app.Run();

Événement OnCheckSlidingExpiration pour contrôler le


renouvellement de cookie
L’expiration décalée de l’authentification Cookie peut désormais être personnalisée ou
supprimée à l’aide du nouveau OnCheckSlidingExpiration. Par exemple, cet événement
peut être utilisé par une application monopage qui a besoin d’effectuer régulièrement
un test ping sur le serveur sans affecter la session d’authentification.

Divers

Rechargement à chaud
Réalisez rapidement des mises à jour d’interface utilisateur et de code pour les
applications en cours d’exécution sans perdre l’état de l’application pour une expérience
de développement plus rapide et plus productive à l’aide du Rechargement à chaud.
Pour plus d’informations, consultez Prise en charge du .NET rechargement à chaud pour
ASP.NET Core et Mise à jour sur les progrès de .NET rechargement à chaud et points
clés de Visual Studio 2022 .

Modèles d’applications monopages (SPA) améliorées


Les modèles de projet ASP.NET Core ont été mis à jour pour Angular et React de
manière à utiliser un modèle amélioré pour les applications monopages, présentant plus
de flexibilité et un alignement plus étroit sur les modèles courants du développement
web front-end moderne.

Auparavant, le modèle ASP.NET Core pour Angular et React utilisait un intergiciel


spécialisé pendant le développement afin de lancer le serveur de développement de
l’infrastructure front-end, puis de proxyser les requêtes de ASP.NET Core vers le serveur
de développement. La logique de lancement du serveur de développement front-end
était spécifique à l’interface de ligne de commande de l’infrastructure front-end
correspondante. La prise en charge d’infrastructures front-end supplémentaires à l’aide
de ce modèle impliquait l’ajout d’une logique supplémentaire à ASP.NET Core.

Les modèles ASP.NET Core mis à jour pour Angular et React dans .NET 6 inversent cette
disposition et tirent parti de la prise en charge de proxy intégrée dans les serveurs de
développement de la plupart des infrastructures front-end modernes. Quand
l’application ASP.NET Core est lancée, le serveur de développement front-end est lancé
comme avant, mais le serveur de développement est configuré pour proxyser des
requêtes au processus back-end d’ASP.NET Core. Toute la configuration spécifique du
front-end pour configurer la proxysation fait partie de l’application, et non
d’ASP.NET Core. La configuration de projets ASP.NET Core pour fonctionner avec
d’autres infrastructures front-end est désormais simple : configurez le serveur de
développement front-end pour l’infrastructure choisie de manière à proxyser vers le
back-end d’ASP.NET Core à l’aide du modèle établi dans les modèles Angular et React.

Le code de démarrage de l’application ASP.NET Core ne nécessite plus une logique


spécifique à l’application monopage. La logique de démarrage du serveur de
développement front-end au cours du développement consiste à injecter le nouveau
package Microsoft.AspNetCore.SpaProxy dans l’application au moment de l’exécution.
Le routage de secours est géré à l’aide d’un routage de point de terminaison au lieu
d’un intergiciel spécifique à l’application monopage.

Les modèles qui suivent ce modèle peuvent toujours être exécutés dans Visual Studio en
tant que projet unique ou utiliser dotnet run de la ligne de commande. Lors de la
publication de l’application, le code front-end du dossier ClientApp est généré et
collecté comme avant dans la racine web de l’application hôte ASP.NET Core et délivré
sous forme de fichiers statiques. Les scripts inclus dans le modèle configurent le serveur
de développement front-end à utiliser le protocole HTTPS à l’aide du certificat de
développement ASP.NET Core.

Brouillon de la prise en charge du protocole HTTP/3 dans


.NET 6
HTTP/3 est actuellement en état de brouillon et peut donc faire l’objet de
modifications. La prise en charge du protocole HTTP/3 dans ASP.NET Core n’est pas
publiée, il s’agit d’une fonctionnalité d’évaluation incluse dans .NET 6.

Consultez l’entrée de blog Prise en charge du protocole HTTP/3 dans .NET 6 .

Annotations de type de référence Nullable


Certaines parties du code source ASP.NET Core 6.0 ont fait l’objet d’une application
d’annotations de possibilité de valeur null.

L’utilisation de la nouvelle fonctionnalité Nullable dans C# 8 permet à ASP.NET Core de


fournir une sécurité supplémentaire au moment de la compilation lors de la gestion des
types référence. Par exemple, la protection contre les exceptions de référence null . Les
projets qui ont choisi d’utiliser des annotations pouvant accepter la valeur Null peuvent
voir de nouveaux avertissements au moment de la génération de la part des API
d’ASP.NET Core.

Pour activer les types référence pouvant accepter la valeur Null, ajoutez la propriété
suivante aux fichiers du projet :

XML

<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>

Pour plus d’informations, consultez Types référence pouvant accepter la valeur Null.

Analyse du code source


Plusieurs analyseurs de plateforme du compilateur .NET ont été ajoutés pour inspecter
le code de l’application à la recherche de problèmes tels que la configuration ou l’ordre
incorrects de l’intergiciel, les conflits de routage, etc. Pour plus d’informations, consultez
Analyse du code dans les applications ASP.NET Core.

Améliorations apportées aux modèles d’application web


Les modèles d’application web :

Utilisent le nouveau modèle d’hébergement minimal.


Réduit considérablement le nombre de fichiers et de lignes de code nécessaires
pour créer une application. Par exemple, l’application web ASP.NET Core vide crée
un fichier C# avec quatre lignes de code qui est une application complète.
Unifie Startup.cs et Program.cs dans un seul fichier Program.cs .
Utilise des instructions de niveau supérieur pour réduire le code nécessaire pour
une application.
Utilise des directives using globales pour éliminer ou réduire le nombre de lignes
d’instructionusing nécessaires.

Ports générés par le modèle pour Kestrel


Des ports aléatoires sont attribués lors de la création du projet pour être utilisés par le
serveur web Kestrel. Les ports aléatoires permettent de réduire les conflits entre les
ports lorsque plusieurs projets sont exécutés sur le même ordinateur.
Lors de la création d’un projet, un port HTTP aléatoire compris entre 5000 et 5300 et un
port HTTPS aléatoire compris entre 7000 et 7300 sont spécifiés dans le fichier
Properties/launchSettings.json généré. Les ports peuvent être modifiés dans le fichier
Properties/launchSettings.json . Si aucun port n’est spécifié, Kestrel utilise les ports par

défaut HTTP 5000 et HTTPS 5001. Pour plus d’informations, consultez Configurer des
points de terminaison pour le serveur web ASP.NET Core Kestrel.

Nouvelles valeurs par défaut pour la journalisation


Les modifications suivantes ont été apportées à la fois à appsettings.json et à
appsettings.Development.json :

diff

- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
+ "Microsoft.AspNetCore": "Warning"

Le passage de "Microsoft": "Warning" à "Microsoft.AspNetCore": "Warning" entraîne la


journalisation de tous les messages d’information de l’espace de noms Microsoft , sauf
Microsoft.AspNetCore . Par exemple, Microsoft.EntityFrameworkCore est désormais

journalisé au niveau des informations.

Intergiciel de la page d’exception du développeur ajouté


automatiquement
Dans l’environnement de développement, DeveloperExceptionPageMiddleware est
ajouté par défaut. Il n’est plus nécessaire d’ajouter le code suivant aux applications web
d’interface utilisateur :

C#

if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

Prise en charge des en-têtes de demande encodés Latin1


dans HttpSysServer
HttpSysServer prend désormais en charge le décodage des en-têtes de demande

encodés Latin1 en définissant la propriété UseLatin1RequestHeaders de


HttpSysOptions sur true :

C#

var builder = WebApplication.CreateBuilder(args);


builder.WebHost.UseHttpSys(o => o.UseLatin1RequestHeaders = true);

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Les journaux du module ASP.NET Core comprennent des


timestamps et des PID
Les journaux de diagnostic améliorés du module ASP.NET Core (ANCM) pour IIS (ANCM)
comprennent des timestamps et un PID du processus émettant les journaux. La
journalisation des timestamps et du PID facilite le diagnostic des problèmes liés aux
redémarrages des processus qui se chevauchent dans IIS lorsque plusieurs processus
Worker IIS sont en cours d’exécution.

Les journaux qui en résultent ressemblent désormais à l’exemple de sortie présenté ci-
dessous :

CLI .NET

[2021-07-28T19:23:44.076Z, PID: 11020] [aspnetcorev2.dll] Initializing logs


for 'C:\<path>\aspnetcorev2.dll'. Process Id: 11020. File Version:
16.0.21209.0. Description: IIS ASP.NET Core Module V2. Commit:
96475a2acdf50d7599ba8e96583fa73efbe27912.
[2021-07-28T19:23:44.079Z, PID: 11020] [aspnetcorev2.dll] Resolving hostfxr
parameters for application: '.\InProcessWebSite.exe' arguments: '' path:
'C:\Temp\e86ac4e9ced24bb6bacf1a9415e70753\'
[2021-07-28T19:23:44.080Z, PID: 11020] [aspnetcorev2.dll] Known dotnet.exe
location: ''

Taille de la mémoire tampon entrante non consommée


configurable pour IIS
Auparavant, le serveur IIS ne mettait dans la mémoire tampon que 64 Kio des corps de
requêtes non consommés. La mise en mémoire tampon de 64 Kio a entraîné la
contrainte des lectures à cette taille maximale, ce qui affecte le niveau de performance
avec les corps entrants volumineux tels que les chargements. Dans .NET 6, la taille de la
mémoire tampon par défaut passe de 64 Kio à 1 Mio, ce qui devrait améliorer le débit
des chargements volumineux. Dans nos tests, un chargement de 700 Mio qui prenait
auparavant 9 secondes ne prend plus que 2,5 secondes.

L’inconvénient d’une taille de mémoire tampon plus importante réside dans


l’augmentation de la consommation de mémoire par requête lorsque l’application ne lit
pas rapidement à partir du corps de la demande. Ainsi, en plus d’avoir apporté des
modifications à la taille de la mémoire tampon par défaut, celle-ci est configurable, ce
qui permet aux applications de configurer la taille de la mémoire tampon en fonction de
la charge de travail.

Afficher les Tag Helpers des composants


Envisagez un composant d’affichage doté d’un paramètre facultatif, comme indiqué
dans le code suivant :

C#

class MyViewComponent
{
IViewComponentResult Invoke(bool showSomething = false) { ... }
}

ASP.NET Core 6 permet d’appeler le l’assistant de balise sans devoir spécifier une valeur
pour le paramètre showSomething :

razor

<vc:my />

Mise à jour du modèle Angular vers Angular 12


Le modèle ASP.NET Core 6.0 pour Angular utilise désormais Angular 12 .

Le modèle React a été mis à jour vers React 17 .

Seuil de mémoire tampon configurable avant l’écriture


sur disque dans le formateur de sortie Json.NET
Remarque : Nous recommandons l’utilisation du formateur de sortie System.Text.Json
sauf dans les cas où un sérialiseur Newtonsoft.Json est nécessaire pour des raisons de
compatibilité. Le sérialiseur System.Text.Json est entièrement async et fonctionne
efficacement pour les charges utiles plus volumineuses.

Le formateur de sortie Newtonsoft.Json par défaut met les réponses allant jusqu’à
32 Kio en mémoire tampon avant la mise en mémoire tampon sur le disque. Cela
permet d’éviter d’effectuer les E/S synchrones, ce qui peut entraîner d’autres effets
secondaires tels qu’une insuffisance de threads et des interblocages d’applications.
Toutefois, si la réponse est plus volumineuse que 32 Kio, des E/S de disque
considérables se produisent. Le seuil de mémoire est désormais configurable via la
propriété MvcNewtonsoftJsonOptions.OutputFormatterMemoryBufferThreshold avant la
mise en mémoire tampon sur le disque :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages()
.AddNewtonsoftJson(options =>
{
options.OutputFormatterMemoryBufferThreshold = 48 * 1024;
});

var app = builder.Build();

Pour plus d’informations, consultez cette demande de tirage (pull request) GitHub et
le fichier NewtonsoftJsonOutputFormatterTest.cs .

Get et set plus rapides pour les en-têtes HTTP


De nouvelles API ont été ajoutées pour exposer tous les en-têtes courants disponibles
sur Microsoft.Net.Http.Headers.HeaderNames en tant que propriétés sur le
IHeaderDictionary, ce qui a rendu l’API plus facile à utiliser. Par exemple, l’intergiciel en
ligne du code suivant obtient et définit à la fois les en-têtes de requête et de réponse à
l’aide des nouvelles API :

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () => "Hello World!");


app.Use(async (context, next) =>
{
var hostHeader = context.Request.Headers.Host;
app.Logger.LogInformation("Host header: {host}", hostHeader);
context.Response.Headers.XPoweredBy = "ASP.NET Core 6.0";
await next.Invoke(context);
var dateHeader = context.Response.Headers.Date;
app.Logger.LogInformation("Response date: {date}", dateHeader);
});

app.Run();

Pour les en-têtes implémentés, les accesseurs get et set sont implémentés par un accès
direct au champ et un contournement de la recherche. Pour les en-têtes non
implémentés, les accesseurs peuvent contourner la recherche initiale par rapport aux en-
têtes implémentés et effectuer directement la recherche de Dictionary<string,
StringValues> . Éviter la recherche entraîne un accès plus rapide pour les deux scénarios.

Diffusion en continu asynchrone


ASP.NET Core prend désormais en charge la diffusion en continu asynchrone à partir
des actions du contrôleur et des réponses du Formateur JSON. Le retour d’un
IAsyncEnumerable à partir d’une action ne met plus en mémoire tampon le contenu de
la réponse avant de l’envoyer. Le fait de ne pas mettre en mémoire tampon permet une
utilisation plus réduite de la mémoire lors du retour de jeux de données volumineux qui
peuvent être énumérés de manière asynchrone.

Il faut noter qu’Entity Framework Core fournit des implémentations de IAsyncEnumerable


pour interroger la base de données. La prise en charge améliorée de IAsyncEnumerable
dans ASP.NET Core dans .NET 6 peut améliorer l’efficacité de l’utilisation de EF Core avec
ASP.NET Core. Par exemple, le code suivant ne met plus les données du produit en
mémoire tampon avant d’envoyer la réponse :

C#

public IActionResult GetMovies()


{
return Ok(_context.Movie);
}

Toutefois, lors de l’utilisation du chargement différé dans EF Core, ce nouveau


comportement peut entraîner des erreurs en raison de l’exécution simultanée des
requêtes pendant l’énumération des données. Les applications peuvent revenir au
comportement précédent en mettant les données en mémoire tampon :
C#

public async Task<IActionResult> GetMovies2()


{
return Ok(await _context.Movie.ToListAsync());
}

Pour plus d’informations sur ce changement de comportement, consultez l’annonce


connexe.

Intergiciel de la journalisation HTTP


La journalisation HTTP est un nouvel intergiciel intégré qui consigne les informations
des requêtes HTTP et des réponses HTTP, y compris les en-têtes et l’ensemble du corps :

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();


app.UseHttpLogging();

app.MapGet("/", () => "Hello World!");

app.Run();

L’accès à / avec le code précédent consigne des informations similaires à la sortie


suivante :

CLI .NET

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
Request:
Protocol: HTTP/2
Method: GET
Scheme: https
PathBase:
Path: /
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,
*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cache-Control: max-age=0
Connection: close
Cookie: [Redacted]
Host: localhost:44372
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Edg/95.0.1020.30
sec-ch-ua: [Redacted]
sec-ch-ua-mobile: [Redacted]
sec-ch-ua-platform: [Redacted]
upgrade-insecure-requests: [Redacted]
sec-fetch-site: [Redacted]
sec-fetch-mode: [Redacted]
sec-fetch-user: [Redacted]
sec-fetch-dest: [Redacted]
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
Response:
StatusCode: 200
Content-Type: text/plain; charset=utf-8

La sortie précédente a été activée à l’aide du fichier appsettings.Development.json


suivant :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware":
"Information"
}
}
}

La journalisation HTTP fournit les journaux suivants :

Informations de requête HTTP


Propriétés communes
En-têtes
Corps
Informations de réponse HTTP

Pour configurer l’intergiciel de journalisation HTTP, spécifiez HttpLoggingOptions :

C#

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddHttpLogging(logging =>
{
// Customize HTTP logging.
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("My-Request-Header");
logging.ResponseHeaders.Add("My-Response-Header");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});

var app = builder.Build();


app.UseHttpLogging();

app.MapGet("/", () => "Hello World!");

app.Run();

IConnectionSocketFeature
La fonctionnalité de requête IConnectionSocketFeature fournit un accès au socket
Accept sous-jacent associé à la requête actuelle. Celle-ci est accessible via le
FeatureCollection sur HttpContext .

Par exemple, l’application suivante définit la propriété LingerState sur le socket accepté :

C#

var builder = WebApplication.CreateBuilder(args);


builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ConfigureEndpointDefaults(listenOptions =>
listenOptions.Use((connection, next) =>
{
var socketFeature =
connection.Features.Get<IConnectionSocketFeature>();
socketFeature.Socket.LingerState = new LingerOption(true, seconds:
10);
return next();
}));
});
var app = builder.Build();
app.MapGet("/", (Func<string>)(() => "Hello world"));
await app.RunAsync();

Contraintes de type générique dans Razor


Lors de la définition de paramètres de type générique dans Razor à l’aide de la directive
@typeparam , les contraintes de type générique peuvent désormais être spécifiées à l’aide

de la syntaxe C# standard :

Scripts SignalR, Blazor Server et MessagePack plus petits


Les scripts SignalR, MessagePack et Blazor Server sont désormais beaucoup plus petits,
ce qui favorise des téléchargements plus petits, moins d’analyse et de compilation
JavaScript par le navigateur et un démarrage plus rapide. Les réductions de taille :

signalr.js : 70 %

blazor.server.js : 45 %

Les scripts plus petits ont résulté de la contribution de la communauté de Ben Adams .
Pour plus d’informations et des détails sur la réduction de taille, consultez Demande de
tirage (pull request) GitHub de Ben .

Activer les sessions de profilage Redis


Une contribution de la communauté de Gabriel Lucaci active la session de profilage
Redis avec Microsoft.Extensions.Caching.StackExchangeRedis :

C#

using StackExchange.Redis.Profiling;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddStackExchangeRedisCache(options =>
{
options.ProfilingSession = () => new ProfilingSession();
});

Pour plus d’informations, consultez Profilage de StackExchange.Redis .

Cliché instantané dans IIS


Une fonctionnalité expérimentale a été ajoutée au Module ASP.NET Core (ANCM) pour
IIS pour ajouter la prise en charge des clichés instantanés des assemblys d’applications.
À l’heure actuelle, .NET verrouille les fichiers binaires d’application lors de l’exécution sur
Windows, ce qui rend le remplacement des fichiers binaires impossible quand
l’application est en cours d’exécution. Bien que notre recommandation reste toujours
d’utiliser un fichier d’application hors connexion, nous reconnaissons qu’il existe certains
scénarios (par exemple des déploiements FTP) dans lesquels cela n’est pas possible.

Dans ces scénarios, vous devez activer le cliché instantané en personnalisant les
paramètres du gestionnaire du module ASP.NET Core. Dans la plupart des cas, les
applications ASP.NET Core ne disposent pas d’un web.config archivé dans le contrôle de
code source que vous pouvez modifier. Dans ASP.NET Core, web.config est
généralement généré par le kit de développement logiciel (SDK). Vous pouvez
commencer par l’exemple de web.config suivant :

XML

<?xml version="1.0" encoding="utf-8"?>


<configuration>
<!-- To customize the asp.net core module uncomment and edit the following
section.
For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->

<system.webServer>
<handlers>
<remove name="aspNetCore"/>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2"
resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%"
stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout">
<handlerSettings>
<handlerSetting name="experimentalEnableShadowCopy" value="true" />
<handlerSetting name="shadowCopyDirectory"
value="../ShadowCopyDirectory/" />
<!-- Only enable handler logging if you encounter issues-->
<!--<handlerSetting name="debugFile" value=".\logs\aspnetcore-
debug.log" />-->
<!--<handlerSetting name="debugLevel" value="FILE,TRACE" />-->
</handlerSettings>
</aspNetCore>
</system.webServer>
</configuration>

Le cliché instantané dans IIS est une fonctionnalité expérimentale dont l’intégration à
ASP.NET Core n’est pas garantie. Veuillez laisser des commentaires sur le cliché
instantané IIS dans ce problème GitHub .

Ressources supplémentaires
Exemples de code migré vers le nouveau modèle d’hébergement minimal dans la
version 6.0
Nouveautés de .NET 6

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Nouveautés d’ASP.NET Core 5.0
Article • 24/12/2023

Cet article met en évidence les modifications les plus importantes dans ASP.NET
Core 5.0 et fournit des liens vers la documentation appropriée.

Améliorations MVC et Razor dans ASP.NET Core

Liaison de modèle DateTime au format UTC


La liaison de modèle prend désormais en charge la liaison de chaînes temporelles UTC à
DateTime . Si la demande contient une chaîne temporelle UTC, la liaison de modèle la lie

à un DateTime UTC. Par exemple, la chaîne temporelle suivante est liée au DateTime
UTC : https://example.com/mycontroller/myaction?time=2019-06-
14T02%3A30%3A04.0576719Z

Liaison de modèle et validation avec les types


d’enregistrement C# 9
Les types d’enregistrement C# 9 peuvent être utilisés avec la liaison de modèle dans un
contrôleur MVC ou une page Razor. Les types d’enregistrement sont un bon moyen de
modéliser les données transmises sur le réseau.

Par exemple, l’élément PersonController suivant utilise le type d’enregistrement Person


avec la liaison de modèle et la validation de formulaire :

C#

public record Person([Required] string Name, [Range(0, 150)] int Age);

public class PersonController


{
public IActionResult Index() => View();

[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}

Le fichier Person/Index.cshtml :
CSHTML

@model Person

Name: <input asp-for="Model.Name" />


<span asp-validation-for="Model.Name" />

Age: <input asp-for="Model.Age" />


<span asp-validation-for="Model.Age" />

Améliorations apportées à
DynamicRouteValueTransformer
ASP.NET Core 3.1 a introduit DynamicRouteValueTransformer comme un moyen
d’utiliser un point de terminaison personnalisé pour sélectionner dynamiquement une
action de contrôleur MVC ou une page Razor. Les applications ASP.NET Core 5.0
peuvent transmettre l’état à un DynamicRouteValueTransformer et filtrer l’ensemble de
points de terminaison choisi.

Divers
L’attribut [Compare] peut être appliqué aux propriétés sur un modèle de page
Razor.
Les paramètres et propriétés liés à partir du corps sont considérés comme requis
par défaut.

API Web

Spécification OpenAPI activée par défaut


La spécification OpenAPI est une norme de l’industrie utilisée pour décrire les API
HTTP et les intégrer dans des processus métier complexes ou avec des tiers. La
spécification OpenAPI est largement prise en charge par tous les fournisseurs de
services cloud et de nombreux registres d’API. Les applications qui émettent des
documents OpenAPI à partir d’API Web disposent d’une variété de nouvelles
opportunités dans lesquelles ces API peuvent être utilisées. En partenariat avec les
chargés de maintenance du projet open source Swashbuckle.AspNetCore , le modèle
d’API ASP.NET Core contient une dépendance NuGet sur Swashbuckle . Swashbuckle
est un package NuGet open source populaire qui émet dynamiquement des documents
OpenAPI. Pour ce faire, Swashbuckle effectue une introspection sur les contrôleurs d’API
et génère le document OpenAPI au moment de l’exécution ou au moment de la
génération à l’aide de l’interface de ligne de commande Swashbuckle.

Dans ASP.NET Core 5.0, les modèles d’API Web activent la prise en charge d’OpenAPI
par défaut. Pour désactiver OpenAPI :

Depuis la ligne de commande :

CLI .NET

dotnet new webapi --no-openapi true

À partir de Visual Studio : décochez Activer la prise en charge d’OpenAPI.

Tous les fichiers .csproj créés pour des projets d’API Web contiennent la référence du
package NuGet Swashbuckle.AspNetCore .

XML

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
</ItemGroup>

Le code généré par le modèle contient du code dans Startup.ConfigureServices qui


active la génération du document OpenAPI :

C#

public void ConfigureServices(IServiceCollection services)


{

services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApp1", Version =
"v1" });
});
}

La méthode Startup.Configure ajoute l’intergiciel Swashbuckle, qui :

permet le processus de génération du document ;


active la page d’interface utilisateur Swagger par défaut en mode de
développement.
Le code généré par le modèle n’expose pas accidentellement la description de l’API lors
de la publication en production.

C#

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)


{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json",
"WebApp1 v1"));
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

Importation dans la gestion des API Azure


Quand les projets d’API ASP.NET Core activent OpenAPI, la publication de Visual
Studio 2019 version 16.8 ou ultérieure offre automatiquement une étape
supplémentaire dans le flux de publication. Les développeurs qui utilisent la gestion des
API Azure ont la possibilité d’importer automatiquement les API dans la gestion des API
Azure durant le flux de publication :
Meilleure expérience de lancement pour les projets d’API
Web
Avec OpenAPI activé par défaut, l’expérience de lancement d’application (F5) pour les
développeurs d’API Web s’améliore considérablement. Avec ASP.NET Core 5.0, le
modèle d’API Web est préconfiguré pour charger la page d’interface utilisateur Swagger.
La page d’interface utilisateur Swagger fournit la documentation ajoutée pour l’API
publiée et permet de tester les API en un seul clic.
Blazor

Optimisation des performances


Pour .NET 5, nous avons apporté des améliorations significatives aux performances
d’exécution Blazor WebAssembly avec un accent particulier sur le rendu complexe de
l’interface utilisateur et la sérialisation JSON. Dans nos tests de performances, Blazor
WebAssembly dans .NET 5 est deux à trois fois plus rapide pour la plupart des scénarios.
Pour plus d’informations, consultez Blog ASP.NET : mises à jour ASP.NET Core dans
.NET 5 Release Candidate 1 .

Isolation CSS
Blazor prend désormais en charge la définition de styles CSS qui sont limités à un
composant donné. Les styles CSS spécifiques aux composants permettent de mieux
raisonner au sujet des styles dans une application et d’éviter les effets secondaires
involontaires des styles globaux. Pour plus d’informations, consultez Isolation CSS
d’ASP.NET Core Blazor.

Nouveau composant InputFile


Le composant InputFile permet de lire un ou plusieurs fichiers sélectionnés par un
utilisateur pour le chargement. Pour plus d’informations, consultez Chargements de
fichiers ASP.NET Core Blazor.

Nouveaux composants InputRadio et InputRadioGroup


Blazor dispose de composants InputRadio et InputRadioGroup intégrés qui simplifient la
liaison de données à des groupes de cases d’option avec validation intégrée. Pour
obtenir plus d’informations, consultez Composants d’entrée Blazor ASP.NET Core.

Virtualisation de composant
Améliorez les performances perçues du rendu des composants à l’aide de la prise en
charge intégrée de la virtualisation de l’infrastructure Blazor. Pour plus d’informations,
consultez Virtualisation des composants ASP.NET Core Razor.

Prise en charge des événements ontoggle


Les événements Blazor prennent désormais en charge l’événement DOM ontoggle . Pour
plus d’informations, consultez Gestion des événements ASP.NET Core Blazor.

Définir le focus de l’interface utilisateur dans les


applications Blazor
Utilisez la méthode pratique FocusAsync sur des références d’élément pour définir le
focus de l’interface utilisateur sur cet élément. Pour plus d’informations, consultez
Gestion des événements ASP.NET Core Blazor.

Attributs de classe CSS de validation personnalisée


Les attributs de classe CSS de validation personnalisée sont utiles lors de l’intégration à
des infrastructures CSS, telles que Bootstrap. Pour obtenir plus d’informations, consultez
Validation des formulaires Blazor ASP.NET Core.

Prise en charge de IAsyncDisposable


Les composants Razor prennent désormais en charge l’interface IAsyncDisposable pour
la mise en production asynchrone des ressources allouées.

Isolation JavaScript et références d’objets


Blazor active l’isolation JavaScript dans les modules JavaScript standard. Pour plus
d’informations, consultez Appeler des fonctions JavaScript à partir de méthodes .NET
dans ASP.NET Core Blazor.

Prise en charge du nom d’affichage des composants de


formulaire
Les composants intégrés suivants prennent en charge les noms d’affichage avec le
paramètre DisplayName :

InputDate

InputNumber

InputSelect

Pour plus d’informations, consultez Vue d’ensemble des formulaires Blazor ASP.NET
Core.

Paramètres de route fourre-tout


Les paramètres de route Catch-all, qui capturent les chemins d’accès dans les limites de
plusieurs dossiers, sont pris en charge dans les composants. Pour plus d’informations,
consultez Routage et navigation ASP.NET Core Blazor.

Améliorations du débogage
Le débogage des applications Blazor WebAssembly est amélioré dans ASP.NET Core 5.0.
En outre, le débogage est désormais pris en charge sur Visual Studio pour Mac. Pour
plus d’informations, consultez Déboguer des applications Blazor ASP.NET Core.

Microsoft Identity v2.0 et MSAL v2.0


La sécurité Blazor utilise désormais Microsoft Identity v2.0 (Microsoft.Identity.Web et
Microsoft.Identity.Web.UI ) et MSAL v2.0. Pour plus d’informations, consultez les
rubriques du nœud Blazor Security and Identity.

Stockage par navigateur protégé pour Blazor Server


Les applications Blazor Server peuvent désormais utiliser la prise en charge intégrée
pour stocker l’état de l’application dans le navigateur qui a été protégé contre la
falsification à l’aide de la protection des données ASP.NET Core. Les données peuvent
être stockées dans le stockage de navigateur local ou dans le stockage de session. Pour
plus d’informations, consultez Gestion d’état ASP.NET Core Blazor.

Prérendu Blazor WebAssembly


L’intégration des composants est améliorée entre les modèles d’hébergement et les
applications Blazor WebAssembly peuvent désormais effectuer un prérendu de la sortie
sur le serveur.

Améliorations apportées au découpage et à la liaison


Blazor WebAssembly effectue un découpage/une liaison de langage intermédiaire (IL)
pendant un build pour découper le langage intermédiaire inutile des assemblys de
sortie de l’application. Avec la publication d’ASP.NET Core 5.0, Blazor WebAssembly
effectue un découpage amélioré avec des options de configuration supplémentaires.
Pour plus d’informations, consultez Configurer l’outil de découpage pour ASP.NET Core
Blazor et Options de découpage.

Analyseur de compatibilité du navigateur


Les applications Blazor WebAssembly ciblent la surface d’exposition complète des API
.NET, mais toutes les API .NET ne sont pas prises en charge sur WebAssembly en raison
des contraintes de bac à sable du navigateur. Les API non prises en charge lèvent
PlatformNotSupportedException lors de leur exécution sur WebAssembly. Un analyseur
de compatibilité de plateforme avertit le développeur quand l’application utilise des API
qui ne sont pas prises en charge par les plateformes cibles de l’application. Pour plus
d’informations, consultez Consommer des composants ASP.NET Core Razor à partir
d’une bibliothèque de classes (RCL) Razor.

Charger des assemblys en mode différé


Les performances de démarrage des applications Blazor WebAssembly peuvent être
améliorées en reportant le chargement de certains assemblys d’application jusqu’à ce
qu’ils soient requis. Pour plus d’informations, consultez Charger des assemblys en mode
différé dans ASP.NET Core Blazor WebAssembly.

Mise à jour de la prise en charge de la globalisation


La prise en charge de la globalisation est disponible pour Blazor WebAssembly en
fonction des composants ICU (International Components for Unicode). Pour plus
d’informations, consultez Globalisation et localisation d’ASP.NET Core Blazor.

gRPC
De nombreuses améliorations de performances ont été apportées dans gRPC . Pour
plus d’informations, consultez Améliorations des performances gRPC dans .NET 5 .

Pour plus d’informations sur gRPC, consultez Vue d’ensemble de gRPC sur .NET.

SignalR

Filtres hub SignalR


Les filtres hub SignalR, appelés pipelines hub dans ASP.NET SignalR, sont une
fonctionnalité qui permet au code de s’exécuter avant et après l’appel des méthodes
Hub. L’exécution de code avant et après l’appel des méthodes Hub est similaire à la
façon dont l’intergiciel a la possibilité d’exécuter du code avant et après une requête
HTTP. Les utilisations courantes incluent la journalisation, la gestion des erreurs et la
validation des arguments.

Pour plus d’informations, consultez Utiliser des filtres hub dans ASP.NET Core SignalR.

Appels de hub parallèles SignalR


ASP.NET Core SignalR est désormais capable de gérer les appels de hub parallèles. Le
comportement par défaut peut être modifié pour permettre aux clients d’appeler
plusieurs méthodes hub à la fois :

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddSignalR(options =>
{
options.MaximumParallelInvocationsPerClient = 5;
});
}

Ajout de la prise en charge de Messagepack dans le client


Java SignalR
Un nouveau package, com.microsoft.signalr.messagepack , ajoute la prise en charge
de MessagePack au client Java SignalR. Pour utiliser le protocole de hub MessagePack,
ajoutez .withHubProtocol(new MessagePackHubProtocol()) au générateur de connexion :

Java

HubConnection hubConnection = HubConnectionBuilder.create(


"http://localhost:53353/MyHub")
.withHubProtocol(new MessagePackHubProtocol())
.build();

Kestrel
Points de terminaison rechargeables via la configuration : Kestrel peut détecter les
modifications apportées à la configuration transmises à
KestrelServerOptions.Configure et les dissocier des points de terminaison existants,
pour les lier à de nouveaux points de terminaison sans nécessiter le redémarrage
de l’application quand le paramètre reloadOnChange a pour valeur true . Par défaut,
quand vous utilisez ConfigureWebHostDefaults ou CreateDefaultBuilder, Kestrel est
lié à la sous-section de configuration « Kestrel » avec reloadOnChange activé. Les
applications doivent transmettre reloadOnChange: true quand elles appellent
KestrelServerOptions.Configure manuellement pour obtenir des points de

terminaison rechargeables.

Améliorations des en-têtes de réponse HTTP/2. Pour plus d’informations, consultez


Améliorations des performances dans la section suivante.

Prise en charge des types de points de terminaison supplémentaires dans le


transport de sockets : en ajoutant à la nouvelle API introduite dans
System.Net.Sockets, le transport par défaut des sockets dans Kestrel permet la
liaison à la fois aux handles de fichiers existants et aux sockets de domaine Unix. La
prise en charge de la liaison à des handles de fichiers existants permet d’utiliser
l’intégration Systemd existante sans nécessiter le transport libuv .

Décodage d’en-tête personnalisé dans Kestrel : les applications peuvent spécifier


quel Encoding utiliser pour interpréter les en-têtes entrants en fonction du nom de
l’en-tête au lieu d’utiliser par défaut UTF-8. Définissez la propriété
Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions.RequestHeaderEncoding

Selector pour spécifier l’encodage à utiliser :

C#
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.RequestHeaderEncodingSelector = encoding =>
{
return encoding switch
{
"Host" => System.Text.Encoding.Latin1,
_ => System.Text.Encoding.UTF8,
};
};
});
webBuilder.UseStartup<Startup>();
});

Options spécifiques au point de terminaison Kestrel via la


configuration
La prise en charge a été ajoutée pour la configuration des options spécifiques au point
de terminaison de Kestrel via la configuration. Les configurations spécifiques au point de
terminaison incluent :

Protocoles HTTP utilisés


Protocoles TLS utilisés
Certificat sélectionné
Mode de certificat client

La configuration permet de spécifier quel certificat est sélectionné en fonction du nom


de serveur spécifié. Le nom de serveur fait partie de l’extension SNI (Server Name
Indication) du protocole TLS, comme indiqué par le client. La configuration de Kestrel
prend également en charge un préfixe générique dans le nom d’hôte.

L’exemple suivant montre comment spécifier un élément spécifique au point de


terminaison à l’aide d’un fichier de configuration :

JSON

{
"Kestrel": {
"Endpoints": {
"EndpointName": {
"Url": "https://*",
"Sni": {
"a.example.org": {
"Protocols": "Http1AndHttp2",
"SslProtocols": [ "Tls11", "Tls12"],
"Certificate": {
"Path": "testCert.pfx",
"Password": "testPassword"
},
"ClientCertificateMode" : "NoCertificate"
},
"*.example.org": {
"Certificate": {
"Path": "testCert2.pfx",
"Password": "testPassword"
}
},
"*": {
// At least one sub-property needs to exist per
// SNI section or it cannot be discovered via
// IConfiguration
"Protocols": "Http1",
}
}
}
}
}
}

SNI (Server Name Indication) est une extension TLS permettant d’inclure un domaine
virtuel dans le cadre de la négociation SSL. Cela signifie en fait que le nom de domaine
virtuel, ou un nom d’hôte, peut être utilisé pour identifier le point de terminaison du
réseau.

Optimisation des performances

HTTP/2
Réductions significatives des allocations dans le chemin de code HTTP/2.

Prise en charge de la compression dynamique HPack des en-têtes de réponse


HTTP/2 dans Kestrel. Pour plus d’informations, consultez Taille de la table d’en-tête
et HPACK : le tueur silencieux (fonctionnalité) de HTTP/2 .

Envoi de trames PING HTTP/2 : HTTP/2 dispose d’un mécanisme permettant


d’envoyer des trames PING afin de garantir qu’une connexion inactive est toujours
fonctionnelle. La garantie d’une connexion viable est particulièrement utile lorsque
vous travaillez avec des flux de longue durée qui sont souvent inactifs, mais qui
connaissent une activité par intermittence, par exemple, les flux gRPC. Les
applications peuvent envoyer des trames PING périodiques dans Kestrel en
définissant les limites sur KestrelServerOptions :

C#

public static IHostBuilder CreateHostBuilder(string[] args) =>


Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.Limits.Http2.KeepAlivePingInterval =
TimeSpan.FromSeconds(10);
options.Limits.Http2.KeepAlivePingTimeout =
TimeSpan.FromSeconds(1);
});
webBuilder.UseStartup<Startup>();
});

Conteneurs
Avant .NET 5.0, la création et la publication d’un fichier Dockerfile pour une application
ASP.NET Core nécessitaient l’extraction (pull) de l’intégralité du kit SDK .NET Core et de
l’image ASP.NET Core. Avec cette version, l’extraction des octets d’image du kit SDK est
réduite et les octets extraits pour l’image ASP.NET Core sont en grande partie éliminés.
Pour plus d’informations, consultez ce commentaire de problème GitHub .

Authentification et autorisation

Authentification ID Microsoft Entra auprès de


Microsoft.Identity.Web
Les modèles de projet ASP.NET Core s’intègrent désormais à Microsoft.Identity.Web
pour gérer l’authentification avec ID Microsoft Entra. Le package
Microsoft.Identity.Web fournit :

Une meilleure expérience d’authentification au moyen d’ID Microsoft Entra.


Un moyen plus simple d’accéder aux ressources Azure pour le compte de vos
utilisateurs, y compris Microsoft Graph. Consultez l’exemple
Microsoft.Identity.Web , qui commence par une connexion de base et avance via
la mutualisation, l’utilisation des API Azure, l’utilisation de Microsoft Graph et la
protection de vos propres API. Microsoft.Identity.Web est disponible en même
temps que .NET 5.
Autoriser l’accès anonyme à un point de terminaison
La méthode d’extension AllowAnonymous autorise l’accès anonyme à un point de
terminaison :

C#

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)


{
app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
})
.AllowAnonymous();
});
}

Gestion personnalisée des échecs d’autorisation


La gestion personnalisée des échecs d’autorisation est désormais plus facile avec la
nouvelle interface IAuthorizationMiddlewareResultHandler qui est appelée par
l’intergicield’autorisation. L’implémentation par défaut reste la même, mais un
gestionnaire personnalisé peut être inscrit dans l’injection de dépendances, qui autorise
des réponses HTTP personnalisées en fonction de la raison de l’échec de l’autorisation.
Consultez cet exemple qui illustre l’utilisation de
IAuthorizationMiddlewareResultHandler .

Autorisation lors de l’utilisation du routage de point de


terminaison
L’autorisation lors de l’utilisation du routage du point de terminaison reçoit désormais le
HttpContext à la place de l’instance du point de terminaison. Cela permet à l’intergiciel

d’autorisation d’accéder à RouteData et aux autres propriétés de HttpContext qui


n’étaient pas accessibles via la classe Endpoint. Le point de terminaison peut être extrait
du contexte à l’aide de context.GetEndpoint.
Contrôle d’accès en fonction du rôle avec
l’authentification Kerberos et LDAP sur Linux
Consultez Authentification Kerberos et contrôle d’accès en fonction du rôle (RBAC)

Améliorations d’API

Méthodes d’extension JSON pour HttpRequest et


HttpResponse
Les données JSON peuvent être lues et écrites dans et à partir de HttpRequest et de
HttpResponse à l’aide des nouvelles méthodes d’extension ReadFromJsonAsync et

WriteAsJsonAsync . Ces méthodes d’extension utilisent le sérialiseur System.Text.Json


pour gérer les données JSON. La nouvelle méthode d’extension HasJsonContentType
peut également vérifier si une demande a un type de contenu JSON.

Les méthodes d’extension JSON peuvent être combinées avec le routage de point de
terminaison pour créer des API JSON dans un style de programmation que nous
appelons route vers le code. Il s’agit d’une nouvelle option pour les développeurs qui
souhaitent créer des API JSON de base de manière légère. Par exemple, une application
web qui n’a qu’une poignée de points de terminaison peut choisir d’utiliser une route
vers le code plutôt que les fonctionnalités complètes d’ASP.NET Core MVC :

C#

endpoints.MapGet("/weather/{city:alpha}", async context =>


{
var city = (string)context.Request.RouteValues["city"];
var weather = GetFromDatabase(city);

await context.Response.WriteAsJsonAsync(weather);
});

System.Diagnostics.Activity
Le format par défaut pour System.Diagnostics.Activity est désormais le format W3C. Cela
rend la prise en charge du suivi distribué dans ASP.NET Core interopérable avec
davantage d’infrastructures par défaut.

FromBodyAttribute
FromBodyAttribute prend désormais en charge la configuration d’une option qui
permet à ces paramètres ou propriétés d’être considérés comme facultatifs :

C#

public IActionResult Post([FromBody(EmptyBodyBehavior =


EmptyBodyBehavior.Allow)]
MyModel model)
{
...
}

Améliorations diverses
Nous avons commencé à appliquer des annotations nullables aux assemblys ASP.NET
Core. Nous prévoyons d’annoter la majeure partie de la surface d’API publique
commune de l’infrastructure .NET 5.

Contrôler l’activation de la classe de démarrage


Une surcharge UseStartup supplémentaire a été ajoutée pour permettre à une
application de fournir une méthode de fabrique pour contrôler l’activation de classe
Startup . Le contrôle de l’activation de classe Startup est utile pour transmettre des

paramètres supplémentaires à Startup qui sont initialisés avec l’hôte :

C#

public class Program


{
public static async Task Main(string[] args)
{
var logger = CreateLogger();
var host = Host.CreateDefaultBuilder()
.ConfigureWebHost(builder =>
{
builder.UseStartup(context => new Startup(logger));
})
.Build();

await host.RunAsync();
}
}

Actualisation automatique avec dotnet watch


Dans .NET 5, l’exécution de dotnet watch sur un projet ASP.NET Core lance le navigateur
par défaut et actualise automatiquement le navigateur à mesure que des modifications
sont apportées au code. Cela signifie que vous pouvez :

Ouvrir un projet ASP.NET Core dans un éditeur de texte.


Exécutez dotnet watch .
Vous concentrer sur les modifications du code pendant que les outils gèrent la
reconstruction, le redémarrage et le rechargement de l’application.

Formateur d’enregistreur d’événements de console


Des améliorations ont été apportées au fournisseur de journaux de console dans la
bibliothèque Microsoft.Extensions.Logging . Les développeurs peuvent désormais
implémenter un ConsoleFormatter personnalisé pour exercer un contrôle total sur la
mise en forme et la colorisation de la sortie de la console. Les API de formateur
permettent une mise en forme enrichie en implémentant un sous-ensemble des
séquences d’échappement VT-100. VT-100 est pris en charge par la plupart des
terminaux modernes. L’enregistreur d’événements de console peut analyser les
séquences d’échappement sur des terminaux non pris en charge, ce qui permet aux
développeurs de créer un seul formateur pour tous les terminaux.

Enregistreur d’événements de console JSON


En plus de la prise en charge des formateurs personnalisés, nous avons également
ajouté un formateur JSON intégré qui émet des journaux JSON structurés à la console.
Le code suivant montre comment passer de l’enregistreur d’événements par défaut à
JSON :

C#

public static IHostBuilder CreateHostBuilder(string[] args) =>


Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.AddJsonConsole(options =>
{
options.JsonWriterOptions = new JsonWriterOptions()
{ Indented = true };
});
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
Les messages de journal émis dans la console sont au format JSON :

JSON

{
"EventId": 0,
"LogLevel": "Information",
"Category": "Microsoft.Hosting.Lifetime",
"Message": "Now listening on: https://localhost:5001",
"State": {
"Message": "Now listening on: https://localhost:5001",
"address": "https://localhost:5001",
"{OriginalFormat}": "Now listening on: {address}"
}
}

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Nouveautés d’ASP.NET Core 3.1
Article • 30/11/2023

Cet article met en évidence les modifications les plus importantes dans ASP.NET Core
3.1 et fournit des liens vers la documentation appropriée.

Prise en charge des classes partielles pour les


composants Razor
Les composants Razor sont désormais générés en tant que classes partielles. Le code
d’un composant Razor peut être écrit à l’aide d’un fichier code-behind défini comme
une classe partielle plutôt que de définir tout le code du composant dans un seul fichier.
Pour plus d’informations, consultez Prise en charge des classes partielles.

Tag Helper de composant et passage de


paramètres aux composants de niveau
supérieur
Dans Blazor avec ASP.NET Core 3.0, les composants étaient rendus dans les pages et les
vues à l’aide d’un assistant HTML ( Html.RenderComponentAsync ). Dans ASP.NET Core 3.1,
rendez un composant à partir d’une page ou d’une vue avec le nouveau Tag Helper de
composant :

CSHTML

<component type="typeof(Counter)" render-mode="ServerPrerendered" />

L’assistant HTML reste pris en charge dans ASP.NET Core 3.1, mais le Tag Helper de
composant est recommandé.

Les applications Blazor Server peuvent désormais passer des paramètres aux
composants de niveau supérieur pendant le rendu initial. Auparavant, vous pouviez
uniquement passer des paramètres à un composant de niveau supérieur avec
RenderMode.Static. Avec cette version, RenderMode.Server et
RenderMode.ServerPrerendered sont pris en charge. Toutes les valeurs de paramètre
spécifiées sont sérialisées au format JSON et incluses dans la réponse initiale.
Par exemple, effectuez le prérendu d’un composant Counter avec un incrément
( IncrementAmount ) :

CSHTML

<component type="typeof(Counter)" render-mode="ServerPrerendered"


param-IncrementAmount="10" />

Pour plus d’informations, consultez Intégrer des composants dans Razor Pages et les
applications MVC.

Prise en charge des files d’attente partagées


dans HTTP.sys
HTTP.sys prend en charge la création de files d’attente de requêtes anonymes. Dans
ASP.NET Core 3.1, nous avons ajouté la possibilité de créer ou d’attacher à une file
d’attente de requêtes existante nommée HTTP.sys. La création ou l’attachement à une
file d’attente de requêtes nommée HTTP.sys permet de créer des scénarios où le
processus de contrôleur HTTP.sys propriétaire de la file d’attente est indépendant du
processus de l’écouteur. Cette indépendance permet de conserver les connexions
existantes et les requêtes mises en file d’attente entre les redémarrages du processus de
l’écouteur :

C#

public static IHostBuilder CreateHostBuilder(string[] args) =>


Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
// ...
webBuilder.UseHttpSys(options =>
{
options.RequestQueueName = "MyExistingQueue";
options.RequestQueueMode = RequestQueueMode.CreateOrAttach;
});
});

Changements cassants pour les cookies


SameSite
Le comportement des cookies SameSite a changé pour refléter les changements à venir
du navigateur. Cela peut affecter les scénarios d’authentification comme AzureAd,
OpenIdConnect ou WsFederation. Pour plus d’informations, consultez Utiliser des cookie
SameSite dans ASP.NET Core.

Empêcher les actions par défaut pour les


événements dans les applications Blazor
Utilisez l’attribut de directive @on{EVENT}:preventDefault pour empêcher l’action par
défaut d’un événement. Dans l’exemple suivant, l’action par défaut d’affichage du
caractère de la clé dans la zone de texte est empêchée :

razor

<input value="@_count" @onkeypress="KeyHandler" @onkeypress:preventDefault


/>

Pour plus d’informations, consultez Empêcher les actions par défaut.

Arrêter la propagation d’événements dans les


applications Blazor
Utilisez l’attribut de directive @on{EVENT}:stopPropagation pour arrêter la propagation
d’événements. Dans l’exemple suivant, la case à cocher empêche les événements de clic
du <div> enfant de se propager au <div> parent :

razor

<input @bind="_stopPropagation" type="checkbox" />

<div @onclick="OnSelectParentDiv">
<div @onclick="OnSelectChildDiv"
@onclick:stopPropagation="_stopPropagation">
...
</div>
</div>

@code {
private bool _stopPropagation = false;
}

Pour plus d’informations, consultez Arrêter la propagation d’événements.


Erreurs détaillées pendant le développement
d’applications Blazor
Lorsqu’une application Blazor ne fonctionne pas correctement pendant le
développement, la réception d’informations détaillées sur les erreurs de l’application
aide à diagnostiquer et résoudre le problème. Lorsqu’une erreur se produit, les
applications Blazor affichent une barre dorée en bas de l’écran :

Pendant le développement, la barre dorée vous dirige vers la console du


navigateur, où vous pouvez voir l’exception.
En production, la barre dorée avertit l’utilisateur qu’une erreur s’est produite et
recommande d’actualiser le navigateur.

Pour plus d’informations, consultez Gérer les erreurs dans les applications ASP.NET
CoreBlazor.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Nouveautés d’ASP.NET Core 3.0
Article • 30/11/2023

Cet article met en évidence les modifications les plus importantes dans ASP.NET Core
3.0 et fournit des liens vers la documentation appropriée.

Blazor
Blazor est une nouvelle infrastructure dans ASP.NET Core pour la création d’une
interface utilisateur web interactive côté client avec .NET :

Créer des interfaces utilisateur interactives riches en utilisant C#.


Partagez la logique d’application côté serveur et côté client écrite dans .NET.
Affichez l’interface utilisateur en langage HTML et CSS pour une large prise en
charge des navigateurs, y compris les navigateurs mobiles.

Scénarios pris en charge par l’infrastructure Blazor :

Composants d’interface utilisateur réutilisables (composants Razor)


Routage côté client
Dispositions des composants
Prise en charge de l’injection de dépendances
Formulaires et validation
Fournir des composants Razor dans les bibliothèques de classes Razor
Interopérabilité JavaScript

Pour plus d’informations, consultez ASP.NET Core Blazor.

Blazor Server
Blazor dissocie la logique de rendu de composant de la manière dont les mises à jour de
l’interface utilisateur sont appliquées. Blazor Server prend en charge l’hébergement des
composants Razor sur le serveur dans une application ASP.NET Core. Les mises à jour de
l’IU sont gérées via une connexion SignalR. Blazor Server est pris en charge dans
ASP.NET Core 3.0.

Blazor WebAssembly (version préliminaire)


Les applications Blazor peuvent également être exécutées directement dans le
navigateur à l’aide d’un runtime .NET basé sur WebAssembly. Blazor WebAssembly est
en préversion et n’est pas pris en charge dans ASP.NET Core 3.0. Blazor WebAssembly
sera pris en charge dans une prochaine version de ASP.NET Core.

Composants Razor
Les applications Blazor sont générées à partir de composants. Les composants sont des
blocs autonomes d’interface utilisateur, tels qu’une page, une boîte de dialogue ou un
formulaire. Les composants sont des classes .NET normales qui définissent la logique
d’affichage de l’interface utilisateur et les gestionnaires d’événements côté client. Vous
pouvez créer des applications web interactives riches sans JavaScript.

Les composants de Blazor sont généralement créés à l’aide de la syntaxe Razor, un


mélange naturel de HTML et C#. Les composants Razor sont similaires à Razor Pages et
MVC dans lesquelles ils utilisent tous les deux Razor. Contrairement aux pages et aux
vues, qui sont basées sur un modèle de requête-réponse, les composants sont utilisés
spécifiquement pour gérer la composition de l’interface utilisateur.

gRPC
gRPC :

Est une infrastructure RPC (appel de procédure distante) populaire et à hautes


performance.

Offre une approche de contrat d’abord avisée pour le développement d’API.

Utilise des technologies modernes telles que :


HTTP/2 pour le transport.
Mémoires tampons de protocole comme langage de description de l’interface.
Format de sérialisation binaire.

Fournit des fonctionnalités telles que :


Authentification
Flux bidirectionnel et contrôle de flux.
Annulations et délais d’expiration.

La fonctionnalité gRPC dans ASP.NET Core 3.0 inclut :

Grpc.AspNetCore : infrastructure ASP.NET Core pour l’hébergement de services


gRPC. gRPC sur ASP.NET Core s’intègre à des fonctionnalités standard ASP.NET
Core telles que la journalisation, l’injection de dépendances (DI), l’authentification
et l’autorisation.
Grpc.Net.Client : client gRPC pour .NET Core qui s’appuie sur le HttpClient
familier.
Grpc.Net.ClientFactory : intégration du client gRPC à HttpClientFactory .

Pour plus d’informations, consultez Vue d’ensemble de gRPC sur .NET.

SignalR
Consultez le code de mise à jour SignalR pour obtenir des instructions de migration.
SignalR utilise désormais System.Text.Json pour sérialiser/désérialiser les messages
JSON. Consultez Basculer vers Newtonsoft.Json pour obtenir des instructions pour
restaurer le sérialiseur basé sur Newtonsoft.Json .

Dans les clients JavaScript et .NET pour SignalR, la prise en charge a été ajoutée pour la
reconnexion automatique. Par défaut, le client tente de se reconnecter immédiatement
et réessaye après 2, 10 et 30 secondes si nécessaire. Si le client se reconnecte
correctement, il reçoit un nouvel ID de connexion. La reconnexion automatique est
activée :

JavaScript

const connection = new signalR.HubConnectionBuilder()


.withUrl("/chathub")
.withAutomaticReconnect()
.build();

Les intervalles de reconnexion peuvent être spécifiés en transmettant un tableau de


durées en millisecondes :

JavaScript

.withAutomaticReconnect([0, 3000, 5000, 10000, 15000, 30000])


//.withAutomaticReconnect([0, 2000, 10000, 30000]) The default intervals.

Une implémentation personnalisée peut être transmise pour un contrôle total des
intervalles de reconnexion.

Si la reconnexion échoue après le dernier intervalle de reconnexion :

Le client considère que la connexion est hors connexion.


Le client cesse de tenter de se reconnecter.

Pendant les tentatives de reconnexion, mettez à jour l’interface utilisateur de


l’application pour informer l’utilisateur que la reconnexion est tentée.
Pour fournir des commentaires sur l’interface utilisateur lorsque la connexion est
interrompue, l’API cliente SignalR a été développée pour inclure les gestionnaires
d’événements suivants :

onreconnecting : permet aux développeurs de désactiver l’interface utilisateur ou

de permettre aux utilisateurs de savoir que l’application est hors connexion.


onreconnected : permet aux développeurs de mettre à jour l’interface utilisateur

une fois la connexion rétablie.

Le code suivant utilise onreconnecting pour mettre à jour l’interface utilisateur lors de la
tentative de connexion :

JavaScript

connection.onreconnecting((error) => {
const status = `Connection lost due to error "${error}". Reconnecting.`;
document.getElementById("messageInput").disabled = true;
document.getElementById("sendButton").disabled = true;
document.getElementById("connectionStatus").innerText = status;
});

Le code suivant utilise onreconnected pour mettre à jour l’interface utilisateur sur la
connexion :

JavaScript

connection.onreconnected((connectionId) => {
const status = `Connection reestablished. Connected.`;
document.getElementById("messageInput").disabled = false;
document.getElementById("sendButton").disabled = false;
document.getElementById("connectionStatus").innerText = status;
});

SignalR 3.0 et versions ultérieures fournit une ressource personnalisée aux gestionnaires
d’autorisation lorsqu’une méthode hub nécessite une autorisation. La ressource est une
instance de HubInvocationContext . HubInvocationContext inclut :

HubCallerContext

Le nom de la méthode hub appelée.


Les arguments de la méthode hub.

Prenons l’exemple suivant d’une application de conversation autorisant la connexion à


plusieurs organisations via Azure Active Directory. Toute personne disposant d’un
compte Microsoft peut se connecter à la conversation, mais seuls les membres de
l’organisation propriétaire peuvent interdire les utilisateurs ou afficher les historiques de
conversation des utilisateurs. L’application peut restreindre certaines fonctionnalités
d’utilisateurs spécifiques.

C#

public class DomainRestrictedRequirement :


AuthorizationHandler<DomainRestrictedRequirement, HubInvocationContext>,
IAuthorizationRequirement
{
protected override Task
HandleRequirementAsync(AuthorizationHandlerContext context,
DomainRestrictedRequirement requirement,
HubInvocationContext resource)
{
if (context.User?.Identity?.Name == null)
{
return Task.CompletedTask;
}

if (IsUserAllowedToDoThis(resource.HubMethodName,
context.User.Identity.Name))
{
context.Succeed(requirement);
}

return Task.CompletedTask;
}

private bool IsUserAllowedToDoThis(string hubMethodName, string


currentUsername)
{
if (hubMethodName.Equals("banUser",
StringComparison.OrdinalIgnoreCase))
{
return currentUsername.Equals("bob42@jabbr.net",
StringComparison.OrdinalIgnoreCase);
}

return currentUsername.EndsWith("@jabbr.net",
StringComparison.OrdinalIgnoreCase));
}
}

Dans le code précédent, DomainRestrictedRequirement sert de


IAuthorizationRequirement personnalisé. Étant donné que le paramètre de ressource
HubInvocationContext est transmis, la logique interne peut :

Inspecter le contexte dans lequel le hub est appelé.


Prendre des décisions sur l’autorisation de l’utilisateur d’exécuter des méthodes
Hub individuelles.
Les méthodes hub individuelles peuvent être marquées avec le nom de la stratégie que
le code vérifie au moment de l’exécution. Lorsque les clients tentent d’appeler des
méthodes Hub individuelles, le gestionnaire DomainRestrictedRequirement s’exécute et
contrôle l’accès aux méthodes. En fonction de la façon dont les contrôles
DomainRestrictedRequirement accèdent :

Tous les utilisateurs connectés peuvent appeler la méthode SendMessage .


Seuls les utilisateurs qui se sont connectés avec une adresse e-mail @jabbr.net
peuvent afficher les historiques des utilisateurs.
Seul bob42@jabbr.net peut interdire les utilisateurs de la salle de conversation.

C#

[Authorize]
public class ChatHub : Hub
{
public void SendMessage(string message)
{
}

[Authorize("DomainRestricted")]
public void BanUser(string username)
{
}

[Authorize("DomainRestricted")]
public void ViewUserHistory(string username)
{
}
}

La création de la stratégie DomainRestricted peut impliquer :

Dans Startup.cs , ajout de la nouvelle stratégie.


Fournir l’exigence personnalisée DomainRestrictedRequirement en tant que
paramètre.
Inscription de DomainRestricted auprès de l’intergiciel d’autorisation.

C#

services
.AddAuthorization(options =>
{
options.AddPolicy("DomainRestricted", policy =>
{
policy.Requirements.Add(new DomainRestrictedRequirement());
});
});

Les hubs SignalR utilisent le routage des points de terminaison. La connexion hub
SignalR a été effectuée explicitement :

C#

app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("hubs/chat");
});

Dans la version précédente, les développeurs devaient connecter des contrôleurs, des
pages Razor et des hubs à divers endroits. Une connexion explicite entraîne une série de
segments de routage presque identiques :

C#

app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("hubs/chat");
});

app.UseRouting(routes =>
{
routes.MapRazorPages();
});

Les hubs SignalR 3.0 peuvent être routés via le routage du point de terminaison. Avec le
routage des points de terminaison, tous les routages peuvent généralement être
configurés dans UseRouting :

C#

app.UseRouting(routes =>
{
routes.MapRazorPages();
routes.MapHub<ChatHub>("hubs/chat");
});

SignalR ASP.NET Core 3.0 ajouté :

Flux client à serveur. Avec le flux client à serveur, les méthodes côté serveur peuvent
prendre des instances de IAsyncEnumerable<T> ou ChannelReader<T> . Dans l’exemple C#
suivant, la méthode UploadStream sur le hub reçoit un flux de chaînes du client :
C#

public async Task UploadStream(IAsyncEnumerable<string> stream)


{
await foreach (var item in stream)
{
// process content
}
}

Les applications clientes .NET peuvent passer une instance IAsyncEnumerable<T> ou


ChannelReader<T> en tant qu’argument stream de la méthode Hub UploadStream ci-

dessus.

Une fois la boucle for terminée et que la fonction locale se termine, l’achèvement du
flux est envoyé :

C#

async IAsyncEnumerable<string> clientStreamData()


{
for (var i = 0; i < 5; i++)
{
var data = await FetchSomeData();
yield return data;
}
}

await connection.SendAsync("UploadStream", clientStreamData());

Les applications clientes JavaScript utilisent SignalR Subject (ou un objet RxJS ) pour
l’argument stream de la méthode Hub UploadStream ci-dessus.

JavaScript

let subject = new signalR.Subject();


await connection.send("StartStream", "MyAsciiArtStream", subject);

Le code JavaScript peut utiliser la méthode subject.next pour gérer les chaînes à
mesure qu’elles sont capturées et prêtes à être envoyées au serveur.

JavaScript

subject.next("example");
subject.complete();
À l’aide de code comme les deux extraits précédents, des expériences de flux en temps
réel peuvent être créées.

Nouvelle sérialisation JSON


ASP.NET Core 3.0 utilise désormais System.Text.Json par défaut pour la sérialisation
JSON :

Lit et écrit JSON de manière asynchrone.


Est optimisé pour le texte UTF-8.
En général, les performances sont plus élevées que Newtonsoft.Json .

Pour ajouter Json.NET à ASP.NET Core 3.0, consultez Ajouter la prise en charge du
format JSON basé sur Newtonsoft.Json.

Nouvelles directives Razor


La liste suivante contient de nouvelles directives Razor :

@attribute: la directive @attribute applique l’attribut donné à la classe de la page


ou de la vue générée. Par exemple : @attribute [Authorize] .
@implements: la directive @implements implémente une interface pour la classe
générée. Par exemple : @implements IDisposable .

IdentityServer4 prend en charge


l’authentification et l’autorisation pour les API
web et les SPA
ASP.NET Core 3.0 offre une authentification dans Single Page Apps (SPA) à l’aide de la
prise en charge de l’autorisation de l’API web. ASP.NET Core Identity pour
l’authentification et le stockage des utilisateurs est combiné avec IdentityServer4 pour
implémenter OpenID Connect.

IdentityServer4 est une infrastructure OpenID Connect et OAuth 2.0 pour ASP.NET Core
3.0. Il active les fonctionnalités de sécurité suivantes :

Authentification en tant que service (AaaS)


Authentification/déconnexion unique (SSO) sur plusieurs types d’applications
Contrôle d’accès pour les API
Federation Gateway
Pour plus d’informations, consultez la documentation IdentityServer4 ou
Authentification et autorisation pour les SPA.

Authentification par certificat et Kerberos


L’authentification par certificat nécessite :

La configuration du serveur pour accepter des certificats.


L’ajout de l’intergiciel d’authentification dans Startup.Configure .
L’ajout du service d’authentification de certificat dans Startup.ConfigureServices .

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
// Other service configuration removed.
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
app.UseAuthentication();
// Other app configuration removed.
}

Les options d’authentification par certificat incluent la possibilité de :

Accepter les certificats auto-signés.


Vérifier la révocation des certificats.
Vérifier que le certificat présenté a les indicateurs d’utilisation appropriés.

Un principal d’utilisateur par défaut est construit à partir des propriétés du certificat. Le
principal d’utilisateur contient un événement qui permet de compléter ou de remplacer
le principal. Pour plus d’informations, consultez Configurer l’authentification par
certificat dans ASP.NET Core.

L’authentification Windows a été étendue sur Linux et macOS. Dans les versions
précédentes, l’authentification Windows était limitée à IIS et HTTP.sys. Dans ASP.NET
Core 3.0, Kestrel dispose de la possibilité d’utiliser Negotiate, Kerberoset NTLM sur
Windows, Linux et macOS pour les hôtes joints à un domaine Windows. La prise en
charge de ces schémas d’authentification par Kestrel est fournie par le package NuGet
Microsoft.AspNetCore.Authentication.Negotiate NuGet . Comme avec les autres
services d’authentification, configurez l’authentification à l’échelle de l’application, puis
configurez le service :

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
// Other service configuration removed.
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)


{
app.UseAuthentication();
// Other app configuration removed.
}

Configuration requise pour l’hôte :

Les hôtes Windows doivent avoir des noms de principal du service (SPN) ajoutés
au compte d’utilisateur hébergeant l’application.
Les machines Linux et macOS doivent être jointes au domaine.
Les SPN doivent être créés pour le processus web.
Les fichiers Keytab doivent être générés et configurés sur l’ordinateur hôte.

Pour plus d’informations, consultez Configurer l’authentification Windows par certificat


dans ASP.NET Core.

Modifications de modèle
Les éléments des modèles d’interface utilisateur web (Razor Pages, MVC avec contrôleur
et vues) suivants ont été supprimés :

L’interface utilisateur de consentement cookie n’est plus incluse. Pour activer la


fonctionnalité de consentement cookie dans une application générée par un
modèle ASP.NET Core 3.0, consultez Prise en charge du Règlement général sur la
protection des données (RGPD) dans ASP.NET Core.
Les scripts et les ressources statiques associées sont désormais référencés en tant
que fichiers locaux au lieu d’utiliser des CDN. Pour plus d’informations, consultez
Scripts et ressources statiques associées sont désormais référencés en tant que
fichiers locaux au lieu d’utiliser des CDN en fonction de l’environnement actuel
(dotnet/AspNetCore.Docs #14350) .

Le modèle Angular a été mis à jour pour utiliser Angular 8.


Le modèle de bibliothèque de classes Razor (RCL) est défini par défaut sur
développement de composants Razor. Une nouvelle option de modèle dans Visual
Studio fournit la prise en charge des modèles pour les pages et les vues. Lors de la
création d’une liste RCL à partir du modèle dans une interface de commandes, passez
l’option --support-pages-and-views ( dotnet new razorclasslib --support-pages-and-
views ).

Hôte générique
Les modèles ASP.NET Core 3.0 utilisent l’hôte générique .NET dans ASP.NET Core. Les
versions précédentes utilisaient WebHostBuilder. L’utilisation de l’hôte générique .NET
Core (HostBuilder) offre une meilleure intégration des applications ASP.NET Core avec
d’autres scénarios de serveur qui ne sont pas spécifiques au web. Pour plus
d’informations, consultez HostBuilder remplace WebHostBuilder.

Configuration de l’hôte
Avant la publication de ASP.NET Core 3.0, les variables d’environnement préfixées avec
ASPNETCORE_ étaient chargées pour la configuration de l’hôte web. Dans la version 3.0,

AddEnvironmentVariables est utilisé pour charger des variables d’environnement

précédées de DOTNET_ pour la configuration de l’hôte avec CreateDefaultBuilder .

Modifications apportées à l’injection de constructeur de


démarrage
L’hôte générique prend uniquement en charge les types suivants pour l’injection de
constructeur Startup :

IHostEnvironment
IWebHostEnvironment
IConfiguration

Tous les services peuvent toujours être injectés directement en tant qu’arguments à la
méthode Startup.Configure . Pour plus d’informations, consultez L’hôte générique limite
l’injection de constructeur de démarrage (aspnet/Announcements #353) .

Kestrel
La configuration de Kestrel a été mise à jour pour la migration vers l’hôte
générique. Dans la version 3.0, Kestrel est configuré sur le générateur d’hôtes web
fourni par ConfigureWebHostDefaults .
Les adaptateurs de connexion ont été supprimés de Kestrel et remplacés par
l’intergiciel de connexion, qui est similaire à l’intergiciel HTTP dans le pipeline
ASP.NET Core, mais pour les connexions de niveau inférieur.
La couche de transport Kestrel a été exposée en tant qu’interface publique dans
Connections.Abstractions .

L’ambiguïté entre les en-têtes et les codes de fin a été résolue en déplaçant les en-
têtes de fin vers une nouvelle collection.
Les API d’E/S synchrones, telles que HttpRequest.Body.Read , sont une source
courante de privation de threads entraînant des incidents d’applications. Dans la
version 3.0, AllowSynchronousIO est désactivé par défaut.

Pour plus d’informations, consultez Migrer de ASP.NET Core 2.2 vers 3.0.

HTTP/2 activé par défaut


HTTP/2 est activé par défaut dans Kestrel pour les points de terminaison HTTPS. La prise
en charge de HTTP/2 pour IIS ou HTTP.sys est activée lorsqu’elle est prise en charge par
le système d’exploitation.

EventCounters à la demande
L’hôte EventSource, Microsoft.AspNetCore.Hosting , émet les nouveaux types
EventCounter suivants liés aux requêtes entrantes :

requests-per-second
total-requests

current-requests

failed-requests

Routage de point de terminaison


Le routage des points de terminaison, qui permet aux frameworks (par exemple, MVC)
de fonctionner correctement avec le middleware, est amélioré :

L’ordre des intergiciels et des points de terminaison est configurable dans le


pipeline de traitement des demandes de Startup.Configure .
Les points de terminaison et les intergiciels composent bien avec d’autres
technologies ASP.NET Core, telles que les contrôles d’intégrité.
Les points de terminaison peuvent implémenter une stratégie, telle que CORS ou
autorisation, dans les intergiciels et MVC.
Les filtres et les attributs peuvent être placés sur des méthodes dans des
contrôleurs.

Pour plus d’informations, consultez Routage dans ASP.NET Core.

Contrôles d'intégrité
Les contrôles d’intégrité utilisent le routage des points de terminaison avec l’hôte
générique. Dans Startup.Configure , appelez MapHealthChecks sur le générateur de
points de terminaison avec l’URL du point de terminaison ou le chemin relatif :

C#

app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health");
});

Les points de terminaison de contrôle d’intégrité peuvent :

Spécifier un ou plusieurs hôtes/ports autorisés.


Exiger une autorisation.
Exiger CORS.

Pour plus d’informations, consultez les articles suivants :

Migrer d’ASP.NET Core 2.2 vers 3.0


Contrôles d’intégrité dans ASP.NET Core

Canaux sur HttpContext


Il est désormais possible de lire le corps de la requête et d’écrire le corps de la réponse à
l’aide de l’API System.IO.Pipelines. La propriété HttpRequest.BodyReader fournit
PipeReader qui peut être utilisé pour lire le corps de la requête. La propriété
HttpResponse.BodyWriter fournit PipeWriter qui peut être utilisé pour écrire le corps de

la réponse. HttpRequest.BodyReader est un analogue du flux HttpRequest.Body .


HttpResponse.BodyWriter est un analogue du flux HttpResponse.Body .

Amélioration du rapport d’erreurs dans IIS


Les erreurs de démarrage lors de l’hébergement d’applications ASP.NET Core dans IIS
produisent désormais des données de diagnostic plus riches. Ces erreurs sont signalées
au journal des événements Windows avec des rapports des appels de procédure, le cas
échéant. En outre, tous les avertissements, erreurs et exceptions non gérées sont
enregistrés dans le journal des événements Windows.

Kit de développement logiciel (SDK) Worker


Service and Worker
.NET Core 3.0 introduit le nouveau modèle d’application Worker Service. Ce modèle
fournit un point de départ pour l’écriture de services durables dans .NET Core.

Pour plus d'informations, consultez les pages suivantes :

Workers .NET Core en tant que services Windows


Tâches d’arrière-plan avec des services hébergés dans ASP.NET Core
Héberger ASP.NET Core dans un service Windows

Améliorations apportées aux intergiciels d’en-


têtes transférés
Dans les versions précédentes de ASP.NET Core, l’appel de UseHsts et
UseHttpsRedirection étaient problématiques lors du déploiement sur Linux Azure ou
derrière un proxy inverse autre que IIS. Le correctif pour les versions précédentes est
documenté dans Transférer le schéma pour les proxys inverses Linux et non IIS.

Ce scénario est résolu dans ASP.NET Core 3.0. L’hôte active le middleware des en-têtes
transférés lorsque la variable d’environnement ASPNETCORE_FORWARDEDHEADERS_ENABLED est
définie sur true . ASPNETCORE_FORWARDEDHEADERS_ENABLED est défini true sur nos images
conteneur.

Optimisation des performances


ASP.NET Core 3.0 inclut de nombreuses améliorations qui réduisent l’utilisation de la
mémoire et améliorent le débit :

Réduction de l’utilisation de la mémoire lors de l’utilisation du conteneur


d’injection de dépendances intégré pour les services étendus.
Réduction des répartitions dans l’infrastructure, y compris les scénarios d’intergiciel
et le routage.
Réduction de l’utilisation de la mémoire pour les connexions WebSocket.
Améliorations de la réduction de la mémoire et du débit pour les connexions
HTTPS.
Nouveau sérialiseur JSON optimisé et entièrement asynchrone.
Réduction de l’utilisation de la mémoire et des améliorations du débit dans
l’analyse de formulaire.

ASP.NET Core 3.0 s’exécute uniquement sur


.NET Core 3.0
Depuis ASP.NET Core 3.0, .NET Framework n’est plus un framework cible pris en charge.
Les projets ciblant .NET Framework peuvent continuer de manière entièrement prise en
charge à l’aide de la version LTS .NET Core 2.1 . La plupart des packages associés à
Core 2.1.x ASP.NET seront pris en charge indéfiniment, au-delà de la période LTS de trois
ans pour .NET Core 2.1.

Pour plus d’informations sur la migration, consultez Port de votre code .NET Framework
vers .NET Core.

Utiliser l’infrastructure partagée ASP.NET Core


L’infrastructure partagée ASP.NET Core 3.0, contenue dans le métapaquet
Microsoft.AspNetCore.App, ne nécessite plus d’élément explicite <PackageReference />
dans le fichier projet. L’infrastructure partagée est automatiquement référencée lors de
l’utilisation du Kit de développement logiciel (SDK) Microsoft.NET.Sdk.Web dans le
fichier projet :

XML

<Project Sdk="Microsoft.NET.Sdk.Web">

Assembly supprimés de l’infrastructure


partagée ASP.NET Core
Les assembly les plus notables supprimés de l’infrastructure partagée ASP.NET Core 3.0
sont les suivants :

Newtonsoft.Json (Json.NET). Pour ajouter Json.NET à ASP.NET Core 3.0, consultez


Ajouter la prise en charge du format JSON basé sur Newtonsoft.Json. ASP.NET
Core 3.0 introduit pour System.Text.Json la lecture et l’écriture JSON. Pour plus
d’informations, consultez Nouvelle sérialisation JSON dans ce document.
Entity Framework Core

Pour obtenir la liste complète des assembly supprimés de l’infrastructure partagée,


consultez Assembly supprimés de Microsoft.AspNetCore.App 3.0 . Pour plus
d’informations sur la motivation de ce changement, consultez Changements cassants de
Microsoft.AspNetCore.App dans la version 3.0 et Premier aperçu des modifications
apportées à ASP.NET Core 3.0 .

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Nouveautés d’ASP.NET Core 2.2
Article • 30/11/2023

Cet article met en évidence les modifications les plus importantes dans ASP.NET Core
2.2 et fournit des liens vers la documentation appropriée.

Analyseurs et conventions d’OpenAPI


OpenAPI (anciennement appelé Swagger) est une spécification indépendante du
langage permettant de décrire les API REST. L’écosystème OpenAPI a des outils qui
permettent de découvrir, de tester et de produire du code client avec la spécification. La
prise en charge de la génération et de la visualisation de documents OpenAPI dans
ASP.NET Core MVC est fournie par le biais de projets portés par la communauté, comme
NSwag et Swashbuckle.AspNetCore . ASP.NET Core 2.2 fournit des outils et des
expériences de runtime améliorés pour la création de documents OpenAPI.

Pour plus d’informations, consultez les ressources suivantes :

Utiliser les analyseurs d’API web


Utiliser les conventions d’API web
ASP.NET Core 2.2.0-preview1 : Analyseurs et conventions d’OpenAPI

Prise en charge des détails des problèmes


ASP.NET Core 2.1 a introduit ProblemDetails , basé sur la spécification RFC 7807 pour
convoyer les détails d’une erreur avec une réponse HTTP. Dans la version 2.2,
ProblemDetails est la réponse standard pour les codes d’erreur des clients dans les

contrôleurs avec l’attribut ApiControllerAttribute . Un IActionResult retournant un


code d’état d’erreur du client (4xx) retourne maintenant un corps ProblemDetails . Le
résultat inclut également un ID de corrélation, qui peut être utilisé pour mettre en
corrélation l’erreur en utilisant les journaux des requêtes. Pour les erreurs des clients,
ProducesResponseType utilise par défaut ProblemDetails comme type de réponse. Cela

est documenté dans la sortie OpenAPI / Swagger générée avec NSwag ou


Swashbuckle.AspNetCore.

Routage de point de terminaison


ASP.NET Core 2.2 utilise un nouveau système de routage de point de terminaison pour
améliorer la distribution des requêtes. Les changements sont notamment de nouveaux
membres d’API de génération de liens et les transformateurs de paramètres de route.

Pour plus d’informations, consultez les ressources suivantes :

Routage de point de terminaison dans la version 2.2


Transformateurs de paramètres de route (consultez la section Routage)
Différences entre le routage IRouter et le routage de point de terminaison

Contrôles d'intégrité
Un nouveau service de contrôles d’intégrité facilite l’utilisation d’ASP.NET Core dans les
environnements qui nécessitent des contrôles d’intégrité, comme Kubernetes. Les
contrôles d’intégrité incluent un middleware (intergiciel), et un ensemble de
bibliothèques qui définissent une abstraction et un service IHealthCheck .

Les contrôles d’intégrité sont utilisés par un orchestrateur de conteneurs ou un


équilibreur de charge pour déterminer rapidement si un système répond normalement
aux requêtes. Un orchestrateur de conteneurs peut répondre à un contrôle d’intégrité
en échec en arrêtant un déploiement en cours ou en redémarrant un conteneur. Un
équilibreur de charge peut répondre à un contrôle d’intégrité en routant le trafic en
dehors d’une instance du service en échec.

Les contrôles d’intégrité sont exposés par une application comme point de terminaison
HTTP utilisé par les systèmes de surveillance. Les contrôles d’intégrité peuvent être
configurés pour une grande variété de scénarios de surveillance en temps réel et de
systèmes de surveillance. Le service de contrôles d’intégrité s’intègre au projet
BeatPulse , ce qui facilite l’ajout de contrôles pour des dizaines de systèmes et de
dépendances les plus courants.

Pour plus d’informations, consultez Contrôles d’intégrité dans ASP.NET Core.

HTTP/2 dans Kestrel


ASP.NET Core 2.2 ajoute la prise en charge de HTTP/2.

HTTP/2 est une révision majeure du protocole HTTP. Les fonctionnalités notables de
HTTP/2 sont les suivantes :

Prise en charge de la compression d’en-tête.


Flux entièrement multiplexés sur une connexion individuelle.

Bien que HTTP/2 conserve la sémantique de HTTP (par exemple, les en-têtes et les
méthodes HTTP), il s’agit d’un changement cassant par rapport à HTTP/1.x sur la façon
dont les données sont encadrées et envoyées entre le client et le serveur.

En raison de ce changement de tramage, les serveurs et les clients doivent négocier la


version du protocole utilisée. APLN (Application-Layer Protocol Negotiation) est une
extension de TLS qui permet au serveur et au client de négocier la version du protocole
utilisé dans le cadre de leur négociation TLS. Bien que le serveur et le client puissent
partager une connaissance préalable quant au protocole, tous les principaux navigateurs
prennent en charge ALPN comme unique moyen d’établir une connexion HTTP/2.

Pour plus d’informations, consultez Prise en charge de HTTP/2.

Configuration Kestrel
Dans les versions antérieures d’ASP.NET Core, les options de Kestrel sont configurées en
appelant UseKestrel . Dans la version 2.2, les options de Kestrel sont configurées en
appelant ConfigureKestrel sur le générateur d’hôte. Cette modification résout un
problème quant à l’ordre des inscriptions IServer pour l’hébergement in-process. Pour
plus d’informations, consultez les ressources suivantes :

Réduire les conflits liés à UseIIS


Configurer les options de serveur Kestrel avec ConfigureKestrel

Hébergement in-process d’IIS


Dans les versions antérieures d’ASP.NET Core, IIS sert de proxy inverse. Dans la version
2.2, le module ASP.NET Core peut démarrer CoreCLR et héberger une application au
sein du processus worker IIS (w3wp.exe). L’hébergement in-process offre des gains en
matière de performances et de diagnostic lors de l’exécution avec IIS.

Pour plus d’informations, consultez Hébergement in-process pour IIS.

Client Java SignalR


ASP.NET Core 2.2 introduit un client Java pour SignalR. Ce client prend en charge la
connexion à un serveur ASP.NET Core SignalR à partir de code Java, notamment dans
des applications Android.

Pour plus d’informations, consultez Client Java ASP.NET Core SignalR.

Améliorations apportées à CORS


Dans les versions antérieures d’ASP.NET Core, le middleware (intergiciel) CORS permet
l’envoi des en-têtes Accept , Accept-Language , Content-Language et Origin quelles que
soient les valeurs configurées dans CorsPolicy.Headers . Dans la version 2.2, une
correspondance de la stratégie de middleware CORS est possible seulement quand les
en-têtes envoyés dans Access-Control-Request-Headers correspondent exactement aux
en-têtes indiqués dans WithHeaders .

Pour plus d’informations, consultez Middleware CORS.

Compression des réponses


ASP.NET Core 2.2 peut compresser les réponses avec le format de compression Brotli .

Pour plus d’informations, consultez Prise en charge de la compression Brotli par le


middleware de compression des réponses.

Modèles de projet
Les modèles de projet web ASP.NET Core ont été mis à jour vers Bootstrap 4 et
Angular 6 . La nouvelle apparence est visuellement plus simple et facilite la
visualisation des structures importantes de l’application.

Performances de la validation
Le système de validation de MVC est conçu pour être extensible et flexible, vous
permettant de déterminer pour chaque requête les validateurs à appliquer à un modèle
donné. Ceci convient bien à la création de fournisseurs de validation complexe.
Cependant, dans la plupart des cas, une application utilise seulement les validateurs
intégrés et ne nécessite pas cette flexibilité supplémentaire. Les validateurs intégrés
incluent des DataAnnotations comme [Required] et [StringLength], et
IValidatableObject .

Dans ASP.NET Core 2.2, MVC peut court-circuiter la validation s’il détermine que le
graphe d’un modèle donné ne nécessite pas de validation. Ignorer les résultats de la
validation permet des améliorations significatives lors de la validation de modèles qui ne
peuvent pas avoir ou qui n’ont pas de validateurs. Ceci inclut des objets comme les
collections de primitifs ( byte[] , string[] , Dictionary<string, string> , etc.) ou des
graphes d’objets complexes sans beaucoup de validateurs.

Performances du client HTTP


Dans ASP.NET Core 2.2, les performances de SocketsHttpHandler ont été améliorées en
réduisant la contention du verrouillage des pools de connexions. Pour les applications
qui font de nombreuses requêtes HTTP sortantes, comme certaines architectures de
microservices, le débit est amélioré. Sous une charge intensive, le débit de HttpClient
peut être amélioré d’un facteur allant jusqu’à 60 % sur Linux et jusqu’à 20 % sur
Windows.

Pour plus d’informations, consultez la demande de tirage à l’origine de cette


amélioration .

Informations supplémentaires
Pour obtenir la liste complète des modifications, consultez les Notes de publication
d’ASP.NET Core 2.2 .

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.
 Indiquer des commentaires sur
le produit
Nouveautés d’ASP.NET Core 2.1
Article • 30/11/2023

Cet article met en évidence les modifications les plus importantes dans ASP.NET 2.1
Core et fournit des liens vers la documentation appropriée.

SignalR
SignalR a été réécrit pour ASP.NET Core 2.1.

ASP.NET Core SignalR comprend plusieurs améliorations :

Un modèle simplifié de montée en puissance parallèle.


Un nouveau client JavaScript sans dépendance de jQuery.
Un nouveau protocole binaire compact basé sur MessagePack.
Prise en charge des protocoles personnalisés.
Un nouveau modèle réponse de streaming.
Prise en charge des clients basés sur des WebSocket nus.

Pour plus d’informations, consultez ASP.NET Core SignalR.

Bibliothèques de classes Razor


Avec ASP.NET Core 2.1, il est plus facile de générer une interface utilisateur basée sur
Razor, de l’inclure dans une bibliothèque et de la partager entre plusieurs projets. Le
nouveau SDK Razor permet de générer des fichiers Razor dans un projet de
bibliothèque de classes qui peut être placé dans un package NuGet. Les vues et les
pages dans les bibliothèques sont automatiquement découvertes et peuvent être
remplacées par l’application. Grâce à l’intégration de la compilation Razor dans la build :

Le temps de démarrage de l’application est nettement plus rapide.


Les mises à jour rapides des pages et vues Razor au moment de l’exécution sont
toujours disponibles dans le cadre d’un flux de travail de développement itératif.

Pour plus d’informations, consultez Créer une interface utilisateur réutilisable à l’aide du
projet Class LibraryRazor.

Bibliothèque de l’interface utilisateur Identity


et génération de modèles automatique &
ASP.NET Core 2.1 fournit ASP.NET Core Identity en tant que Razorbibliothèque de
classes . Les applications qui incluent le Identity peuvent appliquer le nouveau
générateur de modèles automatique de Identity de manière sélective pour ajouter le
code source contenu dans la bibliothèque de classes IdentityRazor (RCL). Vous pouvez
souhaiter générer le code source afin de pouvoir modifier le code et changer le
comportement. Par exemple, vous pouvez demander au générateur de modèles
automatique de générer le code utilisé dans l’inscription. Le code généré est prioritaire
sur le même code dans la bibliothèque de classes Identityd’identité.

Les applications qui n’incluent pas l’authentification peuvent appliquer le générateur de


modèles automatique de Identity pour ajouter le package de Identity de la bibliothèque
de classes. Vous pouvez sélectionner le code de Identity à générer.

Pour plus d’informations, consultez Génération de modèles automatiqueIdentity dans


les projets ASP.NET Core.

HTTPS
L’importance croissante accordée à la sécurité et à la confidentialité justifie l’activation
du protocole HTTPS pour les applications web. La mise en œuvre du protocole HTTPS
devient de plus en plus stricte sur le web. Les sites qui n’utilisent pas le protocole HTTPS
sont considérés comme non sécurisés. Les navigateurs (Chrome, Mozilla) commencent à
imposer l’utilisation des fonctionnalités web dans un contexte sécurisé. Le RGPD exige
l’utilisation du protocole HTTPS pour protéger la confidentialité des utilisateurs.
L’utilisation du protocole HTTPS en production est critique et son utilisation en
développement peut aider à éviter les problèmes liés au déploiement (tels que les liens
non sécurisés). ASP.NET Core 2.1 inclut un certain nombre d’améliorations qui facilitent
l’utilisation du protocole HTTPS pendant le développement et sa configuration en
production. Pour plus d’informations, consultez Appliquer le protocole HTTPS.

Activé par défaut


Pour faciliter le développement de sites web sécurisés, le protocole HTTPS est
maintenant activé par défaut. À compter de la version 2.1, Kestrel écoute sur
https://localhost:5001 quand un certificat de développement local est présent. Un

certificat de développement est créé :

Dans le cadre de la première exécution du kit SDK .NET Core, quand vous utilisez
celui-ci pour la première fois.
Manuellement à l’aide du nouvel outil dev-certs .
Exécutez dotnet dev-certs https --trust pour approuver le certificat.

Redirection et application du protocole HTTPS


En règle générale, les applications web doivent écouter sur les protocoles HTTP et
HTTPS, mais ensuite rediriger tout le trafic HTTP vers HTTPS. Dans la version 2.1 a été
introduit le middleware (intergiciel) de redirection HTTPS spécialisé, qui redirige
intelligemment en fonction de la présence de ports de serveur lié ou de configuration.

Vous pouvez renforcer l’utilisation du protocole HTTPS en utilisant le protocole HSTS


(HTTP Strict Transport Security Protocol). Le protocole HSTS indique aux navigateurs de
toujours accéder au site via HTTPS. ASP.NET Core 2.1 ajoute un middleware HSTS qui
prend en charge des options pour l’âge maximal, les sous-domaines et la liste de
préchargement HSTS.

Configuration pour la production


En production, HTTPS doit être explicitement configuré. Dans la version 2.1, le schéma
de configuration par défaut pour la configuration HTTPS pour Kestrel a été ajouté. Les
applications peuvent être configurées pour utiliser :

Plusieurs points de terminaison, y compris les URL. Pour plus d’informations,


consultez Implémentation du serveur web Kestrel : configuration de point de
terminaison.
Le certificat à utiliser pour le protocole HTTPS à partir d’un fichier sur disque ou
d’un magasin de certificats.

RGPD
ASP.NET Core fournit des API et des modèles qui aident à satisfaire à certaines des
exigences du Règlement général sur la protection des données (RGPD) . Pour plus
d’informations, consultez Prise en charge du RGPD dans ASP.NET Core. Un exemple
d’application montre comment utiliser et tester la plupart des API et points
d’extension RGPD ajoutés aux modèles ASP.NET Core 2.1.

Tests d’intégration
Un nouveau package est introduit qui simplifie la création et l’exécution de tests. Le
package Microsoft.AspNetCore.Mvc.Testing gère les tâches suivantes :
Il copie le fichier de dépendance (*.deps) à partir de l’application testée dans le
dossier bin du projet de test.
Il définit la racine du contenu sur la racine du projet de l’application testée afin que
soient trouvés les pages/vues et fichiers statiques quand les tests sont exécutés.
Il fournit la classe WebApplicationFactory<TEntryPoint> afin de simplifier
l’amorçage de l’application testée avec TestServer.

Le test suivant utilise xUnit pour vérifier que la page Index se charge avec un code
d’état de réussite et avec l’en-tête Content-Type correct :

C#

public class BasicTests


: IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly HttpClient _client;

public BasicTests(WebApplicationFactory<RazorPagesProject.Startup>
factory)
{
_client = factory.CreateClient();
}

[Fact]
public async Task GetHomePage()
{
// Act
var response = await _client.GetAsync("/");

// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}

Pour plus d’informations, consultez la rubrique Tests d’intégration.

[ApiController], ActionResult<T>
ASP.NET Core 2.1 ajoute de nouvelles conventions de programmation qui facilitent la
génération d’API web propres et descriptives. ActionResult<T> est un nouveau type qui
permet à une application de retourner soit un type de réponse, soit tout autre résultat
d’action (à l’image d’IActionResult) tout en indiquant toujours le type de réponse.
L’attribut [ApiController] a également été ajouté comme moyen d’accepter des
comportements et des conventions propres aux API web.
Pour plus d’informations, consultez Créer des API web avec ASP.NET Core.

IHttpClientFactory
ASP.NET Core 2.1 inclut un nouveau service IHttpClientFactory qui facilite la
configuration et l’utilisation d’instances de HttpClient dans les applications. HttpClient
intègre déjà le concept de délégation des gestionnaires qui pourraient être liés
ensemble pour les requêtes HTTP sortantes. La fabrique :

Rend plus intuitive l’inscription des instances de HttpClient par client nommé.
Implémente un gestionnaire Polly qui permet d’utiliser des stratégies Polly pour
des fonctionnalités telles que Retry (nouvelle tentative) ou CircuitBreaker
(disjoncteur).

Pour plus d’informations, consultez Lancer des requêtes HTTP.

Configuration du transport libuv Kestrel


Dans ASP.NET Core 2.1, le transport par défaut de Kestrel n’est plus basé sur Libuv, mais
sur des sockets managés. Pour plus d’informations, consultez Implémentation du
serveur web Kestrel : configuration du transport.

Générateur d’hôte générique


Le générateur d’hôte générique ( HostBuilder ) a été introduit. Ce générateur peut être
utilisé pour les applications qui ne traitent pas les requêtes HTTP (messagerie, les tâches
en arrière-plan, etc.).

Pour plus d’informations, consultez Hôte générique .NET.

Modèles SPA mis à jour


Les modèles d’applications monopages pour Angular et React sont mis à jour pour
utiliser les systèmes de génération et les structures de projet standard pour chaque
infrastructure.

Le modèle Angular est basé sur l’interface CLI Angular, tandis que le modèle React est
basé sur create-react-app.

Pour plus d'informations, consultez les pages suivantes :


Utiliser Angular avec ASP.NET Core
Utiliser React avec ASP.NET Core

Pages Razor de recherche de ressources Razor


Dans la version 2.1, Pages Razor recherche les ressources Razor (par exemple, les
dispositions et pages partielles) dans les répertoires suivants dans l’ordre indiqué :

1. Dossier Pages en cours.


2. /Pages/Shared/
3. /Views/Shared/

Pages Razor dans une zone


Pages Razor prend désormais en charge les zones. Pour obtenir un exemple de zones,
créez une application web Pages Razor avec des comptes d’utilisateur individuels. Une
application web Pages Razor avec des comptes d’utilisateur individuels inclut
/Zones/Identity/Pages.

Version de compatibilité MVC


La méthode SetCompatibilityVersion permet à une application d’accepter ou de refuser
les changements de comportement potentiellement cassants introduits dans ASP.NET
Core MVC 2.1 ou version ultérieure.

Pour plus d’informations, consultez Version de compatibilité pour ASP.NET Core MVC.

Migrer depuis la version 2.0 vers la version 2.1


Consultez Migrer depuis ASP.NET Core 2.0 vers 2.1.

Informations supplémentaires
Pour obtenir la liste complète des modifications, consultez les Notes de publication
d’ASP.NET Core 2.1 .

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
project. Select a link to provide
La source de ce contenu se feedback:
trouve sur GitHub, où vous
pouvez également créer et  Ouvrir un problème de
examiner les problèmes et les
documentation
demandes de tirage. Pour plus
d’informations, consultez notre
 Indiquer des commentaires sur
guide du contributeur.
le produit
Nouveautés d’ASP.NET Core 2.0
Article • 30/11/2023

Cet article met en évidence les modifications les plus importantes dans ASP.NET 2.0
Core et fournit des liens vers la documentation appropriée.

Razor Pages
Les pages Razor sont une nouvelle fonctionnalité d’ASP.NET Core MVC qui permet de
développer des scénarios orientés page de façon plus simple et plus productive.

Pour plus d’informations, consultez l’introduction et le didacticiel :

Présentation des pages Razor


Prise en main des pages Razor

Métapackage ASP.NET Core


Un nouveau métapackage ASP.NET Core comprend tous les packages créés et pris en
charge par les équipes ASP.NET Core et Entity Framework Core, ainsi que leurs
dépendances internes et tierces. Vous n’avez plus besoin de choisir des fonctionnalités
ASP.NET Core individuelles par package. Toutes les fonctionnalités sont incluses dans le
package Microsoft.AspNetCore.All . Les modèles par défaut utilisent ce package.

Pour plus d’informations, consultez Métapackage Microsoft.AspNetCore.All pour


ASP.NET Core 2.0.

Magasin Runtime
Les applications qui utilisent le métapackage Microsoft.AspNetCore.All tirent
automatiquement parti du nouveau magasin Runtime de .NET Core. Ce magasin
contient toutes les ressources Runtime nécessaires à l’exécution des applications
ASP.NET Core 2.0. Quand vous utilisez le métapackage Microsoft.AspNetCore.All ,
aucune des ressources des packages NuGet ASP.NET Core référencés ne sont déployées
avec l’application, car elles se trouvent déjà sur le système cible. Les ressources dans le
magasin Runtime sont également précompilées afin d’améliorer la vitesse de démarrage
des applications.

Pour plus d’informations, consultez Magasin de packages Runtime


.NET Standard 2.0
Les packages ASP.NET Core 2.0 ciblent .NET Standard 2.0. Les packages peuvent être
référencés par d’autres bibliothèques .NET Standard 2.0, et ils peuvent s’exécuter sur des
implémentations de .NET conformes à .NET Standard 2.0, notamment .NET Core 2.0 et le
.NET Framework 4.6.1.

Le métapackage Microsoft.AspNetCore.All cible .NET Core 2.0 uniquement, car il est


destiné à être utilisé avec .NET Core 2.0 Runtime Store.

Mise à jour de la configuration


Une instance de IConfiguration est ajoutée au conteneur de services par défaut dans
ASP.NET Core 2.0. Quand IConfiguration est dans le conteneur de services, il est plus
facile pour les applications de récupérer des valeurs de configuration à partir du
conteneur.

Pour plus d’informations sur l’état de la documentation planifiée, consultez le problème


GitHub .

Mise à jour de la journalisation


Dans ASP.NET Core 2.0, la journalisation est incorporée dans le système d’injection de
dépendance par défaut. Vous pouvez ajouter des fournisseurs et configurer le filtrage
dans le fichier Program.cs plutôt que dans le fichier Startup.cs . De plus, le
ILoggerFactory par défaut prend en charge le filtrage d’une manière qui vous permet

d’adopter une approche flexible pour le filtrage entre fournisseurs et le filtrage propre
au fournisseur.

Pour plus d’informations, consultez Introduction à la journalisation.

Mise à jour de l’authentification


Un nouveau modèle d’authentification simplifie la configuration de l’authentification
pour une application à l’aide de l’injection de dépendance.

De nouveaux modèles sont disponibles pour configurer l’authentification pour les


applications web et les API web à l’aide de Azure AD B2C .

Pour plus d’informations sur l’état de la documentation planifiée, consultez le problème


GitHub .
Identity update
Nous avons simplifié la génération d’API web sécurisées à l’aide de Identity dans
ASP.NET Core 2.0. Vous pouvez acquérir des jetons d’accès pour accéder à vos API web
à l’aide de la bibliothèque d’authentification Microsoft (MSAL, Microsoft Authentication
Library) .

Pour plus d’informations sur les modifications apportées à l’authentification dans la


version 2.0, consultez les ressources suivantes :

Account confirmation and password recovery in ASP.NET Core (Confirmation de


compte et récupération de mot de passe dans ASP.NET Core)
Activer la génération de code QR pour les applications d’authentification dans
ASP.NET Core
Migrer l’authentification et Identity vers ASP.NET Core 2.0

Modèles SPA
Des modèles de projet SPA (Single Page Application) pour Angular, Aurelia, Knockout.js,
React.js et React.js avec Redux sont disponibles. Le modèle Angular a été mis à jour vers
Angular 4. Les modèles Angular et React sont disponibles par défaut. Pour plus
d’informations sur l’obtention des autres modèles, consultez Créer un projet SPA. Pour
plus d’informations sur la création d’une SPA dans ASP.NET Core, consultez Les
fonctionnalités décrites dans cet article sont obsolètes à partir de ASP.NET Core 3.0.

Améliorations Kestrel
Le serveur web Kestrel offre de nouvelles fonctionnalités qui le rendent plus adapté en
tant que serveur connecté à Internet. Plusieurs options de configuration de contrainte
de serveur ont été ajoutées à la nouvelle propriété Limits de la classe
KestrelServerOptions . Ajoutez des limites pour les éléments suivants :

Nombre maximale de connexions client


Taille maximale du corps de la requête
Débit données minimal du corps de la requête

Pour plus d’informations, consultez Kestrel Implémentations du serveur web dans


ASP.NET Core.

WebListener a été renommé HTTP.sys


Les packages Microsoft.AspNetCore.Server.WebListener et Microsoft.Net.Http.Server
ont été fusionnés dans un nouveau package Microsoft.AspNetCore.Server.HttpSys . Les
espaces de noms ont été mis à jour en conséquence.

Pour plus d’informations, consultez Implémentation du serveur web HTTP.sys dans


ASP.NET Core.

Prise en charge améliorée de l’en-tête HTTP


Lors de l’utilisation de MVC pour transmettre un FileStreamResult ou un
FileContentResult , vous pouvez maintenant définir un ETag ou une date LastModified

sur le contenu que vous transmettez. Vous pouvez définir ces valeurs sur le contenu
retourné avec du code semblable au suivant :

C#

var data = Encoding.UTF8.GetBytes("This is a sample text from a binary


array");
var entityTag = new EntityTagHeaderValue("\"MyCalculatedEtagValue\"");
return File(data, "text/plain", "downloadName.txt", lastModified:
DateTime.UtcNow.AddSeconds(-5), entityTag: entityTag);

Le fichier retourné aux visiteurs a les en-têtes HTTP appropriés pour les valeurs ETag et
LastModified .

Si un visiteur de l’application demande du contenu avec un en-tête de requête de plage,


ASP.NET Core reconnaît la requête et gère l’en-tête. Si le contenu demandé peut être
remis partiellement, ASP.NET Core retourne uniquement le jeu d’octets demandé. Vous
n’avez pas besoin d’écrire des gestionnaires spéciaux dans vos méthodes pour adapter
ou gérer cette fonctionnalité. Elle est gérée automatiquement pour vous.

Hébergement du démarrage et Application


Insights
Les environnements d’hébergement peuvent désormais injecter des dépendances de
packages supplémentaires et exécuter du code pendant le démarrage de l’application,
sans que celle-ci doive explicitement prendre une dépendance ou appeler des
méthodes. Vous pouvez utiliser cette fonctionnalité pour permettre à certains
environnements de « déclencher » des fonctionnalités qui leur sont uniques, sans que
l’application ait besoin de savoir à l’avance.
Dans ASP.NET Core 2.0, cette fonctionnalité est utilisée pour activer automatiquement
les diagnostics Application Insights lors du débogage dans Visual Studio et (après vous
être inscrit) lors de l’exécution dans Azure App Services. Par conséquent, les modèles de
projet n’ajoutent plus de code et de packages Application Insights par défaut.

Pour plus d’informations sur l’état de la documentation planifiée, consultez le problème


GitHub .

Utilisation automatique des jetons anti-


contrefaçon
ASP.NET Core a toujours facilité l’encodage HTML du contenu par défaut, mais la
nouvelle version aide encore davantage à prévenir les attaques par falsification de
requête intersites (XSRF). ASP.NET Core émet désormais des jetons anti-contrefaçon par
défaut, et les valide sur les pages et les actions POST de formulaire sans configuration
supplémentaire.

Pour plus d’informations, consultez Prévenir les attaques par falsification de requête
intersites (XSRF/CSRF) dans ASP.NET Core.

Précompilation automatique
La précompilation de vue Razor est activée par défaut pendant la publication, ce qui
réduit la taille de sortie de publication et la durée de démarrage de l’application.

Pour plus d’informations, consultez Précompilation et compilation de vues Razor dans


ASP.NET Core.

Razor prend en charge C# 7.1


Le moteur de vue Razor a été mis à jour pour fonctionner avec le nouveau compilateur
Roslyn. Cela comprend la prise en charge des fonctionnalités de C# 7.1 telles que les
expressions par défaut, la déduction des noms de tuples et les critères spéciaux avec les
génériques. Pour utiliser C# 7.1 dans votre projet, ajoutez la propriété suivante dans
votre fichier projet, puis rechargez la solution :

XML

<LangVersion>latest</LangVersion>
Pour plus d’informations sur l’état des fonctionnalités de C# 7.1, consultez le dépôt
GitHub de Roslyn .

Autres mises à jour de la documentation pour


la version 2.0
Profils de publication Visual Studio pour le déploiement d’applications ASP.NET
Core
Gestion de clés
Configurer l’authentification Facebook
Configurer l’authentification Twitter
Configurer l’authentification Google
Configurer l’authentification de compte Microsoft

Recommandations en matière de migration


Pour obtenir des conseils sur la migration d’applications ASP.NET Core 1.x vers ASP.NET
Core 2.0, consultez les ressources suivantes :

Migrer d’ASP.NET Core 1.x vers ASP.NET Core 2.0


Migrer l’authentification et Identity vers ASP.NET Core 2.0

Informations supplémentaires
Pour obtenir la liste complète des modifications, consultez les Notes de publication
d’ASP.NET Core 2.0 .

Pour être tenu au courant de la progression et des plans de l’équipe de développement


ASP.NET Core, participez à la réunion de la communauté ASP.NET .

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.
 Indiquer des commentaires sur
le produit
Nouveautés d’ASP.NET Core 1.1
Article • 30/11/2023

ASP.NET Core 1.1 inclut les nouvelles fonctionnalités suivantes :

Intergiciel de réécriture d’URL


Intergiciel de mise en cache des réponses
Afficher les composants sous forme de Tag Helpers
Intergiciel en tant que filtres MVC
Fournisseur TempData basé sur Cookie
Fournisseur de journalisation Azure App Service
Fournisseur de configuration Azure Key Vault
Référentiels des clés de protection des données du Stockage Azure et Redis
Serveur WebListener pour Windows
Prise en charge des WebSockets

Choix entre les versions 1.0 et 1.1 d’ASP.NET


Core
ASP.NET Core 1.1 a plus de fonctionnalités qu’ASP.NET Core 1.0. D’une façon générale,
nous vous recommandons d’utiliser la version la plus récente.

Informations supplémentaires
Notes de publication d’ASP.NET Core 1.1.0
Pour être tenu au courant de la progression et des plans de l’équipe de
développement ASP.NET Core, participez à la réunion de la communauté
ASP.NET .

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.
 Indiquer des commentaires sur
le produit
Documentation ASP.NET Core -
Nouveautés
Bienvenue dans les nouveautés de la documentation ASP.NET Core. Utilisez cette page
pour trouver rapidement les dernières modifications.

Rechercher les mises à jour de la documentation ASP.NET Core

h NOUVEAUTÉS

Janvier 2024

Décembre 2023

Novembre 2023

Octobre 2023

Septembre 2023

Août 2023

Soyez impliqué : contribuez à la documentation ASP.NET Core

e VUE D’ENSEMBLE

Référentiel de la documentation ASP.NET Core

Structure et étiquettes de projet pour les problèmes et les demandes de tirage

p CONCEPT

Guide du contributeur

Guide du contributeur à la documentation ASP.NET Core

Guide du contributeur à la documentation de référence des API ASP.NET Core

Communauté

h NOUVEAUTÉS
Communauté

Pages des nouveautés associées

h NOUVEAUTÉS

Mises à jour de la documentation Xamarin

Notes de publication de .NET Core

Notes de publication d’ASP.NET Core

Notes de publication du compilateur C# (Roslyn)

Notes de mise à jour de Visual Studio

Notes de publication de Visual Studio pour Mac

Notes de mise à jour de Visual Studio Code


Choisir une interface utilisateur web
ASP.NET Core
Article • 06/12/2023

ASP.NET Core est un framework d’interface utilisateur complet. Choisissez les


fonctionnalités à combiner en fonction des besoins d’interface utilisateur web de
l’application.

ASP.NET Core Blazor


Blazor est un framework d’interface utilisateur web complet et est recommandé pour la
plupart des scénarios d’interface utilisateur web.

Avantages de l’utilisation de Blazor :

Modèle de composants réutilisables.


Rendu efficace des composants basés sur les différences.
Rendu flexible des composants depuis le serveur ou le client via WebAssembly.
Construire des composants d’interface utilisateur web interactifs et riches en C#.
Rendre des composants de façon statique depuis le serveur.
Améliorer progressivement les composants rendus du serveur pour faciliter la
navigation et la gestion des formulaires, et pour permettre le rendu en streaming.
Partager du code pour une logique commune sur le client et sur le serveur.
Interopérer avec JavaScript.
Intégrer des composants à des applications MVC, Razor Pages ou JavaScript
existantes.

Pour une présentation complète de Blazor, de son architecture et de ses avantages,


consultez Blazor ASP.NET Core et Modèles d’hébergement Blazor ASP.NET Core. Pour
commencer votre première application Blazor, consultez Créer votre première
application Blazor .

ASP.NET Core Razor Pages


Razor Pages est un modèle basé sur des pages pour la création d’interfaces utilisateur
web rendue par le serveur. L’interface utilisateur des pages Razor sont rendues
dynamiquement sur le serveur pour générer le code HTML et CSS de la page en réponse
à une demande du navigateur. La page arrive sur le client prête à s’afficher. La prise en
charge de Razor Pages repose sur ASP.NET Core MVC.
Avantages de Razor Pages :

Création et mise à jour rapides d’interface utilisateur. Le code de la page est


intégré à la page, tandis que les préoccupations d’interface utilisateur et de
logique métier sont gérées séparément.
Possibilité de tests et s’adapte aux applications volumineuses.
Organisation des pages ASP.NET Core plus simple qu’avec ASP.NET MVC :
La logique propre à l’affichage et les modèles d’affichage peuvent être
maintenus ensemble dans leur propre espace de noms et répertoire.
Les groupes de pages associées peuvent être conservés dans leur propre espace
de noms et répertoire.

Pour vous lancer dans votre première application ASP.NET Core Razor Pages, consultez
Tutoriel : Bien démarrer avec Razor Pages dans ASP.NET Core. Pour obtenir une
présentation complète d’ASP.NET Core Razor Pages, de son architecture et de ses
avantages, consultez Introduction à Razor Pages dans ASP.NET Core.

ASP.NET Core MVC


ASP.NET MVC rend l’interface utilisateur sur le serveur et utilise un modèle
d’architecture MVC (Modèle-Vue-Contrôleur). Le modèle MVC divise une application en
trois groupes de composants principaux : les modèles, les vues et les contrôleurs. Les
demandes utilisateur sont routées vers un contrôleur. Le contrôleur est chargé de
travailler avec le modèle pour effectuer des actions utilisateur ou récupérer les résultats
des requêtes. Le contrôleur choisit la vue à afficher à l’utilisateur et lui fournit les
données du modèle dont elle a besoin.

Avantages d’ASP.NET Core MVC :

Repose sur un modèle scalable et mature permettant de créer des applications


web de grande envergure.
Nette séparation des préoccupations pour une flexibilité maximale.
La séparation des responsabilités MVC est l’assurance que le modèle métier peut
évoluer sans être étroitement couplé aux détails d’implémentation de bas niveau.

Pour commencer avec ASP.NET Core MVC, consultez Bien démarrer avec ASP.NET Core
MVC. Pour obtenir une vue d’ensemble de l’architecture d’ASP.NET Core MVC et de ses
avantages, consultez Vue d’ensemble d’ASP.NET Core MVC.

Application monopage (SPA) ASP.NET Core


avec des frameworks JavaScript front-end
Créer une logique côté client pour des applications ASP.NET Core en utilisant des
frameworks JavaScript courants, comme Angular , React et Vue . ASP.NET Core
fournit des modèles de projet pour Angular, React et Vue, et il peut aussi être utilisé
avec d’autres frameworks JavaScript.

Avantages de l’application monopage (SPA) ASP.NET Core avec des frameworks


JavaScript, en plus des avantages de rendu client cités précédemment :

L’environnement d’exécution JavaScript accompagne déjà le navigateur.


Communauté importante et écosystème mature.
Créer une logique côté client pour des applications ASP.NET Core en utilisant des
frameworks JS courants, comme Angular, React et Vue.

Inconvénients :

Davantage de langages de codage, de frameworks et d’outils nécessaires.


Partage de code difficile en vue de dupliquer une partie de la logique.

Pour commencer, consultez :

Créer une application ASP.NET Core avec Angular


Créer une application ASP.NET Core avec React
Créer une application ASP.NET Core avec Vue
JavaScript et TypeScript dans Visual Studio

Choisir une solution hybride : ASP.NET Core


MVC ou Razor Pages plus Blazor
MVC, Razor Pages et Blazor font partie du framework ASP.NET Core et ont été conçus
pour être utilisés ensemble. Les composants Razor peuvent être intégrés dans les
applications Razor Pages et MVC. Quand une vue ou une page est affichée, les
composants peuvent dans le même temps faire l’objet d’un prérendu.

Avantages de MVC ou Razor Pages plus Blazor, en plus des avantages de MVC ou Razor
Pages :

Le prérendu exécute des composants Razor sur le serveur et les affiche dans une
vue ou une page, ce qui améliore le temps de chargement perçu de l’application.
Ajoute de l’interactivité aux vues ou pages existantes avec l’assistance au balisage
de composant.

Pour commencer à utiliser ASP.NET Core MVC ou Razor Pages plus Blazor, consultez
Intégration des composants ASP.NET Core Razor.
Étapes suivantes
Pour plus d'informations, consultez les pages suivantes :

ASP.NET Core Blazor


Modèles d’hébergement ASP.NET Core Blazor
Intégrer des composants Razor ASP.NET Core aux applications ASP.NET Core
Comparer les services gRPC avec les API HTTP

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Tutoriel : Créer une application web
Razor Pages avec ASP.NET Core
Article • 09/02/2024

Cette série de tutoriels explique les bases de la création d’une application web Razor
Pages.

Pour obtenir une présentation plus avancée destinée aux développeurs qui connaissent
bien les contrôleurs et les vues, consultez Présentation de Razor Pages dans ASP.NET
Core.

Si vous débutez dans le développement ASP.NET Core et que vous ne savez pas quelle
solution d’interface utilisateur web ASP.NET Core convient le mieux à vos besoins,
consultez Choisir une interface utilisateur ASP.NET Core.

Cette série comprend les tutoriels suivants :

1. Créer un projet d’application web Razor Pages


2. Ajouter un modèle à une application Razor Pages
3. Générer la structure de pagesRazor
4. Utiliser une base de données
5. Mettre à jour des pagesRazor
6. Ajouter une recherche
7. Ajouter un nouveau champ
8. Ajouter la validation

À la fin, vous disposez d’une application qui peut afficher et gérer une base de données
de films.
6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Tutoriel : Bien démarrer avec Razor
Pages dans ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

C’est le premier d’une série de tutoriels, qui décrit les principes fondamentaux liés à la
génération d’une application web de Razor Pages dans ASP.NET Core.

Pour obtenir une présentation plus avancée destinée aux développeurs qui connaissent
bien les contrôleurs et les vues, consultez Présentation de Razor Pages. Pour une
présentation vidéo, consultez Entity Framework Core pour les débutants .

Si vous débutez dans le développement ASP.NET Core et que vous ne savez pas quelle
solution d’interface utilisateur web ASP.NET Core convient le mieux à vos besoins,
consultez Choisir une interface utilisateur ASP.NET Core.

À la fin de ce tutoriel, vous disposerez d’une application web Razor Pages qui gère une
base de données de films.

Prérequis
Visual Studio

Préversion de Visual Studio 2022 avec la charge de travail ASP.NET et


développement web.
Créer une application web Razor Pages
Visual Studio

Ouvrez Visual Studio et sélectionnez Nouveau projet.

Dans la boîte de dialogue Créer un projet, sélectionnez Application web


ASP.NET Core (Razor Pages)>Suivant.

Dans la boîte de dialogue Configurer votre nouveau projet, entrez


RazorPagesMovie pour Nom du projet. Il est important de nommer le projet

RazorPagesMovie, en respectant la casse, pour que les espaces de noms


correspondent quand vous copiez et collez l’exemple de code.

Sélectionnez Suivant.

Dans la boîte de dialogue Informations supplémentaires :


Sélectionnez .NET 8.0 (prise en charge à long terme).
Vérifiez que l’option ne pas utiliser les instructions de niveau supérieur est
décochée.

Sélectionnez Créer.
Le projet de démarrage suivant est créé :
Pour obtenir d’autres approches pour créer le projet, consultez Créer un projet dans
Visual Studio.

Exécuter l'application
Visual Studio

Sélectionnez RazorPagesMovie dans l’Explorateur de solutions, puis appuyez sur


Ctrl+F5 pour l’exécuter sans le débogueur.

Visual Studio affiche la boîte de dialogue suivante lorsqu’un projet n’est pas encore
configuré pour utiliser SSL :

Sélectionnez Oui si vous faites confiance au certificat SSL d’IIS Express.

La boîte de dialogue suivante s’affiche :


Sélectionnez Oui si vous acceptez d’approuver le certificat de développement.

Pour plus d’informations sur l’approbation du navigateur Firefox, consultez Erreur


de certificat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.

Visual Studio :

Exécute l’application, qui lance le serveur Kestrel.


Lance le navigateur par défaut sur https://localhost:<port> , qui affiche
l’interface utilisateur des applications. <port> est le port aléatoire attribué lors
de la création de l’application.

Examiner les fichiers projet


Les sections suivantes contiennent une vue d’ensemble des principaux dossiers et
fichiers projet que vous allez utiliser dans les tutoriels suivants.

Dossier Pages
Contient les pages Razor et les fichiers de prise en charge. Chaque page Razor est une
paire de fichiers :

Fichier .cshtml qui a un balisage HTML avec du code C# avec la syntaxe Razor.
Un fichier .cshtml.cs qui contient du code C# gérant les événements de page.

Les fichiers de prise en charge ont des noms commençant par un trait de soulignement.
Par exemple, le fichier _Layout.cshtml configure des éléments d’interface utilisateur
communs à toutes les pages. _Layout.cshtml définit le menu de navigation en haut de
la page et la mention de copyright au bas de la page. Pour plus d’informations,
consultez Disposition dans ASP.NET Core.

Dossier racine
Contient les ressources statiques, comme les fichiers HTML, les fichiers JavaScript et les
fichiers CSS. Pour plus d’informations, consultez Fichiers statiques dans ASP.NET Core.

appsettings.json

Contient les données de configuration, comme les chaînes de connexion. Pour plus
d’informations, consultez Configuration dans ASP.NET Core.
Program.cs
Contient le code suivant :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Les lignes de code suivantes dans ce fichier créent un WebApplicationBuilder avec des
valeurs par défaut préconfigurées, ajoutent la prise en charge de Razor Pages au
conteneur d’injection de dépendances (DI) et créent l’application :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

La page des exceptions de développeur est activée par défaut et fournit des
informations utiles sur les exceptions. Les applications de production ne doivent pas
être exécutées en mode développement, car la page des exceptions de développeur
peut divulguer des informations sensibles.

Le code suivant définit le point de terminaison d’exception sur /Error et active le


protocole HSTS (HTTP Strict Transport Security) lorsque l’application ne s’exécute pas en
mode développement :

C#

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

Par exemple, le code précédent s’exécute lorsque l’application est en mode production
ou test. Pour plus d’informations, consultez Utiliser plusieurs environnements dans
ASP.NET Core.

Le code suivant active différents intergiciels :

app.UseHttpsRedirection(); : redirige les requêtes HTTP vers HTTPS.


app.UseStaticFiles(); : permet de fournir des fichiers statiques, notamment

HTML, CSS, images et JavaScript. Pour plus d’informations, consultez Fichiers


statiques dans ASP.NET Core.
app.UseRouting(); : ajoute la correspondance d’itinéraire au pipeline d’intergiciels.

Pour plus d’informations, consultez Routage dans ASP.NET Core


app.MapRazorPages(); : configure le routage des points de terminaison pour Razor

Pages.
app.UseAuthorization(); : autorise un utilisateur à accéder à des ressources

sécurisées. Cette application n’utilise pas l’autorisation. Par conséquent, cette ligne
peut être supprimée.
app.Run(); : exécute l’application.

Résolution des problèmes avec l’exemple


terminé
Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code
au projet terminé. Afficher ou télécharger le projet terminé (comment télécharger).
Étapes suivantes
Suivant : Ajouter un modèle

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 2, ajouter un modèle à une
application de pages Razor dans
ASP.NET Core
Article • 30/11/2023

Dans ce tutoriel, des classes sont ajoutées pour la gestion des films dans une base de
données. Les classes de modèle de l’application utilisent Entity Framework Core (EF
Core) pour travailler avec la base de données. EF Core est un mappeur relationnel
d’objets (O/RM) qui simplifie l’accès aux données. Vous écrivez d’abord les classes du
modèle, puis EF Core crée la base de données.

Les classes de modèle portent le nom de classes OCT (« Objet CLR Traditionnel »), car
elles n’ont pas de dépendances envers EF Core. Elles définissent les propriétés des
données stockées dans la base de données.

Ajouter un modèle de données


Visual Studio

1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet


RazorPagesMovie>Ajouter>Nouveau dossier. Nommez le dossier Models .

2. Cliquez avec le bouton droit sur le dossier Models . Sélectionnez


Ajouter>Classe. Nommez la classe Movie.

3. Ajoutez les propriétés suivantes à la classe Movie :

C#

using System.ComponentModel.DataAnnotations;

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }
public string? Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
public decimal Price { get; set; }
}

La classe Movie contient :

Le champ ID est nécessaire à la base de données pour la clé primaire.

Attribut [DataType] qui spécifie le type de données dans la propriété


ReleaseDate . Avec cet attribut :

L’utilisateur n’est pas obligé d’entrer les informations de temps dans le


champ de date.
Seule la date est affichée, pas les informations de temps.

La présence du point d’interrogation après string indique que la propriété


peut accepter les valeurs Null. Pour plus d’informations, consultez Types
référence pouvant accepter la valeur Null.

Les DataAnnotations sont traitées dans un prochain didacticiel.

Générez le projet pour vérifier qu’il n’y a pas d’erreur de compilation.

Générer automatiquement le modèle de film


Dans cette section, le modèle de film est généré automatiquement. Autrement dit, l’outil
de génération de modèles automatique génère des pages pour les opérations de
création, de lecture, de mise à jour et de suppression (CRUD) pour le modèle de film.

Visual Studio

1. Créez le dossier Pages/Movies :


a. Cliquez avec le bouton droit sur le dossier Pages>Ajouter>Nouveau
dossier.
b. Nommez le dossier Movies.

2. Cliquez avec le bouton droit sur le dossier Pages/Movies>Ajouter>Nouvel


élément généré automatiquement.
3. Dans la boîte de dialogue Ajouter un nouveau modèle automatique,
sélectionnez Pages Razor utilisant Entity Framework (CRUD)>Ajouter.
4. Renseignez la boîte de dialogue Ajouter des pages Razor avec Entity
Framework (CRUD) :
a. Dans la liste déroulante Classe de modèle, sélectionnez Film
(RazorPagesMovie.Models).
b. Dans la ligne Classe du contexte de données, sélectionnez le signe +
(plus).
i. Dans la boîte de dialogue Ajouter un contexte de données, le nom de la
classe RazorPagesMovie.Data.RazorPagesMovieContext est généré.
ii. Dans la liste déroulante Fournisseur de base de données, sélectionnez
SQL Server.
c. Sélectionnez Ajouter.
Le fichier appsettings.json est mis à jour avec la chaîne de connexion utilisée pour
se connecter à une base de données locale.

Fichiers créés et mis à jour


Le processus de génération de modèles automatique crée les fichiers suivants :

Pages/Movies : Create, Delete, Details, Edit, Index.


Data/RazorPagesMovieContext.cs

Les fichiers créés sont expliqués dans le tutoriel suivant.

Le processus de génération de modèles automatique ajoute le code en surbrillance


suivant au fichier Program.cs :

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Les modifications Program.cs sont expliquées plus loin dans ce tutoriel.

Créer le schéma de base de données initial à


l’aide de la fonctionnalité de migration d’EF
La fonctionnalité de migration dans Entity Framework Core permet de :

Créer le schéma de base de données initial.


Mettre à jour de façon incrémentielle le schéma de base de données afin de le
garder synchronisé avec le modèle de données de l’application. Les données
existantes dans la base de données sont conservées.

Visual Studio

Dans cette section, la fenêtre Console du gestionnaire de package est utilisée


pour :

Ajouter une migration initiale


Mettez à jour la base de données avec la migration initiale.

1. Dans le menu Outils, sélectionnez Gestionnaire de package NuGet>Console


du gestionnaire de package.

2. Dans la console du Gestionnaire de package, entrez les commandes


suivantes :

PowerShell

Add-Migration InitialCreate
Update-Database

La commande Add-Migration génère le code nécessaire à la création du


schéma de base de données initial. Le schéma est basé sur le modèle spécifié
dans DbContext . L’argument InitialCreate est utilisé pour nommer la
migration. Vous pouvez utiliser n’importe quel nom, mais par convention, un
nom décrivant la migration est sélectionné.

La commande Update-Database exécute la méthode Up dans des migrations


qui n’ont pas été appliquées. Dans ce cas, la commande exécute la méthode
Up dans le fichier Migrations/<time-stamp>_InitialCreate.cs , ce qui entraîne

la création de la base de données.

L’avertissement suivant s’affiche et sera traité dans une étape ultérieure :

Aucun type n’a été spécifié pour la colonne décimale 'Price' sur le type d’entité
'Movie'. Les valeurs sont tronquées en mode silencieux si elles ne sont pas
compatibles avec la précision et l’échelle par défaut. Spécifiez explicitement le type
de colonne SQL Server capable d’accueillir toutes les valeurs en utilisant
’HasColumnType()’.

Contexte de données RazorPagesMovieContext :

Dérive de Microsoft.EntityFrameworkCore.DbContext.
Spécifie les entités qui sont incluses dans le modèle de données.
Coordonne les fonctionnalités EF Core, telles que Créer, Lire, Mettre à jour et
Supprimer, pour le modèle Movie .

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Data
{
public class RazorPagesMovieContext : DbContext
{
public RazorPagesMovieContext
(DbContextOptions<RazorPagesMovieContext> options)
: base(options)
{
}

public DbSet<RazorPagesMovie.Models.Movie> Movie { get; set; } =


default!;
}
}

Le code précédent crée une propriété DbSet<Movie> pour le jeu d’entités. Dans la
terminologie Entity Framework, un jeu d’entités correspond généralement à une table
de base de données. Une entité correspond à une ligne dans la table.

Le nom de la chaîne de connexion est transmis au contexte en appelant une méthode


sur un objet DbContextOptions. Pour le développement local, le système de
configuration lit la chaîne de connexion à partir du fichier appsettings.json .

Tester l’application
1. Exécutez l’application et ajoutez /Movies à l’URL dans le navigateur
( http://localhost:port/movies ).

Si vous voyez le message d’erreur suivant :

Console

SqlException: Cannot open database "RazorPagesMovieContext-GUID"


requested by the login. The login failed.
Login failed for user 'User-name'.

Vous avez manqué l’étape des migrations.

2. Testez le lien Créer nouveau.

7 Notes
Vous ne pourrez peut-être pas entrer de virgules décimales dans le champ
Price . Pour prendre en charge la validation jQuery pour les paramètres
régionaux autres que « Anglais » qui utilisent une virgule (« , ») comme
décimale et des formats de date autres que le format « Anglais (États-Unis »),
l’application doit être localisée. Pour obtenir des instructions sur la
localisation, consultez ce problème GitHub .

3. Testez les liens Modifier, Détails et Supprimer.

Le prochain didacticiel décrit les fichiers créés par la génération de modèles


automatique.

Examiner le contexte inscrit avec l’injection de


dépendances
ASP.NET Core comprend l’injection de dépendances. Des services, tels que le contexte
de base de données EF Core, sont inscrits avec l’injection de dépendance au démarrage
de l’application. Ces services sont affectés aux composants qui les nécessitent (par
exemple les Pages Razor) par le biais de paramètres de constructeur. Le code de
constructeur qui obtient une instance de contexte de base de données est indiqué plus
loin dans le tutoriel.

L’outil de génération de modèles automatique a créé automatiquement un contexte de


base de données et l’a inscrit dans le conteneur d’injection de dépendances. Le code
mis en surbrillance suivant est ajouté au fichier Program.cs par le générateur de
modèles :

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Résolution des problèmes avec l’exemple


terminé
Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code
au projet terminé. Afficher ou télécharger le projet terminé (comment télécharger).

Étapes suivantes
Précédent : Bien démarrer

Suivant : Pages Razor obtenues par génération de modèles automatique

6 Collaborer avec nous sur


GitHub
La source de ce contenu se
trouve sur GitHub, où vous
pouvez également créer et
examiner les problèmes et les
demandes de tirage. Pour plus
d’informations, consultez notre
guide du contributeur.
ASP.NET Core feedback
ASP.NET Core is an open source
project. Select a link to provide
feedback:

 Ouvrir un problème de
documentation

 Indiquer des commentaires sur


le produit
Partie 3, pages Razor obtenues par
génération de modèles automatique
dans ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Ce tutoriel décrit les pages Razor créées par génération de modèles automatique dans
le tutoriel précédent.

Pages Create, Delete, Details et Edit


Examinez le modèle de page Pages/Movies/Index.cshtml.cs :

C#

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies;

public class IndexModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

public IList<Movie> Movie { get;set; } = default!;

public async Task OnGetAsync()


{
if (_context.Movie != null)
{
Movie = await _context.Movie.ToListAsync();
}
}
}

Les pages Razor sont dérivées de PageModel. Par convention, la classe dérivée de
PageModel s’appelle PageNameModel . Par exemple, la page Index est nommée IndexModel .
Le constructeur utilise l’injection de dépendances pour ajouter RazorPagesMovieContext
à la page :

C#

public class IndexModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

Consultez Code asynchrone pour plus d’informations sur la programmation asynchrone


avec Entity Framework.

Quand une demande GET est effectuée pour la page, la méthode OnGetAsync retourne
une liste de films à la page Razor. Dans une page Razor, OnGetAsync ou OnGet est
appelée pour initialiser l’état de la page. Dans ce cas, OnGetAsync obtient une liste de
films et les affiche.

Si OnGet retourne void ou que OnGetAsync retourne Task , aucune instruction return
n’est utilisée. Par exemple, examinez la page Privacy :

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPagesMovie.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;

public PrivacyModel(ILogger<PrivacyModel> logger)


{
_logger = logger;
}

public void OnGet()


{
}
}
}
Lorsque le type de retour est IActionResult ou Task<IActionResult> , une instruction de
retour doit être spécifiée. Par exemple, la méthode Pages/Movies/Create.cshtml.cs
OnPostAsync :

C#

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_context.Movie.Add(Movie);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}

Examinez la page Razor Pages/Movies/Index.cshtml :

CSHTML

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.Id">Details</a>
|
<a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Razor peut passer du code HTML à du code C# ou au balisage spécifique à Razor.


Quand un symbole @ est suivi d’un mot clé réservé Razor, il est converti en balisage
spécifique à Razor. Sinon, il est converti en C#.

Directive @page
La directive Razor @page transforme le fichier en action MVC, ce qui lui permet de traiter
des demandes. @page doit être la première directive Razor sur une page. @page et
@model sont des exemples de transition vers un balisage spécifique à Razor. Consultez

Syntaxe Razor pour plus d’informations.

Directive @model
CSHTML

@page
@model RazorPagesMovie.Pages.Movies.IndexModel
La directive @model spécifie le type du modèle passé à la page Razor. Dans l’exemple
précédent, la ligne @model rend la classe dérivée de PageModel accessible à la page
Razor. Le modèle est utilisé dans les HTML Helpers @Html.DisplayNameFor et
@Html.DisplayFor de la page.

Examinez l’expression lambda utilisée dans le HTML Helper suivant :

CSHTML

@Html.DisplayNameFor(model => model.Movie[0].Title)

Le HTML Helper DisplayNameFor inspecte la propriété Title référencée dans


l’expression lambda pour déterminer le nom d’affichage. L’expression lambda est
inspectée plutôt qu’évaluée. Cela signifie qu’il n’existe pas de violation d’accès quand
model , model.Movie ou model.Movie[0] a la valeur null ou est vide. Quand l’expression

lambda est évaluée, par exemple avec @Html.DisplayFor(modelItem => item.Title) , les
valeurs de propriété du modèle sont évaluées.

La page de disposition
Sélectionnez les liens de menu RazorPagesMovie, Home et Privacy. Chaque page
affiche la même disposition de menu. La disposition du menu est implémentée dans le
fichier Pages/Shared/_Layout.cshtml .

Ouvrez et examinez le fichier Pages/Shared/_Layout.cshtml .

Les modèles de Disposition permettent que la disposition du conteneur HTML soit :

spécifiée à un seul endroit ;


appliquée à plusieurs pages du site.

Recherchez la ligne @RenderBody() . RenderBody est un espace réservé dans lequel toutes
les vues propres à la page s’affichent, encapsulées dans la page de disposition. Par
exemple, sélectionnez le lien Privacy et la vue Pages/Privacy.cshtml est affichée à
l’intérieur de la méthode RenderBody .

ViewData et disposition
Examinez le balisage suivant à partir du fichier Pages/Movies/Index.cshtml :

CSHTML
@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

Le balisage précédent en surbrillance est un exemple de conversion de Razor vers C#.


Les caractères { et } délimitent un bloc de code C#.

La classe de base PageModel contient une propriété de dictionnaire ViewData qui peut
être utilisée pour transmettre des données à une vue. Des objets sont ajoutés au
dictionnaire ViewData à l’aide d’un modèle clé valeur. Dans l’exemple précédent, la
propriété Title est ajoutée au dictionnaire ViewData .

La propriété Title est utilisée dans le fichier Pages/Shared/_Layout.cshtml . Le balisage


suivant montre les premières lignes du fichier _Layout.cshtml .

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"
/>
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
<link rel="stylesheet" href="~/RazorPagesMovie.styles.css" asp-append-
version="true" />

La ligne @*Markup removed for brevity.*@ est un commentaire Razor. Contrairement


aux commentaires HTML <!-- --> , les commentaires Razor ne sont pas envoyés au
client. Consultez Documents web MDN : Prise en main du code HTML pour plus
d’informations.

Mettre à jour la disposition


1. Changez l’élément <title> dans le fichier Pages/Shared/_Layout.cshtml pour
afficher Movie au lieu de RazorPagesMovie.

CSHTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-
scale=1.0" />
<title>@ViewData["Title"] - Movie</title>

2. Recherchez l’élément anchor suivant dans le fichier Pages/Shared/_Layout.cshtml .

CSHTML

<a class="navbar-brand" asp-area="" asp-


page="/Index">RazorPagesMovie</a>

3. Remplacez l’élément précédent par la balise suivante :

CSHTML

<a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>

L’élément anchor précédent est un Tag Helper. Dans le cas présent, il s’agit du Tag
Helper d’ancre. L’attribut et la valeur du Tag Helper asp-page="/Movies/Index"
créent un lien vers la page Razor /Movies/Index . La valeur de l’attribut asp-area est
vide : la zone n’est donc pas utilisée dans le lien. Pour plus d’informations,
consultez Zones.

4. Enregistrez les modifications et testez l’application en sélectionnant le lien


RpMovie. Consultez le fichier _Layout.cshtml dans GitHub si vous rencontrez des
problèmes.

5. Testez les liens Home, RpMovie, Create, Edit et Delete. Chaque page définit le
titre, que vous pouvez voir dans l’onglet du navigateur. Quand vous définissez un
signet pour une page, le titre est affecté au signet.

7 Notes

Vous ne pourrez peut-être pas entrer de virgules décimales dans le champ Price .
Pour prendre en charge la validation jQuery pour les paramètres régionaux
autres que l’anglais qui utilisent une virgule (« , ») comme point décimal, et des
formats de date autres que l’anglais des États-Unis, vous devez prendre des
mesures pour mondialiser l’application. Consultez cette page GitHub problème
4076 pour savoir comment ajouter une virgule décimale.

La propriété Layout est définie dans le fichier Pages/_ViewStart.cshtml :

CSHTML

@{
Layout = "_Layout";
}

Le balisage précédent définit le fichier de disposition Pages/Shared/_Layout.cshtml pour


tous les fichiers Razor du dossier Pages. Pour plus d’informations, consultez Disposition.

Le modèle de page Create


Examinez le modèle de page Pages/Movies/Create.cshtml.cs :

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext
_context;

public CreateModel(RazorPagesMovie.Data.RazorPagesMovieContext
context)
{
_context = context;
}

public IActionResult OnGet()


{
return Page();
}

[BindProperty]
public Movie Movie { get; set; } = default!;

// To protect from overposting attacks, see


https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || _context.Movie == null || Movie ==
null)
{
return Page();
}

_context.Movie.Add(Movie);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}
}
}

La méthode OnGet initialise l’état nécessaire pour la page. La page Create n’ayant aucun
état à initialiser, Page est retourné. Plus loin dans le tutoriel, un exemple d’état
d’initialisation OnGet est affiché. La méthode Page crée un objet PageResult qui affiche
la page Create.cshtml .

La propriété Movie utilise l’attribut [BindProperty] pour adhérer à la liaison de modèles.


Quand le formulaire Create publie les valeurs de formulaire, le runtime ASP.NET Core lie
les valeurs publiées au modèle Movie .

La méthode OnPostAsync est exécutée quand la page publie les données de formulaire :

C#

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_context.Movie.Add(Movie);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}

S’il existe des erreurs liées au modèle, le formulaire est réaffiché, ainsi que toutes les
données de formulaire publiées. La plupart des erreurs de modèle peuvent être
interceptées côté client avant la publication du formulaire. Voici un exemple d’erreur de
modèle : la publication pour le champ de date d’une valeur qui ne peut pas être
convertie en date. La validation côté client et la validation du modèle sont présentées
plus loin dans le tutoriel.
S’il n’y a pas d’erreurs de modèle :

Les données sont enregistrées.


Le navigateur est redirigé vers la page Index.

Page Razor Create


Examinez le fichier de page Razor Pages/Movies/Create.cshtml :

CSHTML

@page
@model RazorPagesMovie.Pages.Movies.CreateModel

@{
ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label">
</label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger">
</span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary"
/>
</div>
</form>
</div>
</div>

<div>
<a asp-page="Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Visual Studio

Visual Studio affiche la balise suivante dans une police différenciée en gras utilisée
pour les Tag Helpers :

<form method="post">

<div asp-validation-summary="ModelOnly" class="text-danger"></div>


<label asp-for="Movie.Title" class="control-label"></label>

<input asp-for="Movie.Title" class="form-control" />

<span asp-validation-for="Movie.Title" class="text-danger"></span>


L’élément <form method="post"> est un Tag Helper de formulaire. Le Tag Helper de
formulaire inclut automatiquement un jeton de protection contre les falsifications.

Le moteur de génération de modèles automatique crée le balisage Razor pour chaque


champ du modèle, sauf l’ID, de la manière suivante :

CSHTML

<div asp-validation-summary="ModelOnly" class="text-danger"></div>


<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>

Les Tag Helpers de validation ( <div asp-validation-summary et <span asp-validation-


for ) affichent des erreurs de validation. La validation est traitée de manière plus

détaillée plus loin dans cette série.

Le Tag Helper d’étiquette ( <label asp-for="Movie.Title" class="control-label">


</label> ) génère la légende de l’étiquette et l’attribut [for] pour la propriété Title .
Le Tag Helper d’entrée ( <input asp-for="Movie.Title" class="form-control"> ) utilise les
attributs DataAnnotations et produit les attributs HTML nécessaires à la validation
jQuery côté client.

Pour plus d’informations sur les Tag Helpers, comme <form method="post"> , consultez
Tag Helpers dans ASP.NET Core.

Étapes suivantes
Précédent : Ajouter un modèle Suivant : Utiliser une base de données

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 4 de la série de tutoriels sur Pages
Razor
Article • 30/11/2023

Par Joe Audette

L’objet RazorPagesMovieContext gère la tâche de connexion à la base de données et de


mappage d’objets Movie à des enregistrements de la base de données. Le contexte de
base de données est inscrit auprès du conteneur d’injection de dépendances dans
Program.cs :

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));

var app = builder.Build();

Le système de configuration d’ASP.NET Core lit la clé ConnectionString . Pour un


développement local, la configuration obtient la chaîne de connexion à partir du fichier
appsettings.json .

Visual Studio

La chaîne de connexion générée est similaire à la valeur JSON suivante :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"RazorPagesMovieContext": "Server=
(localdb)\\mssqllocaldb;Database=RazorPagesMovieContext-
bc;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

Quand l’application est déployée sur un serveur de test ou de production, une variable
d’environnement peut être utilisée pour définir la chaîne de connexion à un serveur de
base de données de test ou de production. Pour plus d’informations, consultez
Configuration.

Visual Studio

Base de données locale SQL Server Express


LocalDB est une version allégée du moteur de base de données SQL Server Express,
qui est ciblée pour le développement de programmes. LocalDB démarre à la
demande et s’exécute en mode utilisateur, ce qui n’implique aucune configuration
complexe. Par défaut, la base de données LocalDB crée des fichiers *.mdf dans le
répertoire C:\Users\<user>\ .

1. Dans le menu Affichage, ouvrez l’Explorateur d’objets SQL Server (SSOX).


2. Faites un clic droit sur la table Movie et sélectionnez Concepteur de vues :
Notez l’icône de clé en regard de ID . Par défaut, EF crée une propriété
nommée ID pour la clé primaire.

3. Faites un clic droit sur la table Movie et sélectionnez Afficher les données :

Amorcer la base de données


Créez une classe nommée SeedData dans le dossier Modèles avec le code suivant :

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;

namespace RazorPagesMovie.Models;

public static class SeedData


{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new RazorPagesMovieContext(
serviceProvider.GetRequiredService<
DbContextOptions<RazorPagesMovieContext>>()))
{
if (context == null || context.Movie == null)
{
throw new ArgumentNullException("Null
RazorPagesMovieContext");
}

// Look for any movies.


if (context.Movie.Any())
{
return; // DB has been seeded
}

context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M
},

new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},

new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},

new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}

Si la base de données contient des films, l’initialiseur de valeur initiale retourne une
valeur et aucun film n’est ajouté.

C#

if (context.Movie.Any())
{
return;
}

Ajouter l’initialiseur de valeur initiale


Mettez à jour Program.cs avec le code en surbrillance suivant :

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
using RazorPagesMovie.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));

var app = builder.Build();

using (var scope = app.Services.CreateScope())


{
var services = scope.ServiceProvider;

SeedData.Initialize(services);
}

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Dans le code précédent, Program.cs a été modifié pour effectuer les opérations
suivantes :

Obtenir une instance de contexte de base de données à partir du conteneur


d’injection de dépendance.
Appelez la méthode seedData.Initialize en lui transmettant le contexte de base
de données de l’instance.
Supprimer le contexte une fois la méthode seed terminée. L’instruction using
garantit que le contexte est supprimé.

L’exception suivante se produit lorsque Update-Database n’a pas été exécuté :

SqlException: Cannot open database "RazorPagesMovieContext-" requested by the

login. The login failed. Login failed for user 'user name'.

Tester l’application
Supprimez tous les enregistrements dans la base de données pour que la méthode seed
s’exécute. Arrêtez et démarrez l’application pour amorcer la base de données. Si la base
de données n’est pas amorcée, placez un point d’arrêt et if (context.Movie.Any())
parcourez le code.

L’application affiche les données de départ :


Étapes suivantes
Précédent : Pages Razor générées automatiquement

Suivant : Mettre à jour les pages

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 5, Mettre à jour les pages
générées dans une application ASP.NET
Core
Article • 30/11/2023

L’application de gestion des films générée est un bon début, mais la présentation n’est
pas idéale. ReleaseDate doit être écrit en deux mots, Release Date.

Mettre le modèle à niveau


Mettez à jour Models/Movie.cs avec le code mis en évidence suivant :

C#

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;

[Column(TypeName = "decimal(18, 2)")]


public decimal Price { get; set; }
}

Dans le code précédent :

L’annotation de données [Column(TypeName = "decimal(18, 2)")] permet à Entity


Framework Core de mapper correctement Price en devise dans la base de
données. Pour plus d’informations, consultez Types de données.
L’attribut [Display] spécifie le nom complet d’un champ. Dans le code précédent,
Release Date au lieu de ReleaseDate .

l’attribut [DataType] spécifie le type de données ( Date ). Les informations de temps


stockées dans le champ ne s’affichent pas.

DataAnnotations est abordé dans le tutoriel suivant.

Accédez à Pages/Movies, puis placez le curseur sur un lien Modifier pour afficher l’URL
cible.
Les liens Edit, Details, et Delete sont générés par le Tag Helper d’ancre dans le fichier
Pages/Movies/Index.cshtml .

CSHTML

@foreach (var item in Model.Movie) {


<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a>
|
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Les Tag Helpers permettent au code côté serveur de participer à la création et au rendu
des éléments HTML dans les fichiers Razor.

Dans le code précédent, le Tag Helper d’ancre génère dynamiquement la valeur


d’attribut HTML href dans la page Razor (l’itinéraire est relatif), asp-page et l’ID de
l’itinéraire ( asp-route-id ). Pour plus d’informations, consultez Génération d’URL pour
Pages.

Utilisez Afficher la Source dans un navigateur pour examiner le balisage généré. Une
partie du code HTML généré est affichée ci-dessous :

HTML

<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
Les liens générés dynamiquement transmettent l’ID de film avec une chaîne de
requête . Par exemple, ?id=1 dans https://localhost:5001/Movies/Details?id=1 .

Ajouter un modèle d’itinéraire


Mettez à jour les Pages Razor Edit, Details et Delete pour utiliser le modèle de route
{id:int} . Remplacez la directive de chacune de ces pages ( @page "{id:int}" ) par

@page . Exécuter l’application, puis affichez le code source.

Le code HTML généré ajoute l’ID à la partie de chemin de l’URL :

HTML

<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>

Une requête à la page avec le modèle d’itinéraire {id:int} qui n’inclut pas l’entier
retourne une erreur HTTP 404 (introuvable). Par exemple,
https://localhost:5001/Movies/Details retourne une erreur 404. Pour que l’ID soit

facultatif, ajoutez ? à la contrainte d’itinéraire :

CSHTML

@page "{id:int?}"

Testez le comportement de @page "{id:int?}" :

1. Définissez la directive de page dans Pages/Movies/Details.cshtml sur @page "


{id:int?}" .

2. Définissez un point d’arrêt dans public async Task<IActionResult>


OnGetAsync(int? id) , dans Pages/Movies/Details.cshtml.cs .

3. Accédez à https://localhost:5001/Movies/Details/ .

Avec la directive @page "{id:int}" , le point d’arrêt n’est jamais atteint. Le moteur de
routage retourne l’erreur HTTP 404. Avec @page "{id:int?}" , la méthode OnGetAsync
retourne NotFound (HTTP 404) :

C#
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}

Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

if (Movie == null)
{
return NotFound();
}
return Page();
}

Passer en revue la gestion des exceptions d’accès


concurrentiel
Passez en revue la méthode OnPostAsync dans le fichier Pages/Movies/Edit.cshtml.cs :

C#

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_context.Attach(Movie).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}

return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.Id == id);
}

Le code précédent détecte les exceptions d’accès concurrentiel quand un client


supprime le film et que l’autre client envoie les modifications apportées au film.

Pour tester le bloc catch :

1. Définissez un point d’arrêt sur catch (DbUpdateConcurrencyException) .


2. Sélectionnez Edit (Modifier) pour un film, apportez des modifications, mais ne
cliquez pas sur Save (Enregistrer).
3. Dans une autre fenêtre de navigateur, sélectionnez le lien Delete du même film,
puis supprimez le film.
4. Dans la fenêtre de navigateur précédente, postez les modifications apportées au
film.

Dans le code destiné à la production, il est nécessaire de détecter les conflits d’accès
concurrentiel. Pour plus d’informations, consultez Gérer les conflits d’accès
concurrentiel.

Validation de la publication et de la liaison


Examinez le fichier Pages/Movies/Edit.cshtml.cs :

C#

public class EditModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

[BindProperty]
public Movie Movie { get; set; } = default!;

public async Task<IActionResult> OnGetAsync(int? id)


{
if (id == null || _context.Movie == null)
{
return NotFound();
}
var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id ==
id);
if (movie == null)
{
return NotFound();
}
Movie = movie;
return Page();
}

// To protect from overposting attacks, enable the specific properties


you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}

_context.Attach(Movie).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}

return RedirectToPage("./Index");
}

private bool MovieExists(int id)


{
return _context.Movie.Any(e => e.Id == id);
}

Quand une requête HTTP GET est effectuée sur la page Movies/Edit, par exemple,
https://localhost:5001/Movies/Edit/3 :

La méthode OnGetAsync extrait le film de la base de données et retourne la


méthode Page .
La méthode Page restitue la page Pages/Movies/Edit.cshtml Razor. Le fichier
Pages/Movies/Edit.cshtml contient la directive de modèle @model

RazorPagesMovie.Pages.Movies.EditModel , ce qui rend le modèle Movie disponible

sur la page.
Le formulaire d’édition s’affiche avec les valeurs relatives au film.

Quand la page Movies/Edit est postée :

Les valeurs de formulaire affichées dans la page sont liées à la propriété Movie .
L’attribut [BindProperty] active la liaison de données.

C#

[BindProperty]
public Movie Movie { get; set; }

S’il y a des erreurs dans l’état du modèle, par exemple, si ReleaseDate ne peut pas
être converti en date, le formulaire est à nouveau affiché avec les valeurs soumises.

S’il n’y a aucune erreur de modèle, le film est enregistré.

Les méthodes HTTP GET dans les pages Razor Index, Create et Delete suivent un modèle
similaire. La méthode HTTP POST OnPostAsync dans la page Razor Create suit un modèle
semblable à la méthode OnPostAsync dans la page Razor Edit.

Étapes suivantes
Précédent : Utilisation d’une base de données

Suivant : Ajouter une fonction de recherche

6 Collaborer avec nous sur


GitHub
La source de ce contenu se
trouve sur GitHub, où vous
pouvez également créer et
examiner les problèmes et les
demandes de tirage. Pour plus
d’informations, consultez notre
guide du contributeur.
ASP.NET Core feedback
ASP.NET Core is an open source
project. Select a link to provide
feedback:

 Ouvrir un problème de
documentation

 Indiquer des commentaires sur


le produit
Partie 6, ajouter la recherche à ASP.NET
Core Pages Razor
Article • 30/11/2023

Par Rick Anderson

Dans les sections suivantes, la recherche de films par genre ou par nom est ajoutée.

Ajoutez le code en surbrillance suivant à Pages/Movies/Index.cshtml.cs :

C#

public class IndexModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

public IList<Movie> Movie { get;set; } = default!;

[BindProperty(SupportsGet = true)]
public string? SearchString { get; set; }

public SelectList? Genres { get; set; }

[BindProperty(SupportsGet = true)]
public string? MovieGenre { get; set; }

Dans le code précédent :

SearchString : Contient le texte que les utilisateurs entrent dans la zone de texte

de recherche. SearchString a l’attribut [BindProperty]. [BindProperty] lie les


valeurs de formulaire et les chaînes de requête avec le même nom que la
propriété. [BindProperty(SupportsGet = true)] est obligatoire pour la liaison sur
les requêtes HTTP GET.
Genres : Contient la liste des genres. Genres permet à l’utilisateur de sélectionner
un genre dans la liste. SelectList nécessite using
Microsoft.AspNetCore.Mvc.Rendering;
MovieGenre : Contient le genre spécifique sélectionné par l’utilisateur. Par exemple,

« Western ».
Genres et MovieGenre sont utilisés plus loin dans ce tutoriel.

2 Avertissement

Pour des raisons de sécurité, vous devez choisir de lier les données de requête GET
aux propriétés du modèle de page. Vérifiez l’entrée utilisateur avant de la mapper à
des propriétés. Le choix de la liaison GET convient pour les scénarios qui s’appuient
sur des valeurs de routage ou de chaîne de requête.

Pour lier une propriété sur des requêtes GET , définissez la propriété SupportsGet de
l’attribut [BindProperty] sur true :

C#

[BindProperty(SupportsGet = true)]

Pour plus d’informations, consultez ASP.NET Core Community Standup: Bind on


GET discussion (YouTube) .

Mettez à jour la méthode OnGetAsync de la page Index avec le code suivant :

C#

public async Task OnGetAsync()


{
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(SearchString))
{
movies = movies.Where(s => s.Title.Contains(SearchString));
}

Movie = await movies.ToListAsync();


}

La première ligne de la méthode OnGetAsync crée une requête LINQ pour sélectionner
les films :

C#

// using System.Linq;
var movies = from m in _context.Movie
select m;
La requête est seulement définie à ce stade, elle n’a pas été exécutée sur la base de
données.

Si la propriété SearchString n’est pas null ou vide, la requête sur les films est modifiée
de façon à filtrer sur la chaîne de recherche :

C#

if (!string.IsNullOrEmpty(SearchString))
{
movies = movies.Where(s => s.Title.Contains(SearchString));
}

Le code s => s.Title.Contains() est une expression lambda. Les expressions lambda
sont utilisées dans les requêtes LINQ basées sur une méthode en tant qu’arguments
pour les méthodes d’opérateur de requête standard, comme la méthode Where ou
Contains . Les requêtes LINQ ne sont pas exécutées quand elles sont définies ou quand

elles sont modifiées en appelant une méthode, comme Where , Contains ou OrderBy . Au
lieu de cela, l’exécution de la requête est différée. L’évaluation d’une expression est
retardée jusqu’à ce que sa valeur réalisée fasse l’objet d’une itération réelle ou que la
méthode ToListAsync soit appelée. Pour plus d’informations, consultez Exécution de
requête.

7 Notes

La méthode Contains est exécutée sur la base de données, et non pas dans le code
C#. Le respect de la casse pour la requête dépend de la base de données et du
classement. Sur SQL Server, Contains est mappée à SQL LIKE, qui ne respecte pas
la casse. SQLite avec le classement par défaut est un mélange de respect de la
casse et de respect de la casse IN, en fonction de la requête. Pour plus
d’informations sur la création de requêtes SQLite sans respect de la casse, consultez
les rubriques suivantes :

Ce problème GitHub
Ce problème GitHub
Classements et respect de la casse

Accédez à la page Films, puis ajoutez une chaîne de requête telle que ?
searchString=Ghost à l’URL. Par exemple, https://localhost:5001/Movies?

searchString=Ghost Les films filtrés sont affichés.


Si le modèle de routage suivant est ajouté à la page d’index, la chaîne de recherche peut
être passée comme un segment d’URL. Par exemple,
https://localhost:5001/Movies/Ghost

CSHTML

@page "{searchString?}"

La contrainte d’itinéraire précédente permet de rechercher le titre comme données


d’itinéraire (un segment d’URL) et non comme valeur de chaîne de requête. Le ? dans "
{searchString?}" signifie qu’il s’agit d’un paramètre d’itinéraire facultatif.
Le runtime ASP.NET Core utilise la liaison de modèle pour définir la valeur de la
propriété SearchString à partir de la chaîne de requête ( ?searchString=Ghost ) ou des
données de la route ( https://localhost:5001/Movies/Ghost ). La liaison de modèle ne
respecte pas la casse.

Cependant, vous ne pouvez pas attendre des utilisateurs qu’ils modifient l’URL pour
rechercher un film. Dans cette étape, une interface utilisateur est ajoutée pour filtrer les
films. Si vous avez ajouté la contrainte d’itinéraire "{searchString?}" , supprimez-la.

Ouvrez le fichier Pages/Movies/Index.cshtml , puis ajoutez la balise mise en surbrillance


dans le code suivant :

CSHTML

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-page="Create">Create New</a>
</p>

<form>
<p>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>

<table class="table">
@*Markup removed for brevity.*@

La balise HTML <form> utilise les Tag Helpers suivants :

Tag Helper Form. Quand le formulaire est envoyé, la chaîne de filtrage est envoyée
à la page Pages/Movies/Index via la chaîne de requête.
Tag Helper Input

Enregistrez les modifications apportées, puis testez le filtre.

Rechercher par genre


Mettez à jour la méthode OnGetAsync de la page Movies/Index.cshtml.cs avec le code
suivant :

C#
public async Task OnGetAsync()
{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;

var movies = from m in _context.Movie


select m;

if (!string.IsNullOrEmpty(SearchString))
{
movies = movies.Where(s => s.Title.Contains(SearchString));
}

if (!string.IsNullOrEmpty(MovieGenre))
{
movies = movies.Where(x => x.Genre == MovieGenre);
}
Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
Movie = await movies.ToListAsync();
}

Le code suivant est une requête LINQ qui récupère tous les genres dans la base de
données.

C#

// Use LINQ to get list of genres.


IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;

La liste SelectList de genres est créée en projetant des différents genres.

C#

Genres = new SelectList(await genreQuery.Distinct().ToListAsync());

Ajoutez une recherche par genre à la Page Razor


Mettez à jour Index.cshtml <form>l’élément comme mis en évidence dans le balisage
suivant :

CSHTML
@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-page="Create">Create New</a>
</p>

<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>

Testez l’application en effectuant une recherche par genre, par titre de film et selon ces
deux critères.

Étapes suivantes
Précédent : Mettre à jour les pages Suivant : Ajouter un nouveau champ

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 7, ajouter un nouveau champ à
une page Razor dans ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Dans cette section, Migrations Entity Framework Code First est utilisé pour :

Ajouter un nouveau champ au modèle.


Migrer la nouvelle modification du schéma des champs vers la base de données.

Quand vous utilisez EF Code First pour créer et suivre automatiquement une base de
données, Code First :

Ajoute une table __EFMigrationsHistory à la base de données pour déterminer si le


schéma de la base de données est synchronisé avec les classes de modèle à partir
desquelles il a été généré.
Lève une exception si les classes de modèle ne sont pas synchronisées avec la base
de données.

La vérification automatique de la synchronisation du schéma et du modèle facilite la


recherche de problèmes de code de base de données incohérents.

Ajout d’une propriété Rating au modèle Movie


1. Ouvrez le fichier Models/Movie.cs et ajoutez une propriété Rating :

C#

public class Movie


{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;

[Column(TypeName = "decimal(18, 2)")]


public decimal Price { get; set; }
public string Rating { get; set; } = string.Empty;
}
2. Modifiez Pages/Movies/Index.cshtml , puis ajoutez un champ Rating :

CSHTML

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-page="Create">Create New</a>
</p>

<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>

<table class="table">

<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model =>
model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<a asp-page="./Edit" asp-route-
id="@item.Id">Edit</a> |
<a asp-page="./Details" asp-route-
id="@item.Id">Details</a> |
<a asp-page="./Delete" asp-route-
id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

3. Mettez à jour les pages suivantes avec un champ Rating :

Pages/Movies/Create.cshtml .
Pages/Movies/Delete.cshtml .
Pages/Movies/Details.cshtml .
Pages/Movies/Edit.cshtml .

L’application ne fonctionne pas tant que la base de données n’est pas mise à jour pour
inclure le nouveau champ. L’exécution de l’application sans mise à jour de la base de
données lève un SqlException :

SqlException: Invalid column name 'Rating'.

Cette exception SqlException est due au fait que la classe du modèle Film mise à jour
est différente du schéma de la table Film de la base de données. Il n’existe pas de
colonne Rating dans la table de base de données.

Plusieurs approches sont possibles pour résoudre l’erreur :

1. Laisser Entity Framework supprimer et recréer automatiquement la base de


données avec le nouveau schéma de classes du modèle. Cette approche est très
utile au début du cycle de développement. Elle permet au développeur de faire
évoluer rapidement le modèle et le schéma de base de données ensemble.
L’inconvénient est que les données existantes dans la base de données sont
perdues. N’utilisez pas cette approche sur une base de données de production ! La
suppression de la base de données lors des changements de schéma et l’utilisation
d’un initialiseur pour amorcer automatiquement la base de données avec des
données de test est souvent un moyen efficace pour développer une application.
2. Modifier explicitement le schéma de la base de données existante pour le faire
correspondre aux classes du modèle. L’avantage de cette approche est que vous
conservez les données. Apportez cette modification manuellement ou en créant un
script de modification de la base de données.
3. Utilisez les migrations Code First pour mettre à jour le schéma de base de
données.

Pour ce didacticiel, nous allons utiliser les migrations Code First.

Mettez à jour la classe SeedData pour qu’elle fournisse une valeur pour la nouvelle
colonne. Vous pouvez voir un exemple de modification ci-dessous, mais appliquez cette
modification à chaque bloc new Movie .

C#

context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M,
Rating = "R"
},

Consultez le fichier SeedData.cs complet .

Générez la solution.

Visual Studio

Ajouter une migration pour le champ d’évaluation


1. Dans le menu Outils, sélectionnez Gestionnaire de package NuGet > Console
du gestionnaire de package.
2. Dans la console du gestionnaire de package, entrez les commandes suivantes :

PowerShell

Add-Migration Rating
Update-Database

La commande Add-Migration indique au framework qu’il doit :

Comparer le modèle Movie au schéma de base de données Movie .


Créer du code pour migrer le schéma de base de données vers le nouveau
modèle.

Le nom « Rating » est arbitraire et utilisé pour nommer le fichier de migration. Il est
utile d’utiliser un nom explicite pour le fichier de migration.

La commande Update-Database indique à l’infrastructure d’appliquer les


modifications de schéma à la base de données et de conserver les données
existantes.

Supprimez tous les enregistrements de la base de données, l’initialiseur amorce la


base de données et inclut le champ Rating . La suppression peut ce faire en utilisant
les liens de suppression disponibles dans le navigateur ou à partir de Sql Server
Object Explorer (SSOX).

Une autre option consiste à supprimer la base de données et à utiliser des


migrations pour recréer la base de données. Pour supprimer la base de données
dans SSOX :

1. Sélectionnez la base de données dans SSOX.

2. Cliquez avec le bouton droit sur la base de données, puis sélectionnez


Supprimer.

3. Cochez Fermer les connexions existantes.

4. Sélectionnez OK.

5. Dans la console du gestionnaire de package, mettez à jour la base de données


:

PowerShell
Update-Database

Exécutez l’application et vérifiez que vous pouvez créer/modifier/afficher des films avec
un champ Rating . Si la base de données n’est pas amorcée, définissez un point d’arrêt
dans la méthode SeedData.Initialize .

Étapes suivantes
Précédent : Ajouter une recherche Suivant : Ajouter une validation

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 8 de la série de tutoriels sur Pages
Razor
Article • 01/02/2024

Par Rick Anderson

Dans cette section, une logique de validation est ajoutée au modèle Movie . Les règles
de validation sont appliquées chaque fois qu’un utilisateur crée ou modifie un film.

Validation
DRY (« Don't Repeat Yourself », Ne vous répétez pas) constitue un principe clé du
développement de logiciel. Les Pages Razor favorisent le développement dans lequel
une fonctionnalité est spécifiée une seule fois et sont répercutées dans toute
l’application. DRY peut aider à :

Réduire la quantité de code dans une application.


Rendre le code moins sujet aux erreurs, et plus facile à tester et à maintenir.

La prise en charge de la validation fournie par les Pages Razor et Entity Framework est
un bon exemple du principe DRY :

Les règles de validation sont spécifiées de manière déclarative au même endroit,


dans la classe de modèle.
Les règles sont appliquées partout dans l’application.

Ajouter des règles de validation au modèle de


film
L’espace de nom System.ComponentModel.DataAnnotations fournit :

Ensemble d’attributs de validation intégrés appliqués de manière déclarative à une


classe ou à une propriété.
Attributs de mise en forme comme [DataType] qui aident à effectuer la mise en
forme et ne fournissent aucune validation.

Mettez à jour la classe Movie pour tirer parti des attributs de validation intégrés
[Required] , [StringLength] , [RegularExpression] et [Range] .

C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }

[StringLength(60, MinimumLength = 3)]


[Required]
public string Title { get; set; } = string.Empty;

[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; } = string.Empty;

[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; } = string.Empty;
}

Les attributs de validation spécifient le comportement à appliquer sur les propriétés du


modèle auxquelles ils sont appliqués :

Les attributs [Required] et [MinimumLength] indiquent qu’une propriété doit avoir


une valeur. Rien n’empêche un utilisateur d’entrer des espaces blancs pour
satisfaire cette validation.

L’attribut [RegularExpression] sert à limiter les caractères pouvant être entrés.


Dans le code précédent, Genre :
Doit utiliser seulement des lettres.
La première lettre doit être une majuscule. Les espaces sont autorisés, tandis
que les nombres et les caractères spéciaux ne le sont pas.

RegularExpression Rating :

Nécessite que le premier caractère soit une lettre majuscule.


Autorise les caractères spéciaux et les chiffres aux emplacements qui suivent.
« PG-13 » est valide pour une évaluation, mais échoue pour un Genre .

L’attribut [Range] limite une valeur à une plage spécifiée.

L’attribut [StringLength] peut définir la longueur maximale d’une propriété de


chaîne, et éventuellement sa longueur minimale.

Les types valeur (tels que decimal , int , float , DateTime ) sont obligatoires par
nature et n’ont pas besoin de l’attribut [Required] .

Les règles de validation précédentes sont utilisées pour la démonstration, elles ne sont
pas optimales pour un système de production. Par exemple, le précédent empêche
d’entrer un film avec seulement deux caractères et n’autorise pas les caractères spéciaux
dans Genre .

L’application automatique des règles de validation par ASP.NET Core permet de :

Rendre l’application plus robuste.


Réduire les risques d’enregistrement de données non valides dans la base de
données.

Interface utilisateur d’erreur de validation dans les Pages


Razor
Exécutez l’application, puis accédez à Pages/Movies.

Sélectionnez le lien Créer nouveau. Complétez le formulaire avec des valeurs non
valides. Quand la validation jQuery côté client détecte l’erreur, elle affiche un message
d’erreur.
7 Notes

Vous ne pourrez peut-être pas entrer de virgules décimales dans les champs
décimaux. Pour prendre en charge la validation jQuery pour les paramètres
régionaux autres que l’anglais qui utilisent une virgule (« , ») comme décimale et
des formats de date autres que l’anglais des États-Unis, vous devez effectuer des
étapes pour localiser votre application. Consultez ce commentaire GitHub 4076
pour savoir comment ajouter une virgule décimale.

Notez que le formulaire a affiché automatiquement un message d’erreur de validation


dans chaque champ contenant une valeur non valide. Les erreurs sont appliquées à la
fois côté client à l’aide de JavaScript et de jQuery, et côté serveur quand JavaScript est
désactivé pour un utilisateur.
L’un des principaux avantages est qu’aucun changement de code n’a été nécessaire
dans les pages Créer ou Modifier. Une fois les annotations de données appliquées au
modèle, l’interface utilisateur de validation a été activée. Les Pages Movie créées dans ce
tutoriel ont prélevé les règles de validation à l’aide des attributs de validation définis sur
les propriétés de la classe de modèle Razor. Testez la validation à l’aide de la page de
modification. La même validation est appliquée.

Les données de formulaire ne sont pas publiées sur le serveur tant qu’il y a des erreurs
de validation côté client. Vérifiez que les données du formulaire ne sont pas publiées à
l’aide d’une ou de plusieurs des approches suivantes :

Placez un point d’arrêt dans la méthode OnPostAsync . Envoyez le formulaire en


sélectionnant Créer ou Enregistrer. Le point d’arrêt n’est jamais atteint.
Utilisez l’outil Fiddler .
Utilisez les outils de développement du navigateur pour surveiller le trafic réseau.

Validation côté serveur


Quand JavaScript est désactivé dans le navigateur, l’envoi du formulaire avec des erreurs
poste les données sur le serveur.

Facultatif : Testez la validation côté serveur :

1. Désactivez JavaScript dans le navigateur. JavaScript peut être désactivé à l’aide des
outils de développement du navigateur. Si JavaScript ne peut pas être désactivé
dans le navigateur, essayez un autre navigateur.

2. Définissez un point d’arrêt dans la méthode OnPostAsync de la page Créer ou


Modifier.

3. Envoyez un formulaire avec des données non valides.

4. Vérifiez que l’état de modèle n’est pas valide :

C#

if (!ModelState.IsValid)
{
return Page();
}

Vous pouvez également désactiver la validation côté client sur le serveur.


Le code suivant affiche la partie de la page Create.cshtml générée automatiquement
plus tôt dans le tutoriel. Elle est utilisée par les pages Créer et Modifier pour :

Affichez le formulaire initial.


Réafficher le formulaire en cas d’erreur.

CSHTML

<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>

Le Tag Helper d’entrée utilise les attributs DataAnnotations et produit les attributs HTML
nécessaires à la validation jQuery côté client. Le Tag Helper Validation affiche les erreurs
de validation. Pour plus d’informations, consultez Validation.

Les pages Créer et Modifier ne contiennent pas de règles de validation. Les règles de
validation et les chaînes d’erreur sont spécifiées uniquement dans la classe Movie . Ces
règles de validation sont automatiquement appliquées aux pages Razorqui modifient le
modèle Movie .

Quand la logique de validation doit être modifiée, cela s’effectue uniquement dans le
modèle. La validation est appliquée de manière cohérente dans l’ensemble de
l’application. La logique de validation est définie dans un emplacement unique. La
validation dans un emplacement unique permet de maintenir votre code clair, et d’en
faciliter la maintenance et la mise à jour.

Utiliser des attributs DataType


Examiner la classe Movie . L’espace de noms System.ComponentModel.DataAnnotations
fournit des attributs de mise en forme en plus de l’ensemble intégré d’attributs de
validation. L’attribut [DataType] est appliqué aux propriétés ReleaseDate et Price .

C#

[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

Les attributs [DataType] fournissent :

Conseils pour le moteur d’affichage pour mettre en forme les données.


Fournit des attributs tels que <a> pour l’URL et <a
href="mailto:EmailAddress.com"> pour l’e-mail.

Utilisez l’attribut [RegularExpression] pour valider le format des données. L’attribut


[DataType] sert à spécifier un type de données qui est plus spécifique que le type

intrinsèque de la base de données. Les attributs [DataType] ne sont pas des attributs de
validation. Dans l’échantillon d’application, seule la date est affichée, sans l’heure.

L’énumération DataType fournit de nombreux types de données, tels que Date , Time ,
PhoneNumber , Currency , EmailAddress , et bien plus encore.

Les attributs [DataType] :

Peut permettre à l’application de fournir automatiquement des fonctionnalités


propres au type. Par exemple, il est possible de créer un lien mailto: pour
DataType.EmailAddress .

Peuvent fournir un sélecteur de date DataType.Date dans les navigateurs qui


prennent en charge HTML5.
Émettent HTML 5 data- , prononcé « tiret de données », attributs utilisés par les
navigateurs HTML 5.
Ne fournissent aucune validation.

DataType.Date ne spécifie pas le format de la date qui s’affiche. Par défaut, le champ de

données est affiché conformément aux formats par défaut basés sur le CultureInfo du
serveur.

L’annotation de données [Column(TypeName = "decimal(18, 2)")] est nécessaire pour


qu’Entity Framework Core puisse correctement mapper Price en devise dans la base de
données. Pour plus d’informations, consultez Types de données.

L’attribut [DisplayFormat] est utilisé pour spécifier explicitement le format de date :

C#

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode =


true)]
public DateTime ReleaseDate { get; set; }
Le paramètre ApplyFormatInEditMode indique que la mise en forme sera appliquée
quand la valeur est affichée à des fins de modification. Ce comportement n’est peut-être
pas souhaité pour certains champs. Par exemple, dans les valeurs monétaires, le symbole
monétaire n’est généralement pas voulu dans l’interface utilisateur de modification.

L’attribut [DisplayFormat] peut être utilisé seul, mais il est généralement préférable
d’utiliser l’attribut [DataType] . L’attribut [DataType] transmet la sémantique des
données, plutôt que la manière de l’afficher à l’écran. L’attribut [DataType] offre les
avantages suivants qui ne sont pas disponibles avec [DisplayFormat] :

Le navigateur peut activer des fonctionnalités HTML5, par exemple pour afficher
un contrôle de calendrier, le symbole monétaire correspondant aux paramètres
régionaux, des liens de messagerie, etc.
Par défaut, le navigateur affiche les données à l’aide du format correspondant aux
paramètres régionaux.
L’attribut [DataType] peut permettre à l’infrastructure ASP.NET Core de choisir le
modèle de champ approprié pour afficher les données. S’il est utilisé seul,
DisplayFormat utilise le modèle de chaîne.

Remarque : La validation jQuery ne fonctionne pas avec l’attribut [Range] et DateTime .


Par exemple, le code suivant affiche toujours une erreur de validation côté client, même
quand la date se trouve dans la plage spécifiée :

C#

[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]

Il recommandé d’éviter de compiler des dates en dur dans des modèles. Par conséquent,
l’utilisation de l’attribut [Range] et de DateTime est déconseillée. Utilisez Configuration
pour les plages de dates et d’autres valeurs qui sont sujettes à des modifications
fréquentes au lieu de les spécifier dans le code.

Le code suivant illustre la combinaison d’attributs sur une seule ligne :

C#

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; } = string.Empty;

[Display(Name = "Release Date"), DataType(DataType.Date)]


public DateTime ReleaseDate { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$"), Required, StringLength(30)]


public string Genre { get; set; } = string.Empty;

[Range(1, 100), DataType(DataType.Currency)]


[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$"), StringLength(5)]
public string Rating { get; set; } = string.Empty;
}

Prise en main des Pages Razor et EF Core affiche les opérations avancées EF Core avec
Pages Razor.

Appliquer des migrations


L’application de DataAnnotations à la classe modifie le schéma. Par exemple,
DataAnnotations appliqué au champ Title :

C#

[StringLength(60, MinimumLength = 3)]


[Required]
public string Title { get; set; } = string.Empty;

limite les caractères à 60 ;


n’autorise pas de valeur null .

La table Movie a actuellement le schéma suivant :

SQL

CREATE TABLE [dbo].[Movie] (


[ID] INT IDENTITY (1, 1) NOT NULL,
[Title] NVARCHAR (MAX) NULL,
[ReleaseDate] DATETIME2 (7) NOT NULL,
[Genre] NVARCHAR (MAX) NULL,
[Price] DECIMAL (18, 2) NOT NULL,
[Rating] NVARCHAR (MAX) NULL,
CONSTRAINT [PK_Movie] PRIMARY KEY CLUSTERED ([ID] ASC)
);
Les modifications précédentes du schéma n’entraînent pas la levée d’une exception par
EF. Cependant, créez une migration pour que le schéma soit cohérent avec le modèle.

Visual Studio

Dans le menu Outils, sélectionnez Gestionnaire de package NuGet > Console du


gestionnaire de package. Dans la console du gestionnaire de package, entrez les
commandes suivantes :

PowerShell

Add-Migration New_DataAnnotations
Update-Database

Update-Database exécute la méthode Up de la classe New_DataAnnotations .

Examinez la méthode Up :

C#

public partial class New_DataAnnotations : Migration


{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Title",
table: "Movie",
type: "nvarchar(60)",
maxLength: 60,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");

migrationBuilder.AlterColumn<string>(
name: "Rating",
table: "Movie",
type: "nvarchar(5)",
maxLength: 5,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");

migrationBuilder.AlterColumn<string>(
name: "Genre",
table: "Movie",
type: "nvarchar(30)",
maxLength: 30,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");
}

La table Movie mise à jour a le schéma suivant :

SQL

CREATE TABLE [dbo].[Movie] (


[ID] INT IDENTITY (1, 1) NOT NULL,
[Title] NVARCHAR (60) NOT NULL,
[ReleaseDate] DATETIME2 (7) NOT NULL,
[Genre] NVARCHAR (30) NOT NULL,
[Price] DECIMAL (18, 2) NOT NULL,
[Rating] NVARCHAR (5) NOT NULL,
CONSTRAINT [PK_Movie] PRIMARY KEY CLUSTERED ([ID] ASC)
);

Publication dans Azure


Pour plus d’informations sur le déploiement sur Azure, consultez Tutoriel : Créer une
application ASP.NET Core dans Azure avec SQL Database.

Nous vous remercions d’avoir effectué cette introduction aux pages Razor. Pour
compléter ce tutoriel, vous pouvez consulter Bien démarrer avec les Pages Razor et EF
Core.

Ressources supplémentaires
Tag Helpers dans les formulaires dans ASP.NET Core
Globalisation et localisation dans ASP.NET Core
Tag Helpers dans ASP.NET Core
Créer des Tag Helpers dans ASP.NET Core

Étapes suivantes
Précédent : Ajouter un nouveau champ

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Bien démarrer avec ASP.NET Core MVC
Article • 30/11/2023

Par Rick Anderson

Ce didacticiel vous permet de découvrir le développement web ASP.NET Core MVC avec
des contrôleurs et des vues. Si vous débutez dans le développement web ASP.NET Core,
choisissez la version RazorRazor Pages de ce didacticiel, qui fournit un point de départ
plus facile. Consultez Choisir une interface utilisateur ASP.NET Core, qui compare Razor
Pages, MVC et Blazor pour le développement de l’interface utilisateur.

Il s’agit du premier didacticiel d’une série qui enseigne le développement web ASP.NET
Core MVC avec des contrôleurs et des vues.

À la fin de cette série, vous disposerez d’une application permettant de gérer et


d’afficher des données de film. Vous allez apprendre à effectuer les actions suivantes :

" Créez une application web.


" Ajouter et structurer un modèle.
" Utiliser une base de données.
" Ajouter une fonctionnalité de recherche et de validation.

Affichez ou téléchargez un exemple de code (procédure de téléchargement).

Prérequis
Visual Studio

Préversion de Visual Studio 2022 avec la charge de travail ASP.NET et


développement web.
Créer une application web
Visual Studio

Démarrez Visual Studio et sélectionnez Créer un projet.


Dans la boîte de dialogue Créer un nouveau projet, sélectionnez Application
web ASP.NET Core (modèle-vue-contrôleur)>Suivant.
Dans la boîte de dialogue Configurer votre nouveau projet, entrez MvcMovie
pour Nom du projet. Il est important de nommer le projet MvcMovie. La mise
en majuscules doit correspondre à chaque namespace quand le code est copié.
Cliquez sur Suivant.
Dans la boîte de dialogue Informations supplémentaires :
Sélectionnez .NET 8.0 (support à long terme).
Vérifiez que l’option ne pas utiliser les instructions de niveau supérieur
soit décochée.
Cliquez sur Créer.
Pour plus d’informations, notamment d’autres approches pour créer le projet,
consultez Créer un projet dans Visual Studio.

Visual Studio utilise le modèle de projet par défaut pour le projet MVC créé. Le
projet créé :

Est une application opérationnelle.


Est un projet de démarrage de base.

Exécuter l'application

Visual Studio

Sélectionnez Ctrl+F5 pour exécuter l’application sans le débogueur.

Visual Studio affiche la boîte de dialogue suivante lorsqu’un projet n’est pas
encore configuré pour utiliser SSL :
Sélectionnez Oui si vous faites confiance au certificat SSL d’IIS Express.

La boîte de dialogue suivante s’affiche :

Sélectionnez Oui si vous acceptez d’approuver le certificat de développement.

Pour plus d’informations sur l’approbation du navigateur Firefox, consultez


Erreur de certificat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.

Visual Studio exécute l’application et ouvre le navigateur par défaut.

La barre d’adresses affiche localhost:<port#> au lieu de quelque chose qui


ressemble à example.com . Le nom d’hôte standard de votre ordinateur local est
localhost . Quand Visual Studio crée un projet web, un port aléatoire est utilisé

pour le serveur web.


Le lancement de l’application sans débogage en sélectionnant Ctrl+F5 vous permet
de :

Modifiez le code.
Enregistrez le fichier.
Actualiser rapidement le navigateur et voir les modifications de code.

Vous pouvez lancer l’application en mode débogage ou non-débogage à partir du


menu Déboguer :

Vous pouvez déboguer l’application en sélectionnant le bouton https dans la barre


d’outils :
L’image suivante montre l’application :

Visual Studio

Aide de Visual Studio


Apprendre à déboguer le code C# avec Visual Studio
Introduction à l’IDE Visual Studio

Dans le prochain didacticiel de la série, vous allez découvrir MVC et commencer à écrire
du code.

Suivant : Ajouter un contrôleur

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus
documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 2, ajouter un contrôleur à une
application ASP.NET Core MVC
Article • 30/11/2023

Par Rick Anderson

Le modèle d’architecture MVC (Model-View-Controller) sépare une application en trois


composants principaux : Modèle, Vue et Contrôleur. Le modèle MVC vous permet de
créer des applications qui sont plus faciles à tester et à mettre à jour que les applications
monolithiques traditionnelles.

Les applications basées sur MVC contiennent :

Des Modèles : des classes qui représentent les données de l’application. Les classes
du modèle utilisent une logique de validation pour appliquer des règles
d’entreprise à ces données. En règle générale, les objets du modèle récupèrent et
stockent l’état du modèle dans une base de données. Dans ce didacticiel, un
modèle Movie récupère les données des films dans une base de données, les
fournit à la vue ou les met à jour. Les données mises à jour sont écrites dans une
base de données.
Vues : les vues sont les composants qui affichent l’interface utilisateur de
l’application. En règle générale, cette interface utilisateur affiche les données du
modèle.
Contrôleurs : Classes qui :
Gèrent les demandes de navigateur.
Récupèrent les données du modèle.
Appellent les modèles d’affichage qui retournent une réponse.

Dans une application MVC, la vue affiche uniquement des informations. Le contrôleur
gère et répond à l’entrée et à l’interaction utilisateur. Par exemple, le contrôleur gère les
valeurs des données de routage et des chaînes de requête, et passe ces valeurs au
modèle. Le modèle peut utiliser ces valeurs pour interroger la base de données. Par
exemple :

https://localhost:5001/Home/Privacy : spécifie le contrôleur Home et l’action

Privacy .

https://localhost:5001/Movies/Edit/5 : est une requête de modification du film


avec ID=5 à l’aide du contrôleur Movies et de l’action Edit , qui sont détaillées plus
loin dans le tutoriel.

Les données d’itinéraire sont expliquées plus loin dans le didacticiel.


Le modèle d’architecture MVC sépare une application en trois groupes de composants
principaux : les modèles, les vues et les contrôleurs. Ce modèle permet de séparer les
problèmes : La logique d’interface utilisateur appartient à la vue. La logique d’entrée
appartient au contrôleur. La logique métier appartient au modèle. Cette séparation vous
aide à gérer la complexité quand vous créez une application, car elle vous permet de
travailler sur un aspect de l’implémentation à la fois, sans impacter le code d’un autre
aspect. Par exemple, vous pouvez travailler sur le code des vues de façon indépendante
du code de la logique métier.

Ces concepts sont présentés et démontrés dans cette série de tutoriels lors de la
création d’une application de film. Le projet MVC contient des dossiers pour les
contrôleurs et pour les vues.

Ajout d'un contrôleur


Visual Studio

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur Contrôleurs >
Ajouter > Contrôleur.

Dans la boîte de dialogue Ajouter un nouvel élément généré automatiquement,


sélectionnez Contrôleur MVC - Vide>Ajouter.
Dans la boîte de dialogue Ajouter un nouvel élément - MvcMovie, entrez
HelloWorldController.cs et sélectionnez Ajouter.

Remplacez le contenu de Controllers/HelloWorldController.cs par le code suivant :

C#

using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;

namespace MvcMovie.Controllers;

public class HelloWorldController : Controller


{
//
// GET: /HelloWorld/
public string Index()
{
return "This is my default action...";
}
//
// GET: /HelloWorld/Welcome/
public string Welcome()
{
return "This is the Welcome action method...";
}
}
Chaque méthode public d’un contrôleur peut être appelée en tant que point de
terminaison HTTP. Dans l’exemple ci-dessus, les deux méthodes retournent une chaîne.
Notez les commentaires qui précèdent chaque méthode.

Un point de terminaison HTTP :

URL pouvant être ciblée dans l’application web, telle que


https://localhost:5001/HelloWorld .

Combine :
Protocole utilisé : HTTPS .
Emplacement réseau du serveur web, y compris le port TCP : localhost:5001 .
URI cible : HelloWorld .

Le premier commentaire indique qu’il s’agit d’une méthode HTTP GET qui est appelée
en ajoutant /HelloWorld/ à l’URL de base.

Le deuxième commentaire indique une méthode HTTP GET qui est appelée en
ajoutant /HelloWorld/Welcome/ à l’URL. Plus loin dans ce tutoriel, le moteur de
génération de modèles automatique est utilisé pour générer des méthodes HTTP POST
qui mettent à jour des données.

Exécutez l’application sans le débogueur.

Ajoutez /HelloWorld au chemin dans la barre d’adresse. La méthode Index retourne


une chaîne.

MVC appelle les classes du contrôleur et les méthodes d’action au sein de celles-ci en
fonction de l’URL entrante. La logique de routage d’URL par défaut utilisée par le
modèle MVC utilise un format comme celui-ci pour déterminer le code à appeler :

/[Controller]/[ActionName]/[Parameters]
Le format de routage est défini dans le fichier Program.cs .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

Quand vous naviguez jusqu’à l’application et que vous ne fournissez aucun segment
d’URL, sa valeur par défaut est le contrôleur « Home » et la méthode « Index » spécifiée
dans la ligne du modèle mise en surbrillance ci-dessus. Dans les segments d’URL
précédents :

Le premier segment d’URL détermine la classe du contrôleur à exécuter.


localhost:5001/HelloWorld mappe donc à la classe de contrôleur HelloWorld.

La seconde partie du segment d’URL détermine la méthode d’action sur la classe.


Ainsi, localhost:5001/HelloWorld/Index provoque l’exécution de la méthode Index
de la classe HelloWorldController . Notez que vous n’avez eu qu’à accéder à
localhost:5001/HelloWorld pour que la méthode Index soit appelée par défaut.
Index est la méthode par défaut qui est appelée sur un contrôleur si un nom de

méthode n’est pas explicitement spécifié.


La troisième partie du segment d’URL ( id ) concerne les données de routage. Les
données d’itinéraire sont expliquées plus loin dans le didacticiel.

Accédez à : https://localhost:{PORT}/HelloWorld/Welcome . Remplacez {PORT} par votre


numéro de port.

La méthode Welcome s’exécute et retourne la chaîne This is the Welcome action


method... . Pour cette URL, le contrôleur est HelloWorld , et Welcome est la méthode

d’action. Vous n’avez pas encore utilisé la partie [Parameters] de l’URL.


Modifiez le code pour passer des informations sur les paramètres de l’URL au
contrôleur. Par exemple : /HelloWorld/Welcome?name=Rick&numtimes=4 .

Modifiez la méthode Welcome en y incluant les deux paramètres, comme indiqué dans le
code suivant.

C#

// GET: /HelloWorld/Welcome/
// Requires using System.Text.Encodings.Web;
public string Welcome(string name, int numTimes = 1)
{
return HtmlEncoder.Default.Encode($"Hello {name}, NumTimes is:
{numTimes}");
}

Le code précédent :

Utilise la fonctionnalité de paramètre facultatif de C# pour indiquer que le


paramètre numTimes a 1 comme valeur par défaut si aucune valeur n’est passée
pour ce paramètre.
Utilise HtmlEncoder.Default.Encode pour protéger l’application contre les entrées
malveillantes, par exemple via JavaScript.
Utilise des chaînes interpolées dans $"Hello {name}, NumTimes is: {numTimes}" .

Exécutez l’application et accédez à : https://localhost:{PORT}/HelloWorld/Welcome?


name=Rick&numtimes=4 . Remplacez {PORT} par votre numéro de port.
Essayez différentes valeurs pour name et numtimes dans l’URL. Le système de liaison de
données MVC mappe automatiquement les paramètres nommés provenant de la chaîne
de requête aux paramètres de la méthode. Pour plus d’informations, consultez Liaison
de données.

Dans l’image précédente :

Le segment Parameters d’URL n’est pas utilisé.


Les paramètres name et numTimes sont transmis dans la chaîne de requête .
Le ? (point d’interrogation) dans l’URL ci-dessus est un séparateur, qui est suivi
d’une chaîne de requête.
Le caractère & sépare les paires champ-valeur.

Remplacez la méthode Welcome par le code suivant :

C#

public string Welcome(string name, int ID = 1)


{
return HtmlEncoder.Default.Encode($"Hello {name}, ID: {ID}");
}

Exécutez l’application et entrez l’URL suivante : https://localhost:


{PORT}/HelloWorld/Welcome/3?name=Rick

Dans l’URL précédente :

Le troisième segment de l’URL correspondait au paramètre de routage id .


La méthode Welcome contient un paramètre id qui correspondait au modèle
d’URL de la méthode MapControllerRoute .
Le ? de fin démarre la chaîne de requête .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

Dans l’exemple précédent :

Le troisième segment de l’URL correspondait au paramètre de routage id .


La méthode Welcome contient un paramètre id qui correspondait au modèle
d’URL de la méthode MapControllerRoute .
Le ? de fin (dans id? ) indique que le paramètre id est facultatif.

Précédent : Bien démarrer Suivant : Ajouter une vue

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 3, Ajouter une vue à une
application ASP.NET Core MVC
Article • 30/11/2023

Par Rick Anderson

Dans cette section, vous modifiez la classe HelloWorldController pour utiliser des
fichiers de vue Razor. Cela encapsule proprement le processus de génération de
réponses HTML à un client.

Les modèles de vue sont créés avec Razor. Les modèles de vue basés sur Razor :

Ont une extension de fichier .cshtml .


Offrent un moyen élégant pour créer une sortie HTML avec C#.

Actuellement, la méthode Index retourne une chaîne avec un message dans la classe du
contrôleur. Dans la classe HelloWorldController , remplacez la méthode Index par le
code suivant :

C#

public IActionResult Index()


{
return View();
}

Le code précédent :

Appel de la méthode View du contrôleur.


Utilise un modèle de vue pour générer une réponse HTML.

Les méthodes du contrôleur :

Sont appelées méthodes d’action. Par exemple, la méthode d’action Index dans le
code précédent.
Retournent généralement un IActionResult ou une classe dérivée de ActionResult,
et non un type comme string .

Ajouter une vue


Visual Studio
Cliquez avec le bouton droit sur le dossier Vues, cliquez sur Ajouter > Nouveau
dossier, puis nommez le dossier HelloWorld.

Cliquez avec le bouton droit sur le dossier Vues/HelloWorld, puis cliquez sur Ajouter
> Nouvel élément.

Dans la boîte de dialogue Ajouter un nouvel élément, sélectionnez Afficher tous


les modèles.

Dans la boîte de dialogue Ajouter un nouvel élément - MvcMovie :

Dans la zone de recherche située en haut à droite, entrez vue


Sélectionnez Vue Razor - Vide
Conservez la valeur de la zone Nom, Index.cshtml .
Sélectionnez Ajouter

Remplacez le contenu du fichier de vue Views/HelloWorld/Index.cshtml Razor par ce qui


suit :

CSHTML

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>
<p>Hello from our View Template!</p>

Accédez à https://localhost:{PORT}/HelloWorld :

La méthode Index dans HelloWorldController a exécuté l’instruction return


View(); , laquelle spécifiait que la méthode doit utiliser un fichier de modèle de vue

pour restituer une réponse au navigateur.

Aucun nom de fichier de modèle de vue n’ayant été spécifié, MVC utilise le fichier
d’affichage par défaut. Lorsque le nom du fichier de vue n’est pas spécifié, la vue
par défaut est retournée. Dans cet exemple, la vue par défaut porte le même nom
que la méthode d’action Index . Le modèle de vue
/Views/HelloWorld/Index.cshtml est utilisé.

L’image suivante montre la chaîne « Hello from our View Template! » codée en dur
dans la vue :

Changer les vues et les pages de disposition


Sélectionnez les liens de menu MvcMovie, Home et Privacy. Chaque page affiche la
même disposition de menu. La disposition du menu est implémentée dans le fichier
Views/Shared/_Layout.cshtml .

Ouvrez le fichier Views/Shared/_Layout.cshtml .

Les modèles de disposition permettent de :


Spécification de la disposition de conteneur HTML d’un site à un emplacement.
Application de la disposition de conteneur HTML sur plusieurs pages du site.

Recherchez la ligne @RenderBody() . RenderBody est un espace réservé dans lequel toutes
les pages spécifiques aux vues que vous créez s’affichent, encapsulées dans la page de
disposition. Par exemple, si vous sélectionnez le lien Privacy, la vue
Views/Home/Privacy.cshtml est affichée à l’intérieur de la méthode RenderBody .

Changer le lien de titre, de pied de page et de


menu dans le fichier de disposition
Remplacez le contenu du fichier Views/Shared/_Layout.cshtml par le balisage suivant.
Les modifications apparaissent en surbrillance :

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Movie App</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-
light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Movies"
asp-action="Index">Movie App</a>
<button class="navbar-toggler" type="button" data-bs-
toggle="collapse" data-bs-target=".navbar-collapse" aria-
controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle
navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex
justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">


<div class="container">
&copy; 2023 - Movie App - <a asp-area="" asp-controller="Home"
asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Le balisage précédent apporte les modifications suivantes :

Trois occurrences de MvcMovie à Movie App .


Remplacement de l’élément d’ancrage <a class="navbar-brand" asp-area="" asp-
controller="Home" asp-action="Index">MvcMovie</a> par <a class="navbar-brand"

asp-controller="Movies" asp-action="Index">Movie App</a> .

Dans le balisage précédent, l’ asp-area="" attribut Tag Helper d’ancrage et la valeur


d’attribut ont été omis, car cette application n’utilise pas de zones.

Remarque : Le contrôleur Movies n’a pas été implémenté. À ce stade, le lien Movie App
ne fonctionne pas.

Enregistrez les modifications et sélectionnez le lien Privacy. Notez comment le titre de


l’onglet de navigateur affiche Stratégie Privacy - Movie App au lieu de Stratégie Privacy
- MvcMovie
Sélectionnez le lien Home.

Notez que le titre et le texte d’ancrage affichent Movie App. Les changements ont été
apportés dans le modèle de disposition et ont le nouveau texte de lien et le nouveau
titre reflétés sur toutes les pages du site.

Examinez le fichier Views/_ViewStart.cshtml :

CSHTML

@{
Layout = "_Layout";
}

Le fichier Views/_ViewStart.cshtml apporte le fichier Views/Shared/_Layout.cshtml à


chaque vue. La propriété Layout peut être utilisée pour définir un mode de disposition
différent ou lui affecter la valeur null . Aucun fichier de disposition n’est donc utilisé.

Ouvrez le fichier de vue Views/HelloWorld/Index.cshtml .

Modifiez le titre et l’élément <h2> comme indiqué dans ce qui suit :

CSHTML

@{
ViewData["Title"] = "Movie List";
}

<h2>My Movie List</h2>

<p>Hello from our View Template!</p>


Le titre et l’élément <h2> sont légèrement différents afin que vous puissiez voir
clairement quel morceau du code modifie l’affichage.

Dans le code ci-dessus, ViewData["Title"] = "Movie List"; définit la propriété Title


du dictionnaire ViewData sur « Movie List ». La propriété Title est utilisée dans
l’élément HTML <title> dans la page de disposition :

CSHTML

<title>@ViewData["Title"] - Movie App</title>

Enregistrez la modification et accédez à https://localhost:{PORT}/HelloWorld .

Notez que les éléments suivants ont changé :

Titre du navigateur.
Titre principal.
En-têtes secondaires.

S’il n’y a aucune modification dans le navigateur, il peut s’agir du contenu en cache qui
est en cours d’affichage. Appuyez sur Ctrl+F5 dans le navigateur pour forcer le
chargement de la réponse du serveur. Le titre du navigateur est créé avec la valeur
ViewData["Title"] que nous avons définie dans le modèle de vue Index.cshtml et la

chaîne « - Movie App » ajoutée dans le fichier de disposition.

Le contenu du modèle de vue Index.cshtml est fusionné avec le modèle de vue


Views/Shared/_Layout.cshtml . Une réponse HTML unique est envoyée au navigateur. Les

modèles de disposition permettent d’apporter facilement des modifications qui


s’appliquent à toutes les pages d’une application. Pour en savoir plus, consultez
Disposition.
Le petit morceau de « données », le message « Hello from our View Template! », est
toutefois codé en dur. L’application MVC a une vue (« V») et un contrôleur (« C »), mais
pas encore de modèle (« M »).

Passage de données du contrôleur vers la vue


Les actions du contrôleur sont appelées en réponse à une demande d’URL entrante. Une
classe de contrôleur est l’endroit où le code est écrit et qui gère les demandes du
navigateur entrantes. Le contrôleur récupère les données d’une source de données et
détermine le type de réponse à envoyer au navigateur. Il est possible d’utiliser des
modèles de vue à partir d’un contrôleur pour générer et mettre en forme une réponse
HTML au navigateur.

Les contrôleurs sont chargés de fournir les données nécessaires pour qu’un modèle de
vue restitue une réponse.

Les modèles de vue ne doivent pas :

Effectuer une logique métier.


Interagir directement avec une base de données.

Un modèle de vue doit fonctionner uniquement avec les données que le contrôleur lui
fournit. Préserver cette « séparation des intérêts » permet de maintenir le code :

Propre.
Testable.
Maintenable.
Actuellement, la méthode Welcome de la classe HelloWorldController prend un name et
un paramètre ID , puis génère les valeurs directement dans le navigateur.

Au lieu que le contrôleur restitue cette réponse sous forme de chaîne, changez le
contrôleur pour qu’il utilise un modèle de vue à la place. Comme le modèle de vue
génère une réponse dynamique, les données appropriées doivent être passées du
contrôleur à la vue pour générer la réponse. Pour cela, le contrôleur doit placer les
données dynamiques (paramètres) dont le modèle de vue a besoin dans un dictionnaire
ViewData . Le modèle de vue peut ensuite accéder aux données dynamiques.

Dans HelloWorldController.cs , modifiez la méthode Welcome pour ajouter un Message


et une valeur NumTimes au dictionnaire ViewData .

Le dictionnaire ViewData est un objet dynamique, ce qui signifie que n’importe quel
type peut être utilisé. L’objet ViewData ne possède aucune propriété définie tant que
vous ne placez pas d’élément dedans. Le système de liaison de données MVC mappe
automatiquement les paramètres nommés ( name et numTimes ) provenant de la chaîne
de requête aux paramètres de la méthode. Le HelloWorldController complet :

C#

using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;

namespace MvcMovie.Controllers;

public class HelloWorldController : Controller


{
public IActionResult Index()
{
return View();
}
public IActionResult Welcome(string name, int numTimes = 1)
{
ViewData["Message"] = "Hello " + name;
ViewData["NumTimes"] = numTimes;
return View();
}
}

L’objet dictionnaire ViewData contient des données qui seront passées à la vue.

Créez un modèle de vue d’accueil nommé Views/HelloWorld/Welcome.cshtml .

Vous allez créer une boucle dans le modèle de vue Welcome.cshtml qui affiche « Hello »
NumTimes . Remplacez le contenu de Views/HelloWorld/Welcome.cshtml par ce qui suit :
CSHTML

@{
ViewData["Title"] = "Welcome";
}

<h2>Welcome</h2>

<ul>
@for (int i = 0; i < (int)ViewData["NumTimes"]!; i++)
{
<li>@ViewData["Message"]</li>
}
</ul>

Enregistrez vos modifications et accédez à l’URL suivante :

https://localhost:{PORT}/HelloWorld/Welcome?name=Rick&numtimes=4

Les données sont extraites de l’URL et passées au contrôleur à l’aide du classeur de


modèles MVC. Le contrôleur empaquette les données dans un dictionnaire ViewData et
passe cet objet à la vue. La vue restitue ensuite les données au format HTML dans le
navigateur.

Dans l’exemple précédent, le dictionnaire ViewData a été utilisé pour passer des
données du contrôleur à une vue. Plus loin dans ce didacticiel, un modèle de vue est
utilisé pour passer les données d’un contrôleur à une vue. L’approche basée sur le
modèle de vue pour passer des données est préférée à l’approche basée sur le
dictionnaire ViewData .

Dans le didacticiel suivant, une base de données de films est créée.

Précédent : Ajouter un contrôleur Suivant : Ajouter un modèle


6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 4, ajouter un modèle dans une
application ASP.NET Core MVC
Article • 05/01/2024

Par Rick Anderson et Jon P Smith .

Dans ce tutoriel, des classes sont ajoutées pour la gestion des films dans une base de
données. Ces classes constituent la partie « Modèle » de l’application MVC.

Ces classes de modèle sont utilisées avec Entity Framework Core (EF Core) pour utiliser
une base de données. EF Core est une infrastructure de mappage relationnel d’objets
qui simplifie le code d’accès aux données à écrire.

Les classes du modèle créées sont appelées classes POCO, à partir de Plain Old CLR
Objects. Les classes POCO n’ont aucune dépendance vis-à-vis de EF Core. Elles
définissent uniquement les propriétés des données qui seront stockées dans la base de
données.

Dans ce tutoriel, les classes du modèle sont d’abord créées, puis EF Core crée la base de
données.

Ajouter une classe de modèle de données


Visual Studio

Cliquez avec le bouton droit sur le dossier Modèles>Ajouter>Classe. Nommez le


fichier Movie.cs .

Mettez à jour le fichier Models/Movie.cs avec le code suivant :

C#

using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
public string? Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
public decimal Price { get; set; }
}

La classe Movie contient un champ Id , qui est nécessaire à la base de données pour la
clé primaire.

L’attribut DataType de ReleaseDate spécifie le type de données ( Date ). Avec cet


attribut :

L’utilisateur n’est pas obligé d’entrer les informations de temps dans le champ de
date.
Seule la date est affichée, pas les informations de temps.

Les DataAnnotations sont traitées dans un prochain didacticiel.

La présence du point d’interrogation après string indique que la propriété peut


accepter les valeurs Null. Pour plus d’informations, consultez Types référence pouvant
accepter la valeur Null.

Ajouter des packages NuGet


Visual Studio

Visual Studio installe automatiquement les packages requis.

Générez le projet en tant que vérification des erreurs du compilateur.

Générer automatiquement des pages de film


Utilisez l’outil de génération de modèles automatique pour Create , Read , Update , et
Delete des pages du modèle de film.

Visual Studio

Cliquez avec le bouton droit sur le dossier Contrôleurs dans l’Explorateur de


solutions, puis sélectionnez Ajouter > Nouvel élément généré automatiquement.
Dans la boîte de dialogue Ajouter un nouvel élément de modèle généré
automatiquement :

Dans le volet gauche, sélectionnez Installé>Communes>MVC.


Sélectionnez Contrôleur MVC avec vues, utilisant Entity Framework.
Cliquez sur Ajouter.
Terminez la boîte de dialogue Ajouter un contrôleur MVC avec des vues, à l’aide
d’Entity Framework :

Dans la liste déroulante Classe du modèle, sélectionnez Film


(MvcMovie.Models).
Dans la ligne Classe du contexte de données, sélectionnez le signe + (plus).
Dans la boîte de dialogue Ajouter un contexte de données, le nom de
classe MvcMovie.Data.MvcMovieContext est généré.
Cliquez sur Ajouter.
Dans la liste déroulante Fournisseur de base de données, sélectionnez SQL
Server.
Vues et Nom du contrôleur : conservez la valeur par défaut.
Cliquez sur Ajouter.
Si vous obtenez un message d’erreur, sélectionnez Ajouter une deuxième fois pour
réessayer.

La génération de modèles automatique ajoute les packages suivants :

Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools

Microsoft.VisualStudio.Web.CodeGeneration.Design

La génération automatique crée les éléments suivants :

Un contrôleur de films : Controllers/MoviesController.cs


Razor afficher les fichiers pour les pages Créer, Supprimer, Détails, Modifier
et Indexer : Views/Movies/*.cshtml
Une classe de contexte de base de données : Data/MvcMovieContext.cs

La génération de modèles automatique met à jour les éléments suivants :

Insère les références de package requises dans le fichier projet


MvcMovie.csproj .

Inscrit le contexte de base de données dans le fichier Program.cs .


Ajoute une chaîne de connexion de base de données au fichier
appsettings.json .
La création automatique de ces fichiers et mises à jour est appelée génération de
modèles automatique.

Les pages générées automatiquement ne peuvent pas encore être utilisées, car la
base de données n’existe pas. L’exécution de l’application et la sélection du lien
Application vidéo entraînent un message d’erreur Impossible d’ouvrir la base de
données ou Le type de table suivant n’existe pas : Film.

Générez l’application pour vérifier qu’il n’y a pas d’erreurs.

Migration initiale
Utilisez la fonctionnalité EF CoreMigrations pour créer la base de données. La
fonctionnalité Migrations est un ensemble d’outils qui vous permettent de créer et de
mettre à jour une base de données pour qu’elle corresponde à votre modèle de
données.

Visual Studio

Dans le menu Outils, sélectionnez Gestionnaire de package NuGet>Console du


gestionnaire de package.

Dans la console du Gestionnaire de package, entrez les commandes suivantes :

PowerShell

Add-Migration InitialCreate
Update-Database

Add-Migration InitialCreate : Génère un fichier de migration

Migrations/{timestamp}_InitialCreate.cs . L’argument InitialCreate est le

nom de la migration. Vous pouvez utiliser n’importe quel nom, mais par
convention, un nom décrivant la migration est sélectionné. Étant donné qu’il
s’agit de la première migration, la classe générée contient du code permettant
de créer le schéma de la base de données. Le schéma de base de données est
basé sur le modèle spécifié dans la classe MvcMovieContext .

Update-Database : Met à jour la base de données vers la dernière migration,

qui a été créée par la commande précédente. Cette commande exécute la


méthode Up dans le fichier Migrations/{time-stamp}_InitialCreate.cs , qui
crée la base de données.
La commande Update-Database génère l’avertissement suivant :

Aucun type de magasin n’a été spécifié pour la propriété décimale « Price » sur
le type d’entité « Movie ». Les valeurs sont tronquées en mode silencieux si
elles ne sont pas compatibles avec la précision et l’échelle par défaut. Spécifiez
explicitement le type de colonne SQL Server qui peut prendre en charge toutes
les valeurs dans « OnModelCreating » à l’aide de « HasColumnType », spécifiez
la précision et l’échelle à l’aide de « HasPrecision » ou configurez un
convertisseur de valeurs à l’aide de « HasConversion ».

Ignorez l’avertissement précédent, il est résolu dans un tutoriel ultérieur.

Pour plus d’informations sur les outils de la console du gestionnaire de package


pour EF Core, consultez EF Core référence sur les outils - Console du gestionnaire
de package dans Visual Studio.

Tester l’application
Visual Studio

Exécutez l’application et sélectionnez sur le lien Movie App.

Si vous obtenez une exception semblable à ce qui suit, vous avez peut-être manqué
la commande Update-Database à l’étape des migrations :

Console

SqlException: Cannot open database "MvcMovieContext-1" requested by the


login. The login failed.

7 Notes

Vous ne pourrez peut-être pas entrer de virgules décimales dans le champ Price .
Pour prendre en charge la validation jQuery pour les paramètres régionaux
autres que « Anglais » qui utilisent une virgule (« , ») comme décimale et des
formats de date autres que le format « Anglais (États-Unis »), l’application doit être
localisée. Pour obtenir des instructions sur la localisation, consultez ce problème
GitHub .
Examinez la classe de contexte de base de données
générée et l’inscription
Avec EF Core, l’accès aux données est effectué à l’aide d’un modèle. Un modèle est
constitué de classes d’entité et d’un objet de contexte qui représente une session avec
la base de données. L’objet de contexte permet l’interrogation et l’enregistrement des
données. Le contexte de base de données est dérivé de
Microsoft.EntityFrameworkCore.DbContext et il spécifie les entités à inclure dans le
modèle de données.

La génération de modèles crée la classe Data/MvcMovieContext.cs de contexte de base


de données :

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;

namespace MvcMovie.Data
{
public class MvcMovieContext : DbContext
{
public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
: base(options)
{
}

public DbSet<MvcMovie.Models.Movie> Movie { get; set; }


}
}

Le code précédent crée une propriété DbSet<Movie> qui représente les films dans la
base de données.

Injection de dépendances
ASP.NET Core comprend l’injection de dépendances (DI). Les services, tels que le
contexte de base de données, sont inscrits avec une injection de dépendance dans
Program.cs . Ces services sont fournis aux composants qui en ont besoin via des

paramètres de constructeur.
Dans le fichier Controllers/MoviesController.cs , le constructeur utilise une injection de
dépendance pour injecter le contexte de base de données MvcMovieContext dans le
contrôleur. Le contexte de base de données est utilisé dans chacune des méthodes la
CRUD du contrôleur.

La génération de modèles a généré le code en surbrillance suivant dans Program.cs :

Visual Studio

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));

Le système de configuration ASP.NET Core lit la chaîne de connexion de base de


données « MvcMovieContext ».

Examinez la chaîne de connexion de base de données


générée
La génération de modèles a ajouté une chaîne de connexion au fichier
appsettings.json :

Visual Studio

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Data Source=MvcMovieContext-ea7a4069-f366-4742-
bd1c-3f753a804ce1.db"
}
}

Pour le développement local, le système de configuration ASP.NET Core lit la clé


ConnectionString depuis le fichier appsettings.json .

La classe InitialCreate
Examinez le fichier de migration Migrations/{timestamp}_InitialCreate.cs :

C#

using System;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace MvcMovie.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Movie",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)",
nullable: true),
ReleaseDate = table.Column<DateTime>(type: "datetime2",
nullable: false),
Genre = table.Column<string>(type: "nvarchar(max)",
nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)",
nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Movie", x => x.Id);
});
}

protected override void Down(MigrationBuilder migrationBuilder)


{
migrationBuilder.DropTable(
name: "Movie");
}
}
}

Dans le code précédent :

La méthode InitialCreate.Up crée la table Film et configure Id comme la clé


primaire.
InitialCreate.Down rétablit les modifications de schéma provoquées par la

migration Up .

Injection de dépendances dans le contrôleur


Ouvrez le fichier Controllers/MoviesController.cs et examinez le constructeur :

C#

public class MoviesController : Controller


{
private readonly MvcMovieContext _context;

public MoviesController(MvcMovieContext context)


{
_context = context;
}

Le constructeur utilise une injection de dépendance pour injecter le contexte de base de


données ( MvcMovieContext ) dans le contrôleur. Le contexte de base de données est
utilisé dans chacune des méthodes la CRUD du contrôleur.

Testez la page Create. Entrez et envoyez des données.

Testez les pages Edit, Details et Delete.

Modèles fortement typés et directive @model


Plus tôt dans ce didacticiel, vous avez vu comment un contrôleur peut passer des
données ou des objets à une vue en utilisant le dictionnaire ViewData . Le dictionnaire
ViewData est un objet dynamique qui fournit un moyen pratique d’effectuer une liaison

tardive pour passer des informations à une vue.

Le modèle MVC fournit la possibilité de passer des objets de modèle fortement typés à
une vue. Cette approche fortement typée permet de vérifier votre code au moment de
la compilation. Le mécanisme de génération de modèles a passé un modèle fortement
typé dans la classe et les vues MoviesController .

Examinez la méthode Details générée dans le fichier


Controllers/MoviesController.cs :

C#

// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie


.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}

return View(movie);
}

Le paramètre id est généralement passé en tant que données de routage. Par exemple,
https://localhost:5001/movies/details/1 définit :

Le contrôleur sur le contrôleur movies , le premier segment de l’URL.


L’action sur details , le deuxième segment de l’URL.
Le id à 1, le dernier segment d’URL.

Le id peut être transmis avec une chaîne de requête, comme dans l’exemple suivant :

https://localhost:5001/movies/details?id=1

Le paramètre id est défini comme type Nullable ( int? ) au cas où la valeur id n’est pas
fournie.

Une expression lambda est passée à la méthode FirstOrDefaultAsync pour sélectionner


les entités de film qui correspondent aux données de routage ou à la valeur de la chaîne
de requête.

C#
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);

Si un film est trouvé, une instance du modèle Movie est passée à la vue Details :

C#

return View(movie);

Examinez le contenu du fichier Views/Movies/Details.cshtml :

CSHTML

@model MvcMovie.Models.Movie

@{
ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
<h4>Movie</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Genre)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>

L’instruction @model située en haut du fichier de la vue spécifie le type d’objet attendu
par la vue. Lorsque le contrôleur de film était créé, l’instruction @model suivante était
incluse :

CSHTML

@model MvcMovie.Models.Movie

Cette directive @model autorise l’accès au film que le contrôleur a passé à la vue. L’objet
Model est fortement typé. Par exemple, dans la vue Details.cshtml , le code passe

chaque champ du film aux Helpers HTML DisplayNameFor et DisplayFor avec l’objet
Model fortement typé. Les méthodes et les vues Create et Edit passent aussi un objet

du modèle Movie .

Examinez la vue Index.cshtml et la méthode Index dans le contrôleur Movies. Notez


comment le code crée un objet List quand il appelle la méthode View . Le code passe
cette liste Movies de la méthode d’action Index à la vue :

C#

// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}

Le code retourne les détails du problème si la propriété Movie du contexte de données


est null.

Lors de la création du contrôleur du film, la génération de modèles automatique a inclus


l’instruction @model suivante en haut du fichier Index.cshtml :

CSHTML

@model IEnumerable<MvcMovie.Models.Movie>
La directive @model permet d’accéder à la liste des films que le contrôleur a passé à la
vue en utilisant un objet Model qui est fortement typé. Par exemple, dans la vue
Index.cshtml , le code boucle dans les films avec une instruction foreach sur l’objet
Model fortement typé :

CSHTML

@model IEnumerable<MvcMovie.Models.Movie>

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a>
|
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Comme l’objet Model est fortement typé en tant qu’objet IEnumerable<Movie> , chaque
élément de la boucle est typé en tant que Movie . Entre autres avantages, le compilateur
valide les types utilisés dans le code.

Ressources supplémentaires
Entity Framework Core pour les débutants
Tag Helpers
Globalisation et localisation

Précédent : Ajout d’une vue Suivant : Utilisation de SQL

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Partie 5, Utilisation d’une base de
données dans une application ASP.NET
Core MVC
Article • 30/11/2023

Par Rick Anderson et Jon P Smith .

L’objet MvcMovieContext gère la tâche de connexion à la base de données et de


mappage d’objets Movie à des enregistrements de la base de données. Le contexte de
base de données est inscrit auprès du conteneur d’injection de dépendances dans le
fichier Program.cs :

Visual Studio

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));

Le système de configuration d’ASP.NET Core lit la clé ConnectionString . Pour un


développement local, il obtient la chaîne de connexion à partir du fichier
appsettings.json .

JSON

"ConnectionStrings": {
"MvcMovieContext": "Data Source=MvcMovieContext-ea7a4069-f366-4742-
bd1c-3f753a804ce1.db"
}

Quand l’application est déployée sur un serveur de test ou de production, une variable
d’environnement peut être utilisée pour définir la chaîne de connexion à un serveur SQL
Server de production. Pour plus d’informations, consultez Configuration.

Visual Studio
Base de données locale SQL Server Express
Base de données locale :

Version légère du moteur de base de données SQL Server Express, installée


par défaut avec Visual Studio.
Démarre à la demande à l’aide d’une chaîne de connexion.
Est destiné au développement de programmes. Elle s’exécute en mode
utilisateur, ce qui n’implique aucune configuration complexe.
Par défaut, crée des fichiers .mdf dans le répertoire C:/Users/{utilisateur}.

Examiner la base de données


Dans le menu Affichage, ouvrez l’Explorateur d’objets SQL Server (SSOX).

Cliquez avec le bouton droit sur la table Movie ( dbo.Movie ) >Concepteur de vues
Notez l’icône de clé en regard de ID . Par défaut, EF fait d’une propriété nommée
ID la clé primaire.

Cliquez avec le bouton droit sur la table Movie > Afficher les données.
Amorcer la base de données
Créez une classe nommée SeedData dans l’espace de noms Modèles. Remplacez le code
généré par ce qui suit :

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MvcMovie.Data;
using System;
using System.Linq;

namespace MvcMovie.Models;

public static class SeedData


{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MvcMovieContext(
serviceProvider.GetRequiredService<
DbContextOptions<MvcMovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}

Si la base de données contient des films, l’initialiseur de valeur initiale retourne une
valeur et aucun film n’est ajouté.

C#

if (context.Movie.Any())
{
return; // DB has been seeded.
}

Ajouter l’initialiseur de valeur initiale

Visual Studio

Remplacez le contenu de Program.cs par le code suivant. Le nouveau code est mis
en évidence.

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MvcMovie.Data;
using MvcMovie.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));

// Add services to the container.


builder.Services.AddControllersWithViews();

var app = builder.Build();


using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;

SeedData.Initialize(services);
}

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Supprimez tous les enregistrements de la base de données. Pour ce faire, utilisez les
liens de suppression disponibles dans le navigateur ou à partir de SSOX.

Testez l’application. Forcez l’application à s’initialiser en appelant les méthodes de la


classe Program.cs , pour que la méthode seed s’exécute. Pour forcer l’initialisation,
fermez la fenêtre d’invite de commandes que Visual Studio a ouverte, puis
redémarrez en appuyant sur Ctrl+F5.

L’application affiche les données de valeurs de départ.


Précédent : Ajout d’un modèle

Suivant : Ajout de méthodes et de vues de contrôleur

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 6, Méthodes et vues de
contrôleur dans ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Nous avons une bonne ébauche de l’application de films, mais sa présentation n’est pas
idéale, par exemple, ReleaseDate devrait être écrit en deux mots.

Ouvrez le fichier Models/Movie.cs et ajoutez les lignes en surbrillance ci-dessous :

C#

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}

Les DataAnnotations sont expliquées dans le tutoriel suivant. L’attribut Display spécifie
les éléments à afficher pour le nom d’un champ (dans le cas présent, « Release Date » au
lieu de « ReleaseDate »). L’attribut DataType spécifie le type des données (Date). Les
informations d’heures stockées dans le champ ne s’affichent donc pas.

L’annotation de données [Column(TypeName = "decimal(18, 2)")] est nécessaire pour


qu’Entity Framework Core puisse correctement mapper Price en devise dans la base de
données. Pour plus d’informations, consultez Types de données.

Accédez au contrôleur Movies et maintenez le pointeur de la souris sur un lien Edit pour
afficher l’URL cible.

Les liens Edit, Details et Delete sont générés par le Tag Helper d’ancre Core MVC dans le
fichier Views/Movies/Index.cshtml .

CSHTML
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>

Les Tag Helpers permettent au code côté serveur de participer à la création et au rendu
des éléments HTML dans les fichiers Razor. Dans le code ci-dessus, AnchorTagHelper
génère dynamiquement la valeur de l’attribut HTML href à partir de la méthode
d’action du contrôleur et de l’ID de route. Vous utilisez Afficher la source à partir de
votre navigateur favori ou utilisez les outils de développement pour examiner le
balisage généré. Une partie du code HTML généré est affichée ci-dessous :

HTML

<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>

Rappelez-vous le format du routage défini dans le fichier Program.cs :

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core traduit https://localhost:5001/Movies/Edit/4 en une requête à la


méthode d’action Edit du contrôleur Movies avec un paramètre Id de 4. (Les
méthodes de contrôleur sont également appelées méthodes d’action.)

Les Tag Helpers sont l’une des nouvelles fonctionnalités les plus populaires dans
ASP.NET Core. Pour plus d'informations, consultez Ressources supplémentaires.

Ouvrez le contrôleur Movies et examinez les deux méthodes d’action Edit . Le code
suivant montre la méthode HTTP GET Edit , qui extrait le film et renseigne le formulaire
de modification généré par le fichier Razor Edit.cshtml .

C#

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie.FindAsync(id);


if (movie == null)
{
return NotFound();
}
return View(movie);
}

Le code suivant montre la méthode HTTP POST Edit , qui traite les valeurs de film
publiées :

C#

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you
want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id,
[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}

L’attribut [Bind] est l’un des moyens qui permettent d’assurer une protection contre la
sur-publication. Vous devez inclure dans l’attribut [Bind] uniquement les propriétés que
vous souhaitez modifier. Pour plus d’informations, consultez Protéger votre contrôleur
contre la sur-publication. Les ViewModels fournissent une alternative pour empêcher
la sur-publication.

Notez que la deuxième méthode d’action Edit est précédée de l’attribut [HttpPost] .

C#

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you
want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id,
[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
L’attribut HttpPost indique que cette méthode Edit peut être appelée uniquement pour
les requêtes POST . Vous pouvez appliquer l’attribut [HttpGet] à la première méthode
Edit, mais cela n’est pas nécessaire car [HttpGet] est la valeur par défaut.

L’attribut ValidateAntiForgeryToken est utilisé pour lutter contre la falsification d’une


demande. Il est associé à un jeton anti-contrefaçon généré dans le fichier de la vue Edit
( Views/Movies/Edit.cshtml ). Le fichier de la vue Edit génère le jeton anti-contrefaçon
avec le Tag Helper Form.

CSHTML

<form asp-action="Edit">

Le Tag Helper Form génère un jeton anti-contrefaçon masqué qui doit correspondre au
jeton anti-contrefaçon généré par [ValidateAntiForgeryToken] dans la méthode Edit
du contrôleur Movies. Pour plus d’informations, consultez Prévenir les attaques par
falsification de requête intersites (XSRF/CSRF) dans ASP.NET Core.

La méthode HttpGet Edit prend le paramètre ID du film, recherche le film à l’aide de la


méthode Entity Framework FindAsync , et retourne le film sélectionné à la vue Edit. Si un
film est introuvable, l’erreur NotFound (HTTP 404) est retournée.

C#

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie.FindAsync(id);


if (movie == null)
{
return NotFound();
}
return View(movie);
}

Quand le système de génération de modèles automatique a créé la vue Edit, il a


examiné la classe Movie et a créé le code pour restituer les éléments <label> et <input>
de chaque propriété de la classe. L’exemple suivant montre la vue Edit qui a été générée
par le système de génération de modèles automatique de Visual Studio :
CSHTML

@model MvcMovie.Models.Movie

@{
ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>

<div>
<a asp-action="Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Notez que le modèle de vue comporte une instruction @model MvcMovie.Models.Movie en
haut du fichier. @model MvcMovie.Models.Movie indique que la vue s’attend à ce que le
modèle pour le modèle de vue soit de type Movie .

Le code de génération de modèles automatique utilise plusieurs méthodes Tag Helper


afin de rationaliser le balisage HTML. Le Tag Helper Label affiche le nom du champ
(« Title », « ReleaseDate », « Genre » ou « Price »). Le Tag Helper Input affiche l’élément
<input> HTML. Le Tag Helper Validation affiche les messages de validation associés à

cette propriété.

Exécutez l’application et accédez à l’URL /Movies . Cliquez sur un lien Edit. Dans le
navigateur, affichez la source de la page. Le code HTML généré pour l’élément <form>
est indiqué ci-dessous.

HTML

<form action="/Movies/Edit/7" method="post">


<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field
is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre"
name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-
valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true"
data-val-number="The field Price must be a number." data-val-required="The
Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-
valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden"
value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmq
UyXnJBXhmrjcUVDJyDUMm7-
MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Les éléments <input> sont dans un élément HTML <form> dont l’attribut action est
défini de façon à publier à l’URL /Movies/Edit/id . Les données du formulaire sont
publiées au serveur en cas de clic sur le bouton Save . La dernière ligne avant l’élément
</form> de fermeture montre le jeton XSRF masqué généré par le Tag Helper Form.

Traitement de la requête POST


Le code suivant montre la version [HttpPost] de la méthode d’action Edit .

C#

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you
want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id,
[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}

L’attribut [ValidateAntiForgeryToken] valide le jeton XSRF masqué généré par le


générateur de jetons anti-contrefaçon dans le Tag Helper Form

Le système de liaison de modèle prend les valeurs de formulaire publiées et crée un


objet Movie qui est passé en tant que paramètre movie . La propriété
ModelState.IsValid vérifie que les données envoyées dans le formulaire peuvent être

utilisées pour changer (modifier ou mettre à jour) un objet Movie . Si les données sont
valides, elles sont enregistrées. Les données de film mises à jour (modifiées) sont
enregistrées dans la base de données en appelant la méthode SaveChangesAsync du
contexte de base de données. Après avoir enregistré les données, le code redirige
l’utilisateur vers la méthode d’action Index de la classe MoviesController , qui affiche la
collection de films, avec notamment les modifications qui viennent d’être apportées.

Avant que le formulaire soit publié sur le serveur, la validation côté client vérifie les
règles de validation sur les champs. En cas d’erreur de validation, un message d’erreur
s’affiche et le formulaire n’est pas publié. Si JavaScript est désactivé, aucune validation
côté client n’est effectuée, mais le serveur détecte les valeurs publiées qui ne sont pas
valides, et les valeurs de formulaire sont réaffichées avec des messages d’erreur. Plus
loin dans ce didacticiel, nous examinerons la Validation du modèle plus en détail. Le Tag
Helper Validation dans le modèle de vue Views/Movies/Edit.cshtml se charge de
l’affichage des messages d’erreur appropriés.
Toutes les méthodes HttpGet du contrôleur Movies suivent un modèle similaire. Elles
reçoivent un objet de film (ou une liste d’objets, dans le cas de Index ) et passent l’objet
(modèle) à la vue. La méthode Create passe un objet de film vide à la vue Create .
Toutes les méthodes qui créent, modifient, suppriment ou changent d’une quelconque
manière des données le font dans la surcharge [HttpPost] de la méthode. Modifier des
données dans une méthode HTTP GET présente un risque pour la sécurité. La
modification des données dans une méthode HTTP GET enfreint également les bonnes
pratiques HTTP et le modèle architectural REST , qui spécifie que les demandes GET ne
doivent pas changer l’état de votre application. En d’autres termes, une opération GET
doit être sûre, ne doit avoir aucun effet secondaire et ne doit pas modifier vos données
persistantes.

Ressources supplémentaires
Globalisation et localisation
Introduction aux Tag Helpers
Créer des Tag Helpers
Éviter les attaques de falsification de demande intersites (XSRF/CSRF) dans
ASP.NET Core
Protéger votre contrôleur contre la sur-publication
ViewModels
Tag Helper de formulaire
Tag Helper Input
Tag Helper Label
Tag Helper de sélection
Tag Helper Validation

Précédent Suivant

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 7, ajouter la recherche à une
application MVC ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Dans cette section, vous ajoutez une fonctionnalité de recherche à la méthode d’action
Index qui vous permet de rechercher des films par genre ou par nom.

Mettez à jour la méthode Index trouvée dans Controllers/MoviesController.cs avec le


code suivant :

C#

public async Task<IActionResult> Index(string searchString)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


select m;

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}

return View(await movies.ToListAsync());


}

La ligne suivante dans la méthode d’action Index crée une requête LINQ pour
sélectionner les films :

C#

var movies = from m in _context.Movie


select m;

La requête est seulement définie à ce stade ; elle n’a pas été exécutée sur la base de
données.

Si le paramètre searchString contient une chaîne, la requête de films est modifiée de


façon à filtrer sur la valeur de la chaîne de recherche :
C#

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}

Le code s => s.Title!.Contains(searchString) ci-dessus est une expression lambda.


Les expressions lambda sont utilisées dans les requêtes LINQ basées sur une méthode
en tant qu’arguments pour les méthodes d’opérateur de requête standard, comme la
méthode Where ou Contains (utilisées dans le code ci-dessus). Les requêtes LINQ ne
sont pas exécutées quand elles sont définies ou quand elles sont modifiées en appelant
une méthode, comme Where , Contains ou OrderBy . Au lieu de cela, l’exécution de la
requête est différée. Cela signifie que l’évaluation d’une expression est retardée jusqu’à
ce que sa valeur réalisée fasse l’objet d’une itération réelle ou que la méthode
ToListAsync soit appelée. Pour plus d’informations sur l’exécution différée des requêtes,

consultez Exécution de requête.

Remarque : La méthode Contains est exécutée sur la base de données, et non pas dans
le code C# ci-dessus. Le respect de la casse pour la requête dépend de la base de
données et du classement. Sur SQL Server, Contains est mappée à SQL LIKE, qui ne
respecte pas la casse. Dans SQLite, avec le classement par défaut, elle respecte la casse.

Accédez à /Movies/Index . Ajoutez une chaîne de requête comme ?searchString=Ghost à


l’URL. Les films filtrés sont affichés.
Si vous changez la signature de la méthode Index pour y inclure un paramètre nommé
id , le paramètre id correspondra à l’espace réservé facultatif {id} pour les routes par

défaut définies dans Program.cs .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

Remplacez le paramètre par id et toutes les occurrences de searchString par id .

La méthode Index précédente :

C#

public async Task<IActionResult> Index(string searchString)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


select m;

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}

return View(await movies.ToListAsync());


}

La méthode Index mise à jour avec le paramètre id :

C#

public async Task<IActionResult> Index(string id)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


select m;

if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title!.Contains(id));
}

return View(await movies.ToListAsync());


}

Vous pouvez maintenant passer le titre de la recherche en tant que données de routage
(un segment de l’URL) et non pas en tant que valeur de chaîne de requête.

Cependant, vous ne pouvez pas attendre des utilisateurs qu’ils modifient l’URL à chaque
fois qu’ils veulent rechercher un film. Vous allez donc maintenant ajouter des éléments
d’interface utilisateur pour les aider à filtrer les films. Si vous avez changé la signature de
la méthode Index pour tester comment passer le paramètre ID lié à une route,
rétablissez-la de façon à ce qu’elle prenne un paramètre nommé searchString :

C#

public async Task<IActionResult> Index(string searchString)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}

return View(await movies.ToListAsync());


}

Ouvrez le fichier Views/Movies/Index.cshtml et ajoutez la balise <form> mise en


surbrillance ci-dessous :

CSHTML

@model IEnumerable<MvcMovie.Models.Movie>

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index">


<p>
Title: <input type="text" name="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">

La balise HTML <form> utilise le Tag Helper de formulaire, de façon que quand vous
envoyez le formulaire, la chaîne de filtrage soit envoyée à l’action Index du contrôleur
de films. Enregistrez vos modifications puis testez le filtre.
Contrairement à ce que vous pourriez penser, une surcharge de [HttpPost] dans la
méthode Index n’est pas nécessaire. Vous n’en avez pas besoin, car la méthode ne
change pas l’état de l’application, elle filtre seulement les données.

Vous pourriez ajouter la méthode [HttpPost] Index suivante.

C#

[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}

Le paramètre notUsed est utilisé pour créer une surcharge pour la méthode Index . Nous
parlons de ceci plus loin dans le didacticiel.

Si vous ajoutez cette méthode, le demandeur de l’action correspondrait à la méthode


[HttpPost] Index et la méthode [HttpPost] Index s’exécuterait comme indiqué dans

l’image ci-dessous.
Cependant, même si vous ajoutez cette version [HttpPost] de la méthode Index , il
existe une limitation dans la façon dont tout ceci a été implémenté. Imaginez que vous
voulez insérer un signet pour une recherche spécifique, ou que vous voulez envoyer un
lien à vos amis sur lequel ils peuvent cliquer pour afficher la même liste filtrée de films.
Notez que l’URL de la requête HTTP POST est identique à l’URL de la requête GET
(localhost:{PORT}/Movies/Index) : il n’existe aucune information de recherche dans l’URL.
Les informations de la chaîne de recherche sont envoyées au serveur en tant que valeur
d’un champ de formulaire . Vous pouvez vérifier ceci avec les outils de développement
du navigateur ou avec l’excellent outil Fiddler . L’illustration ci-dessous montre les
outils de développement du navigateur Chrome :
Vous pouvez voir le paramètre de recherche et le jeton XSRF dans le corps de la
demande. Notez que, comme indiqué dans le didacticiel précédent, le Tag Helper de
formulaire génère un jeton XSRF anti-contrefaçon. Nous ne modifions pas les données :
nous n’avons donc pas besoin de valider le jeton dans la méthode du contrôleur.

Comme le paramètre de recherche se trouve dans le corps de la demande et pas dans


l’URL, vous ne pouvez pas capturer ces informations de recherche pour les insérer dans
un signet ou les partager avec d’autres personnes. Nous résolvons ce problème en
spécifiant que la requête doit être HTTP GET trouvable dans le fichier
Views/Movies/Index.cshtml .

CSHTML

@model IEnumerable<MvcMovie.Models.Movie>

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index" method="get">


<p>
Title: <input type="text" name="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">

Maintenant, quand vous soumettez une recherche, l’URL contient la chaîne de requête
de la recherche. La recherche accède également à la méthode d’action HttpGet Index ,
même si vous avez une méthode HttpPost Index .
La mise en forme suivante montre la modification apportée à la balise form :

CSHTML

<form asp-controller="Movies" asp-action="Index" method="get">

Ajouter la recherche par genre


Ajoutez la classe MovieGenreViewModel suivante au dossier Models :

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace MvcMovie.Models;

public class MovieGenreViewModel


{
public List<Movie>? Movies { get; set; }
public SelectList? Genres { get; set; }
public string? MovieGenre { get; set; }
public string? SearchString { get; set; }
}

Le modèle de vue movie-genre contiendra :

Une liste de films.


Une SelectList contenant la liste des genres. Cela permet à l’utilisateur de
sélectionner un genre dans la liste.
MovieGenre , qui contient le genre sélectionné.

SearchString , qui contient le texte que les utilisateurs entrent dans la zone de

texte de recherche.

Remplacez la méthode Index dans MoviesController.cs par le code suivant :

C#

// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string
searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

// Use LINQ to get list of genres.


IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;

if (!string.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}

if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}

var movieGenreVM = new MovieGenreViewModel


{
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Movies = await movies.ToListAsync()
};

return View(movieGenreVM);
}
Le code suivant est une requête LINQ qui récupère tous les genres de la base de
données.

C#

// Use LINQ to get list of genres.


IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;

La SelectList de genres est créée en projetant les genres distincts (nous ne voulons pas
que notre liste de sélection ait des genres en doublon).

Quand l’utilisateur recherche l’élément, la valeur de recherche est conservée dans la


zone de recherche.

Ajouter la recherche par genre à la vue Index


Mettez à jour Index.cshtml trouvé dans Views/Movies/ comme suit :

CSHTML

@model MvcMovie.Models.MovieGenreViewModel

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>

<select asp-for="MovieGenre" asp-items="Model.Genres">


<option value="">All</option>
</select>

Title: <input type="text" asp-for="SearchString" />


<input type="submit" value="Filter" />
</p>
</form>

<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies!)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-
id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-
id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Examinez l’expression lambda utilisée dans le HTML Helper suivant :

@Html.DisplayNameFor(model => model.Movies![0].Title)

Dans le code précédent, le Helper HTML DisplayNameFor inspecte la propriété Title


référencée dans l’expression lambda pour déterminer le nom d’affichage. Comme
l’expression lambda est inspectée et non pas évaluée, vous ne recevez pas de violation
d’accès quand model , model.Movies ou model.Movies[0] sont null ou vides. Quand
l’expression lambda est évaluée (par exemple @Html.DisplayFor(modelItem =>
item.Title) ), les valeurs des propriétés du modèle sont évaluées. ! après model.Movies

est l’opérateur null-forgiving, qui est utilisé pour déclarer qui Movies n’est pas nul.

Testez l’application en effectuant une recherche par genre, par titre de film et selon ces
deux critères :

Précédent Suivant

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 8, ajouter un nouveau champ à
une application ASP.NET Core MVC
Article • 30/11/2023

Par Rick Anderson

Dans cette section, Migrations Entity Framework Code First est utilisé pour :

Ajouter un nouveau champ au modèle.


Migrer le nouveau champ vers la base de données.

Quand EF Code First est utilisé pour créer automatiquement une base de données, Code
First :

Ajoute une table à la base de données pour en suivre le schéma.


Vérifie que la base de données est synchronisée avec les classes de modèle à partir
desquelles elle a été générée. S’ils ne sont pas synchronisés, EF lève une exception.
Cela facilite la recherche de problèmes d’incohérence de code/de bases de
données.

Ajouter une propriété Rating au modèle Movie


Ajoutez une propriété Rating à Models/Movie.cs :

C#

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
public string? Title { get; set; }

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }

[Column(TypeName = "decimal(18, 2)")]


public decimal Price { get; set; }
public string? Rating { get; set; }
}

Générer l’application

Visual Studio

Ctrl+Maj+B

Comme vous avez ajouté un nouveau champ à la classe Movie , vous devez mettre à jour
la liste des liaisons de propriété pour y inclure cette nouvelle propriété. Dans
MoviesController.cs , mettez à jour l’attribut [Bind] des méthodes d’action Create et
Edit pour y inclure la propriété Rating :

C#

[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")]

Mettez à jour les modèles de vue pour afficher, créer et modifier la nouvelle propriété
Rating dans la vue du navigateur.

Modifiez le fichier /Views/Movies/Index.cshtml et ajoutez un champ Rating :

CSHTML

<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies!)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-
id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-
id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Mettez à jour /Views/Movies/Create.cshtml avec un champ Rating .

Visual Studio / Visual Studio pour Mac

Vous pouvez copier/coller le « groupe de formulaire » précédent et laisser


IntelliSense vous aider à mettre à jour les champs. IntelliSense fonctionne avec des
Tag Helpers.
Mettez à jour les modèles restants.

Mettez à jour la classe SeedData pour qu’elle fournisse une valeur pour la nouvelle
colonne. Vous pouvez voir un exemple de modification ci-dessous, mais elle doit être
appliquée à chaque new Movie .

C#

new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Rating = "R",
Price = 7.99M
},

L’application ne fonctionne pas tant que la base de données n’est pas mise à jour pour
inclure le nouveau champ. Si elle est exécutée maintenant, l’erreur SqlException est
levée :

SqlException: Invalid column name 'Rating'.

Cette erreur survient car la classe du modèle Movie mise à jour est différente du schéma
de la table Movie de la base de données existante. (Il n’existe pas de colonne Rating
dans la table de base de données.)

Plusieurs approches sont possibles pour résoudre l’erreur :


1. Laissez Entity Framework supprimer et recréer automatiquement la base de
données sur la base du nouveau schéma de classe de modèle. Cette approche est
très utile au début du cycle de développement quand vous effectuez un
développement actif sur une base de données de test. Elle permet de faire évoluer
rapidement le schéma de modèle et de base de données ensemble. L’inconvénient,
cependant, est que vous perdez les données existantes dans la base de données.
Par conséquent, n’utilisez pas cette approche sur une base de données de
production. L’utilisation d’un initialiseur pour amorcer automatiquement une base
de données avec des données de test est souvent un moyen efficace pour
développer une application. Il s’agit d’une bonne approche pour le développement
initial et lors de l’utilisation de SQLite.

2. Modifier explicitement le schéma de la base de données existante pour le faire


correspondre aux classes du modèle. L’avantage de cette approche est que vous
conservez vos données. Vous pouvez apporter cette modification manuellement
ou en créant un script de modification de la base de données.

3. Utilisez les migrations Code First pour mettre à jour le schéma de base de
données.

Pour ce didacticiel, les migrations Code First sont utilisées.

Visual Studio

Dans le menu Outils, sélectionnez Gestionnaire de package NuGet > Console du


gestionnaire de package.
Dans la console du gestionnaire de package, entrez les commandes suivantes :

PowerShell

Add-Migration Rating
Update-Database

La commande Add-Migration indique au framework de migration d’examiner le


modèle Movie actuel avec le schéma de la base de données Movie actuel et de
créer le code nécessaire pour migrer la base de données vers le nouveau modèle.

Le nom « Rating » est arbitraire et utilisé pour nommer le fichier de migration. Il est
utile d’utiliser un nom explicite pour le fichier de migration.

Si tous les enregistrements de la base de données sont supprimés, la méthode


d’initialisation amorce la base de données et inclut le champ Rating .

Exécutez l’application et vérifiez que vous pouvez créer, modifier et afficher des films
avec un champ Rating .

Précédent Suivant
6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 9, ajouter la validation à une
application MVC ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Dans cette section :

Une logique de validation est ajoutée au modèle Movie .


Vous vous assurez que les règles de validation sont appliquées chaque fois qu’un
utilisateur crée ou modifie un film.

Ne vous répétez pas


L’un des principes de conception de MVC est « Ne vous répétez pas » (désigné par
l’acronyme DRY , Don’t Repeat Yourself). ASP.NET Core MVC vous encourage à
spécifier les fonctionnalités ou les comportements une seule fois, puis à utiliser la
réflexion partout dans une application. Cela réduit la quantité de code à écrire, et rend
le code que vous écrivez moins susceptible aux erreurs et plus facile à tester et à gérer.

La prise en charge de la validation fournie par MVC et Entity Framework Core Code First
est un bon exemple du principe DRY en action. Vous pouvez spécifier de façon
déclarative des règles de validation à un seul emplacement (dans la classe de modèle),
et les règles sont appliquées partout dans l’application.

Ajouter des règles de validation au modèle de


film
L’espace de noms DataAnnotations fournit un ensemble d’attributs de validation
intégrés qui sont appliqués de façon déclarative à une classe ou à une propriété.
DataAnnotations contient également des attributs de mise en forme, comme DataType ,
qui aident à effectuer la mise en forme et ne fournissent aucune validation.

Mettez à jour la classe Movie pour tirer parti des attributs de validation intégrés
Required , StringLength , RegularExpression , Range et de l’attribut de mise en forme

DataType .

C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }

[StringLength(60, MinimumLength = 3)]


[Required]
public string? Title { get; set; }

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
[Required]
[StringLength(30)]
public string? Genre { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string? Rating { get; set; }
}

Les attributs de validation spécifient le comportement que vous souhaitez appliquer sur
les propriétés du modèle sur lesquels ils sont appliqués :

Les attributs Required et MinimumLength indiquent qu’une propriété doit avoir une
valeur, mais rien n’empêche un utilisateur d’entrer un espace pour satisfaire à cette
validation.

L’attribut RegularExpression sert à limiter les caractères pouvant être entrés. Dans
le code précédent, « Genre » :
Doit utiliser seulement des lettres.
La première lettre doit être une majuscule. Les espaces blancs sont autorisés
tandis que les nombres et les caractères spéciaux ne sont pas autorisés.

L’expression RegularExpression « Rating » :


Nécessite que le premier caractère soit une lettre majuscule.
Autorise les caractères spéciaux et les chiffres aux emplacements qui suivent.
« PG-13 » est valide pour une évaluation, mais échoue pour un « Genre ».

L’attribut Range limite une valeur à une plage spécifiée.

L’attribut StringLength vous permet de définir la longueur maximale d’une


propriété de chaîne, et éventuellement sa longueur minimale.

Les types valeur (tels que decimal , int , float et DateTime ) sont obligatoires par
nature et n’ont pas besoin de l’attribut [Required] .

L’application automatique des règles de validation par ASP.NET Core permet d’accroître
la fiabilité de votre application. Cela garantit également que vous n’oublierez pas de
valider un élément et que vous n’autoriserez pas par inadvertance l’insertion de données
incorrectes dans la base de données.

Interface utilisateur des erreurs de validation


Exécutez l’application et accédez au contrôleur Movies.

Sélectionnez le lien Créer nouveau pour ajouter un nouveau film. Remplissez le


formulaire avec des valeurs non valides. Dès que la validation côté client jQuery détecte
l’erreur, elle affiche un message d’erreur.
7 Notes

Vous ne pourrez peut-être pas entrer de virgules décimales dans les champs
décimaux. Pour prendre en charge la validation jQuery pour les paramètres
régionaux autres que l’anglais qui utilisent une virgule (« , ») comme décimale et
des formats de date autres que l’anglais des États-Unis, vous devez effectuer des
étapes pour localiser votre application. Consultez ce commentaire GitHub 4076
pour savoir comment ajouter une virgule décimale.

Notez que le formulaire a affiché automatiquement un message d’erreur de validation


approprié dans chaque champ contenant une valeur non valide. Les erreurs sont
appliquées à la fois côté client (à l’aide de JavaScript et jQuery) et côté serveur (au cas
où un utilisateur aurait désactivé JavaScript).

L’un des principaux avantages est que vous n’avez pas eu à changer une seule ligne de
code dans la classe MoviesController ou dans la vue Create.cshtml pour activer cette
interface utilisateur de validation. Le contrôleur et les vues créées précédemment dans
ce didacticiel ont détecté les règles de validation que vous avez spécifiées à l’aide des
attributs de validation sur les propriétés de la classe de modèle Movie . Testez la
validation à l’aide de la méthode d’action Edit . La même validation est appliquée.

Les données de formulaire ne sont pas envoyées au serveur tant qu’il y a des erreurs de
validation côté client. Vous pouvez vérifier cela en plaçant un point d’arrêt dans la
méthode HTTP Post , en utilisant l’outil Fiddler ou à l’aide des Outils de
développement F12.

Fonctionnement de la validation
Vous vous demandez peut-être comment l’interface utilisateur de validation a été
générée sans aucune mise à jour du code dans le contrôleur ou dans les vues. Le code
suivant montre les deux méthodes Create .

C#

// GET: Movies/Create
public IActionResult Create()
{
return View();
}

// POST: Movies/Create
// To protect from overposting attacks, enable the specific properties you
want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult>
Create([Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(movie);
}

La première méthode d’action (HTTP GET) Create affiche le formulaire de création


initial. La deuxième version ( [HttpPost] ) gère la publication de formulaire. La seconde
méthode Create (la version [HttpPost] ) appelle ModelState.IsValid pour vérifier si le
film a des erreurs de validation. L’appel de cette méthode évalue tous les attributs de
validation qui ont été appliqués à l’objet. Si l’objet comporte des erreurs de validation, la
méthode Create réaffiche le formulaire. S’il n’y a pas d’erreur, la méthode enregistre le
nouveau film dans la base de données. Dans notre exemple de film, le formulaire n’est
pas publié sur le serveur quand des erreurs de validation sont détectées côté client ; la
seconde méthode Create n’est jamais appelée quand il y a des erreurs de validation
côté client. Si vous désactivez JavaScript dans votre navigateur, la validation client est
désactivée et vous pouvez tester la méthode Create HTTP POST ModelState.IsValid
pour détecter les erreurs de validation.

Vous pouvez définir un point d’arrêt dans la méthode [HttpPost] Create et vérifier que
la méthode n’est jamais appelée. La validation côté client n’enverra pas les données du
formulaire quand des erreurs de validation seront détectées. Si vous désactivez
JavaScript dans votre navigateur et que vous envoyez ensuite le formulaire avec des
erreurs, le point d’arrêt sera atteint. Vous bénéficiez toujours d’une validation complète
sans JavaScript.

L’illustration suivante montre comment désactiver JavaScript dans le navigateur Firefox.


L’illustration suivante montre comment désactiver JavaScript dans le navigateur Chrome.
Après la désactivation de JavaScript, publiez les données non valides et parcourez le
débogueur.
Une partie du modèle d’affichage Create.cshtml est indiquée dans le balisage suivant :

HTML

<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>

@*Markup removed for brevity.*@

Le balisage précédent est utilisé par les méthodes d’action pour afficher le formulaire
initial et pour le réafficher en cas d’erreur.

Le Tag Helper Input utilise les attributs DataAnnotations et produit les attributs HTML
nécessaires à la validation jQuery côté client. Le Tag Helper Validation affiche les erreurs
de validation. Pour plus d’informations, consultez Validation.

Le grand avantage de cette approche est que ni le contrôleur ni le modèle de vue


Create ne savent rien des règles de validation appliquées ou des messages d’erreur

affichés. Les règles de validation et les chaînes d’erreur sont spécifiées uniquement dans
la classe Movie . Ces mêmes règles de validation sont automatiquement appliquées à la
vue Edit et à tous les autres modèles de vues que vous pouvez créer et qui modifient
votre modèle.

Quand vous devez changer la logique de validation, vous pouvez le faire à un seul
endroit en ajoutant des attributs de validation au modèle (dans cet exemple, la classe
Movie ). Vous n’aurez pas à vous soucier des différentes parties de l’application qui

pourraient être incohérentes avec la façon dont les règles sont appliquées. Toute la
logique de validation sera définie à un seul emplacement et utilisée partout. Le code est
ainsi très propre, facile à mettre à jour et évolutif. Et cela signifie que vous respecterez
entièrement le principe DRY.

Utilisation d’attributs DataType


Ouvrez le fichier Movie.cs et examinez la classe Movie . L’espace de noms
System.ComponentModel.DataAnnotations fournit des attributs de mise en forme en plus

de l’ensemble intégré d’attributs de validation. Nous avons déjà appliqué une valeur
d’énumération DataType aux champs de date de sortie et de prix. Le code suivant illustre
les propriétés ReleaseDate et Price avec l’attribut DataType approprié.

C#

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

Les attributs DataType fournissent uniquement des indices permettant au moteur de vue
de mettre en forme les données (et fournissent des éléments/attributs tels que <a>
pour les URL et <a href="mailto:EmailAddress.com"> pour l’e-mail). Vous pouvez utiliser
l’attribut RegularExpression pour valider le format des données. L’attribut DataType sert
à spécifier un type de données qui est plus spécifique que le type intrinsèque de la base
de données ; il ne s’agit pas d’un attribut de validation. Dans le cas présent, nous
voulons uniquement effectuer le suivi de la date, et non de l’heure. L’énumération
DataType fournit de nombreux types de données, telles que Date, Time, PhoneNumber,
Currency ou EmailAddress. L’attribut DataType peut également permettre à l’application
de fournir automatiquement des fonctionnalités propres au type. Par exemple, vous
pouvez créer un lien mailto: pour DataType.EmailAddress , et vous pouvez fournir un
sélecteur de date pour DataType.Date dans les navigateurs qui prennent en charge
HTML5. Les attributs DataType émettent des attributs HTML 5 data- compréhensibles
par les navigateurs HTML 5. Les attributs DataType ne fournissent aucune validation.

DataType.Date ne spécifie pas le format de la date qui s’affiche. Par défaut, le champ de

données est affiché conformément aux formats par défaut basés sur le CultureInfo du
serveur.

L’attribut DisplayFormat est utilisé pour spécifier explicitement le format de date :

C#

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode =


true)]
public DateTime ReleaseDate { get; set; }

Le paramètre ApplyFormatInEditMode indique que la mise en forme doit également être


appliquée quand la valeur est affichée dans une zone de texte à des fins de
modification. (Ceci peut ne pas être souhaitable pour certains champs ; par exemple,
pour les valeurs monétaires, vous ne souhaiterez sans doute pas que le symbole
monétaire figure dans la zone de texte.)

Vous pouvez utiliser l’attribut DisplayFormat par lui-même, mais il est généralement
préférable d’utiliser l’attribut DataType . L’attribut DataType donne la sémantique des
données, plutôt que de décrire comment effectuer le rendu sur un écran, et il offre les
avantages suivants dont vous ne bénéficiez pas avec DisplayFormat :

Le navigateur peut activer des fonctionnalités HTML5 (par exemple pour afficher
un contrôle de calendrier, le symbole monétaire correspondant aux paramètres
régionaux, des liens de messagerie, etc.).

Par défaut, le navigateur affiche les données à l’aide du format correspondant à


vos paramètres régionaux.

L’attribut DataType peut permettre à MVC de choisir le modèle de champ correct


pour afficher les données ( DisplayFormat , utilisé par lui-même, utilise le modèle de
chaîne).

7 Notes

La validation jQuery ne fonctionne pas avec l’attribut Range et DateTime . Par


exemple, le code suivant affiche toujours une erreur de validation côté client, même
quand la date se trouve dans la plage spécifiée :
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]

Vous devez désactiver la validation de date jQuery pour utiliser l’attribut Range avec
DateTime . Il n’est généralement pas recommandé de compiler des dates dures dans vos

modèles. Par conséquent, l’utilisation de l’attribut Range et de DateTime est


déconseillée.

Le code suivant illustre la combinaison d’attributs sur une seule ligne :

C#

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Display(Name = "Release Date"), DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$"), Required, StringLength(30)]
public string Genre { get; set; }
[Range(1, 100), DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$"), StringLength(5)]
public string Rating { get; set; }
}

Dans la partie suivante de la série, nous examinons l’application et nous apportons des
améliorations aux méthodes Details et Delete générées automatiquement.

Ressources supplémentaires
Utilisation des formulaires
Globalisation et localisation
Introduction aux Tag Helpers
Créer des Tag Helpers

Précédent Suivant
6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 10, Examiner les méthodes Details
et Delete d’une application ASP.NET
Core
Article • 30/11/2023

Par Rick Anderson

Ouvrez le contrôleur vidéo et examinez la méthode Details :

C#

// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie


.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}

return View(movie);
}

Le moteur de génération de modèles automatique MVC qui a créé cette méthode


d’action ajoute un commentaire présentant une requête HTTP qui appelle la méthode.
Dans le cas présent, il s’agit d’une requête GET avec trois segments d’URL : le contrôleur
Movies , la méthode Details et une valeur id . N’oubliez pas que ces segments sont

définis dans Program.cs .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

EF facilite la recherche de données à l’aide de la méthode FirstOrDefaultAsync . Une


fonctionnalité de sécurité importante intégrée à la méthode réside dans le fait que le
code vérifie que la méthode de recherche a trouvé un film avant de tenter toute
opération que ce soit avec lui. Par exemple, un pirate informatique pourrait induire des
erreurs dans le site en modifiant l’URL créée par les liens, en remplaçant
http://localhost:{PORT}/Movies/Details/1 par quelque chose comme
http://localhost:{PORT}/Movies/Details/12345 (ou une autre valeur qui ne représente

pas un film réel). Si vous avez recherché un film null, l’application lève une exception.

Examinez les méthodes Delete et DeleteConfirmed .

C#

// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{

if (id == null)
{
return NotFound();
}

var movie = await _context.Movie


.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}

return View(movie);
}

// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var movie = await _context.Movie.FindAsync(id);
if (movie != null)
{
_context.Movie.Remove(movie);
}

await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}

Notez que la méthode HTTP GET Delete ne supprime pas le film spécifié, mais retourne
une vue du film où vous pouvez soumettre (HttpPost) la suppression. L’exécution d’une
opération de suppression en réponse à une requête GET (ou encore l’exécution d’une
opération de modification, d’une opération de création ou de toute autre opération qui
modifie des données) génère une faille de sécurité.
La méthode [HttpPost] qui supprime les données est nommée DeleteConfirmed pour
donner à la méthode HTTP POST une signature ou un nom unique. Les signatures des
deux méthodes sont illustrées ci-dessous :

C#

// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{

C#

// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{

Le Common Language Runtime (CLR) nécessite des méthodes surchargées pour avoir
une signature à paramètre unique (même nom de méthode, mais liste de paramètres
différentes). Toutefois, vous avez ici besoin de deux méthodes Delete (une pour GET et
une pour POST) ayant toutes les deux la même signature de paramètre. (Elles doivent
toutes les deux accepter un entier unique comme paramètre.)

Il existe deux approches pour ce problème. L’une consiste à attribuer aux méthodes des
noms différents. C’est ce qu’a fait le mécanisme de génération de modèles automatique
dans l’exemple précédent. Toutefois, elle présente un petit problème : ASP.NET mappe
des segments d’une URL à des méthodes d’action par nom. Si vous renommez une
méthode, il est probable que le routage ne pourra pas trouver cette méthode. La
solution consiste à faire ce que vous voyez dans l’exemple, c’est-à-dire à ajouter
l’attribut ActionName("Delete") à la méthode DeleteConfirmed . Cet attribut effectue un
mappage du système de routage afin qu’une URL qui inclut /Delete/ pour une requête
POST trouve la méthode DeleteConfirmed .

Pour contourner le problème des méthodes qui ont des noms et des signatures
identiques, vous pouvez également modifier artificiellement la signature de la méthode
POST pour inclure un paramètre supplémentaire (inutilisé). C’est ce que nous avons fait
dans une publication précédente quand nous avons ajouté le paramètre notUsed . Vous
pouvez faire de même ici pour la méthode [HttpPost] Delete :

C#

// POST: Movies/Delete/6
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id, bool notUsed)

Publier sur Azure


Pour plus d’informations sur le déploiement sur Azure, consultez Tutoriel : Créer une
application ASP.NET Core et SQL Database dans Azure App Service.

Modèles d’application web fiables


Consultez Le modèle d’application web fiable for.NETvidéos YouTube et l’article pour
obtenir des conseils sur la création d’une application ASP.NET Core moderne, fiable,
performante, testable, économique et évolutive, que ce soit à partir de zéro ou en
refactorisant une application existante.

Précédent

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriels ASP.NET Core Blazor
Article • 30/11/2023

Les didacticiels suivants fournissent des expériences de travail de base pour la création
d'applications Blazor.

Pour une présentation de Blazor, consultez ASP.NET Core Blazor.

Créez votre première Blazor application

Créer une Blazor application de liste de tâches (Blazor Web App)

Utiliser ASP.NET Core SignalR avec Blazor (Blazor Web App)

Didacticiels ASP.NET Core Blazor Hybrid

Microsoft Learn
Blazor Parcours d'apprentissage
Blazor Modules d'apprentissage

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : créer une API web avec
ASP.NET Core
Article • 03/01/2024

Par Rick Anderson et Kirk Larkin

Ce tutoriel explique les bases de la création d’une API web basée sur un contrôleur qui
utilise une base de données. Une autre approche pour créer des API dans ASP.NET Core
consiste à créer des API minimales. Pour obtenir de l’aide sur le choix entre les API
minimales et les API basées sur un contrôleur, consultez Vue d’ensemble des API. Pour
obtenir un tutoriel sur la création d’une API minimale, consultez Tutoriel : Créer une API
minimale avec ASP.NET Core.

Vue d'ensemble
Ce didacticiel crée l’API suivante :

ノ Agrandir le tableau

API Description Corps de la Corps de


requête réponse

GET /api/todoitems Obtenir toutes les tâches Aucune Tableau de


tâches

GET /api/todoitems/{id} Obtenir un élément par ID Aucune Tâche

POST /api/todoitems Ajouter un nouvel élément Tâche Tâche

PUT /api/todoitems/{id} Mettre à jour un élément Tâche Aucune


existant

DELETE /api/todoitems/{id} Supprimer un élément Aucune Aucune

Le diagramme suivant illustre la conception de l’application.


Prérequis
Visual Studio

Préversion de Visual Studio 2022 avec la charge de travail ASP.NET et


développement web.

Créer un projet web


Visual Studio
Dans le menu Fichier, sélectionnez Nouveau>Projet.
Entrez API web dans le champ de recherche.
Sélectionnez le modèle API web ASP.NET Core, puis Suivant.
Dans la boîte de dialogue Configurer votre nouveau projet, nommez le projet
TodoApi, puis sélectionnez Suivant.
Dans la boîte de dialogue Informations supplémentaires :
Vérifiez que le Framework est .NET 8.0 (prise en charge à long terme).
Vérifiez que la case à cocher Utiliser des contrôleurs (décocher pour
utiliser les API minimales) est cochée.
Vérifiez que la case Activer la prise en charge d’OpenAPI est cochée.
Sélectionnez Créer.

Ajouter un package NuGet


Un package NuGet doit être ajouté pour prendre en charge la base de données
utilisée dans ce tutoriel.

Dans le menu Outils, sélectionnez Gestionnaire de package NuGet > Gérer


les packages NuGet pour la solution.
Sélectionnez l’onglet Parcourir.
Entrez Microsoft.EntityFrameworkCore.InMemory dans la zone de recherche,
puis sélectionnez Microsoft.EntityFrameworkCore.InMemory .
Cochez la case Projet dans le volet droit, puis sélectionnez Installer.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Tester le projet
Le modèle de projet crée une API WeatherForecast avec prise en charge de Swagger.

Visual Studio

Appuyez sur Ctrl+F5 pour exécuter sans le débogueur.


Visual Studio affiche la boîte de dialogue suivante lorsqu’un projet n’est pas encore
configuré pour utiliser SSL :

Sélectionnez Oui si vous faites confiance au certificat SSL d’IIS Express.

La boîte de dialogue suivante s’affiche :

Sélectionnez Oui si vous acceptez d’approuver le certificat de développement.

Pour plus d’informations sur l’approbation du navigateur Firefox, consultez Erreur


de certificat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.

Visual Studio lance le navigateur par défaut et accède à https://localhost:


<port>/swagger/index.html , <port> étant un numéro de port choisi de manière

aléatoire défini à la création du projet.


La page /swagger/index.html Swagger s’affiche. Sélectionnez GET (Obtenir)>Try it out
(Essayer)>Execute (Exécuter). La page affiche :

La commande Curl pour tester l’API WeatherForecast.


L’URL pour tester l’API WeatherForecast.
Le code de réponse, le corps et les en-têtes.
Zone de liste déroulante avec les types de médias et l’exemple de valeur et de
schéma.

Si la page Swagger n’apparaît pas, consultez ce problème GitHub .

Swagger est utilisé pour générer de la documentation et des pages d’aide utiles pour les
API web. Ce tutoriel utilise Swagger pour tester l’application. Pour plus d’informations
sur Swagger, consultez la documentation de l’API web ASP.NET Core avec
Swagger/OpenAPI.

Copiez et collez l’URL de la requête dans le navigateur : https://localhost:


<port>/weatherforecast

Un code JSON similaire à l’exemple suivant est retourné :

JSON

[
{
"date": "2019-07-16T19:04:05.7257911-06:00",
"temperatureC": 52,
"temperatureF": 125,
"summary": "Mild"
},
{
"date": "2019-07-17T19:04:05.7258461-06:00",
"temperatureC": 36,
"temperatureF": 96,
"summary": "Warm"
},
{
"date": "2019-07-18T19:04:05.7258467-06:00",
"temperatureC": 39,
"temperatureF": 102,
"summary": "Cool"
},
{
"date": "2019-07-19T19:04:05.7258471-06:00",
"temperatureC": 10,
"temperatureF": 49,
"summary": "Bracing"
},
{
"date": "2019-07-20T19:04:05.7258474-06:00",
"temperatureC": -1,
"temperatureF": 31,
"summary": "Chilly"
}
]

Ajouter une classe de modèle


Un modèle est un ensemble de classes qui représentent les données gérées par
l’application. Le modèle pour cette application est la classe TodoItem .

Visual Studio

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet.


Sélectionnez Ajouter>Nouveau dossier. Nommez le dossier Models .
Cliquez avec le bouton de droite sur le dossier Models et sélectionnez
Ajouter>Classe. Nommez la classe TodoItem et sélectionnez sur Ajouter.
Remplacez le code du modèle par ce qui suit :

C#

namespace TodoApi.Models;

public class TodoItem


{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

La propriété Id fonctionne comme la clé unique dans une base de données


relationnelle.

Vous pouvez placer des classes de modèle n’importe où dans le projet, mais le dossier
Models est utilisé par convention.

Ajouter un contexte de base de données


Le contexte de base de données est la classe principale qui coordonne les fonctionnalités
d’Entity Framework pour un modèle de données. Cette classe est créée en dérivant de la
classe Microsoft.EntityFrameworkCore.DbContext.
Visual Studio

Cliquez avec le bouton de droite sur le dossier Models et sélectionnez


Ajouter>Classe. Nommez la classe TodoContext et cliquez sur Ajouter.

Entrez le code suivant :

C#

using Microsoft.EntityFrameworkCore;

namespace TodoApi.Models;

public class TodoContext : DbContext


{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}

public DbSet<TodoItem> TodoItems { get; set; } = null!;


}

Inscrire le contexte de base de données


Dans ASP.NET Core, les services tels que le contexte de base de données doivent être
inscrits auprès du conteneur d’injection de dépendances. Le conteneur fournit le service
aux contrôleurs.

Mettez à jour Program.cs avec le code mis en évidence suivant :

C#

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();


if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Le code précédent :

Ajoute les directives using .


Ajoute le contexte de base de données au conteneur d’injection de dépendances.
Spécifie que le contexte de base de données utilise une base de données en
mémoire.

Générer automatiquement des modèles pour


un contrôleur
Visual Studio

Cliquez avec le bouton droit sur le dossier Contrôleurs.

Sélectionnez Ajouter>Nouvel élément généré automatiquement.

Sélectionnez Contrôleur d’API avec actions, utilisant Entity Framework, puis


Ajouter.

Dans la boîte de dialogue Contrôleur d’API avec actions, utilisant Entity


Framework :
Sélectionnez TodoItem (TodoApi.Models) dans Classe de modèle.
Sélectionnez TodoContext (TodoApi.Models) dans Classe du contexte de
données.
Sélectionnez Ajouter.

Si l’opération de génération de modèles automatique échoue, sélectionnez


Ajouter pour essayer de générer une deuxième fois.
Le code généré :

Marque la classe avec l’attribut [ApiController]. Cet attribut indique que le


contrôleur répond aux requêtes de l’API web. Pour plus d’informations sur les
comportements spécifiques activés par l’attribut, consultez Créer des API web avec
ASP.NET Core.
Utilise l’injection de dépendances pour injecter le contexte de base de données
( TodoContext ) dans le contrôleur. Le contexte de base de données est utilisé dans
chacune des méthodes la CRUD du contrôleur.

Modèles ASP.NET Core pour :

Les contrôleurs avec vues incluent [action] dans le modèle d’itinéraire.


Les contrôleurs d’API n’incluent pas [action] dans le modèle d’itinéraire.

Lorsque le jeton [action] n’est pas dans le modèle d’itinéraire, le nom d’action (nom de
méthode) n’est pas inclus dans le point de terminaison. Autrement dit, le nom de
méthode associé de l’action n’est pas utilisé dans l’itinéraire correspondant.

Mettre à jour la méthode de création de


PostTodoItem
Mettez à jour l’instruction return dans PostTodoItem pour utiliser l’opérateur nameof :

C#

[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();

// return CreatedAtAction("GetTodoItem", new { id = todoItem.Id },


todoItem);
return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id },
todoItem);
}

Le code précédent est une méthode HTTP POST , indiquée par l’attribut [HttpPost]. La
méthode obtient la valeur de TodoItem dans le corps de la requête HTTP.

Pour plus d’informations, consultez Routage par attributs avec des attributs Http[Verbe].

La méthode CreatedAtAction :
Retourne un code d’état HTTP 201 en cas de réussite. HTTP 201 est la réponse
standard d’une méthode HTTP POST qui crée une ressource sur le serveur.
Ajoute un en-tête Location à la réponse. L’en-tête Location spécifie l’URI de
l’élément d’action qui vient d’être créé. Pour plus d’informations, consultez la
section 10.2.2 201 Created .
Fait référence à l’action GetTodoItem pour créer l’URI Location de l’en-tête. Le mot
clé nameof C# est utilisé pour éviter de coder en dur le nom de l’action dans
l’appel CreatedAtAction .

Tester PostTodoItem
Appuyez sur Ctrl+F5 pour exécuter l’application.

Dans la fenêtre du navigateur Swagger, sélectionnez POST /api/TodoItems, puis


Essayer.

Dans la fenêtre d’entrée Corps de la requête, mettez à jour le JSON. Par exemple :

JSON

{
"name": "walk dog",
"isComplete": true
}

Sélectionnez Exécuter
Tester l’URI de l’en-tête d’emplacement
Dans le POST précédent, l’interface utilisateur Swagger affiche l’en-tête
d’emplacement sous En-têtes de réponse. Par exemple, location:
https://localhost:7260/api/TodoItems/1 . L’en-tête d’emplacement affiche l’URI pour la

ressource créée.

Pour tester l’en-tête d’emplacement :

Dans la fenêtre du navigateur Swagger, sélectionnez GET /api/TodoItems/{id}, puis


Essayer.

Entrez 1 dans la zone de saisie id , puis sélectionnez Exécuter.


Examiner les méthodes GET
Deux points de terminaison GET sont implémentés :

GET /api/todoitems

GET /api/todoitems/{id}

La section précédente a montré un exemple d’itinéraire /api/todoitems/{id} .

Suivez les instructions POST pour ajouter un autre élément de tâche, puis testez
l’itinéraire /api/todoitems à l’aide de Swagger.

Cette application utilise une base de données en mémoire. Si l’application est arrêtée et
démarrée, la requête GET précédente ne retourne aucune donnée. Si aucune donnée
n’est retournée, publiez (POST) les données dans l’application.

Routage et chemins d’URL


L’attribut [HttpGet] désigne une méthode qui répond à une requête HTTP GET . Le
chemin d’URL pour chaque méthode est construit comme suit :

Partez de la chaîne de modèle dans l’attribut Route du contrôleur :

C#

[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase

Remplacez [controller] par le nom du contrôleur qui, par convention, est le nom
de la classe du contrôleur sans le suffixe « Controller ». Pour cet exemple, le nom
de la classe du contrôleur étant TodoItemsController, le nom du contrôleur est
« TodoItems ». Le routage d’ASP.NET Core ne respecte pas la casse.

Si l’attribut [HttpGet] a un modèle de route (par exemple, [HttpGet("products")] ),


ajoutez-le au chemin. Cet exemple n’utilise pas de modèle. Pour plus
d’informations, consultez Routage par attributs avec des attributs Http[Verbe].

Dans la méthode GetTodoItem suivante, "{id}" est une variable d’espace réservé pour
l’identificateur unique de la tâche. Quand GetTodoItem est appelée, la valeur de "{id}"
dans l’URL est fournie à la méthode dans son paramètre id .
C#

[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
{
return NotFound();
}

return todoItem;
}

Valeurs de retour
Le type de retour des méthodes GetTodoItems et GetTodoItem est le type
ActionResult<T>. ASP.NET Core sérialise automatiquement l’objet en JSON et écrit le
JSON dans le corps du message de réponse. Le code de réponse pour ce type de retour
est 200 OK , en supposant qu’il n’existe pas d’exception non gérée. Les exceptions non
gérées sont converties en erreurs 5xx.

Les types de retour ActionResult peuvent représenter une large plage de codes d’état
HTTP. Par exemple, GetTodoItem peut retourner deux valeurs d’état différentes :

Si aucun élément ne correspond à l’ID demandé, la méthode retourne un code


d’erreur d’état 404 NotFound.
Sinon, la méthode retourne 200 avec un corps de réponse JSON. Le retour de item
entraîne une réponse HTTP 200 .

Méthode PutTodoItem
Examinez la méthode PutTodoItem :

C#

[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
if (id != todoItem.Id)
{
return BadRequest();
}
_context.Entry(todoItem).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoItemExists(id))
{
return NotFound();
}
else
{
throw;
}
}

return NoContent();
}

PutTodoItem est similaire à PostTodoItem , à la différence près qu’il utilise HTTP PUT . La

réponse est 204 (Pas de contenu) . D’après la spécification HTTP, une requête PUT
nécessite que le client envoie toute l’entité mise à jour, et pas seulement les
changements. Pour prendre en charge les mises à jour partielles, utilisez HTTP PATCH.

Tester la méthode PutTodoItem


Cet exemple utilise une base de données en mémoire qui doit être initialisée à chaque
démarrage de l’application. La base de données doit contenir un élément avant que
vous ne passiez un appel PUT. Appelez GET pour vérifier qu’un élément existe dans la
base de données avant d’effectuer un appel PUT.

À l’aide de l’interface utilisateur Swagger, utilisez le bouton PUT pour mettre à jour le
TodoItem avec Id = 1 et définir son nom sur "feed fish" . Notez que la réponse est

HTTP 204 No Content .

Méthode DeleteTodoItem
Examinez la méthode DeleteTodoItem :

C#

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}

_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();

return NoContent();
}

Tester la méthode DeleteTodoItem


Utilisez l’interface utilisateur Swagger pour supprimer le TodoItem avec Id = 1. Notez
que la réponse est HTTP 204 No Content .

Tester avec d’autres outils


Il existe de nombreux autres outils qui peuvent être utilisés pour tester les API web, par
exemple :

Explorateur de points de terminaison Visual Studio et fichiers .http


http-repl
Postman
curl . Swagger utilise curl et affiche les commandes curl qu’il envoie.
Fiddler

Pour plus d'informations, voir :

Tutoriel des API minimales : tester avec les fichiers .http et l’explorateur de points
de terminaison
Tester les API avec Postman
Installer et tester les API avec http-repl

Empêcher la sur-publication
Actuellement, l’exemple d’application expose l’ensemble de l’objet TodoItem . Les
applications de production limitent généralement les données entrées et retournées à
l’aide d’un sous-ensemble du modèle. Il y a plusieurs raisons à cela, et la sécurité en est
une majeure. Le sous-ensemble d’un modèle est généralement appelé objet de transfert
de données (DTO), modèle d’entrée ou modèle de vue. DTO est utilisé dans ce tutoriel.
Un DTO peut être utilisé pour :

Empêcher la sur-publication.
Masquer les propriétés que les clients ne sont pas censés voir.
Omettre certaines propriétés afin de réduire la taille de la charge utile.
Aplatir les graphes d’objets qui contiennent des objets imbriqués. Les graphiques
d’objets aplatis peuvent être plus pratiques pour les clients.

Pour illustrer l’approche DTO, mettez à jour la classe TodoItem pour inclure un champ
secret :

C#

namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
}

Le champ secret doit être masqué dans cette application, mais une application
administrative peut choisir de l’exposer.

Vérifiez que vous pouvez publier et obtenir le champ secret.

Créez un modèle DTO :

C#

namespace TodoApi.Models;

public class TodoItemDTO


{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

Mettez à jour TodoItemsController pour utiliser TodoItemDTO :

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

namespace TodoApi.Controllers;

[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;

public TodoItemsController(TodoContext context)


{
_context = context;
}

// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
return await _context.TodoItems
.Select(x => ItemToDTO(x))
.ToListAsync();
}

// GET: api/TodoItems/5
// <snippet_GetByID>
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
{
return NotFound();
}

return ItemToDTO(todoItem);
}
// </snippet_GetByID>

// PUT: api/TodoItems/5
// To protect from overposting attacks, see
https://go.microsoft.com/fwlink/?linkid=2123754
// <snippet_Update>
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO
todoDTO)
{
if (id != todoDTO.Id)
{
return BadRequest();
}

var todoItem = await _context.TodoItems.FindAsync(id);


if (todoItem == null)
{
return NotFound();
}

todoItem.Name = todoDTO.Name;
todoItem.IsComplete = todoDTO.IsComplete;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
{
return NotFound();
}

return NoContent();
}
// </snippet_Update>

// POST: api/TodoItems
// To protect from overposting attacks, see
https://go.microsoft.com/fwlink/?linkid=2123754
// <snippet_Create>
[HttpPost]
public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO
todoDTO)
{
var todoItem = new TodoItem
{
IsComplete = todoDTO.IsComplete,
Name = todoDTO.Name
};

_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();

return CreatedAtAction(
nameof(GetTodoItem),
new { id = todoItem.Id },
ItemToDTO(todoItem));
}
// </snippet_Create>

// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}

_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();

return NoContent();
}

private bool TodoItemExists(long id)


{
return _context.TodoItems.Any(e => e.Id == id);
}

private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>


new TodoItemDTO
{
Id = todoItem.Id,
Name = todoItem.Name,
IsComplete = todoItem.IsComplete
};
}

Vérifiez que vous ne pouvez pas publier ou obtenir le champ secret.

Appelez l’API web avec JavaScript


Consultez Tutoriel : Appeler une API web ASP.NET Core avec JavaScript.

Série de vidéos d’API web


Consultez Vidéo : Série pour débutants : API web.

Modèles d’application web fiables


Consultez Le modèle d’application web fiable for.NETvidéos YouTube et l’article pour
obtenir des conseils sur la création d’une application ASP.NET Core moderne, fiable,
performante, testable, économique et évolutive, que ce soit à partir de zéro ou en
refactorisant une application existante.

Ajouter la prise en charge de l’authentification


à une API web
ASP.NET Core Identity ajoute la fonctionnalité de connexion de l’interface utilisateur aux
applications web ASP.NET Core. Pour sécuriser les API web et SPA, utilisez l’une des
options suivantes :
Microsoft Entra ID
Azure Active Directory B2C (Azure AD B2C)
Serveur Identity Duende

Duende Identity Server est un framework OpenID Connect et OAuth 2.0 pour ASP.NET
Core. Duende Identity Server active les fonctionnalités de sécurité suivantes :

Authentification en tant que service (AaaS)


Authentification/déconnexion unique (SSO) sur plusieurs types d’applications
Contrôle d’accès pour les API
Federation Gateway

) Important

Duende Software peut vous demander de payer des frais de licence pour une
utilisation en production de Duende Identity Server. Pour plus d’informations,
consultez Migrer de ASP.NET Core 5.0 vers 6.0.

Pour plus d’informations, consultez la documentation Duende Identity Server (site web
de Duende Software) .

Publier sur Azure


Pour plus d’informations sur le déploiement sur Azure, consultez Démarrage rapide :
Déployer une application web ASP.NET.

Ressources supplémentaires
Affichez ou téléchargez l’exemple de code de ce tutoriel . Consultez Guide pratique
pour télécharger.

Pour plus d'informations, reportez-vous aux ressources suivantes :

Créer des API web avec ASP.NET Core


Tutoriel : Créer une API minimale avec ASP.NET Core
Documentation de l’API web ASP.NET Core avec Swagger/OpenAPI
Razor Pages avec Entity Framework Core dans ASP.NET Core - Tutoriel 1 sur 8
Routage vers les actions du contrôleur dans ASP.NET Core
Types de retour des actions du contrôleur dans l’API web ASP.NET Core
Déployer des applications ASP.NET Core sur Azure App Service
Héberger et déployer ASP.NET Core
Créer une API web avec ASP.NET Core

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Créer une API web avec ASP.NET Core et
MongoDB
Article • 30/11/2023

Par Pratik Khandelwal et Scott Addie

Ce tutoriel crée une API web qui exécute des opérations de création, lecture, mise à jour
et suppression (CRUD) sur une base de données NoSQL MongoDB .

Dans ce tutoriel, vous allez apprendre à :

" Configurer MongoDB
" Créer une base de données MongoDB
" Définir une collection et un schéma MongoDB
" Effectuer des opérations CRUD MongoDB à partir d’une API web
" Personnaliser la sérialisation JSON

Prérequis
MongoDB 6.0.5 ou version ultérieure
Interpréteur de commandes MongoDB

Visual Studio

Préversion de Visual Studio 2022 avec la charge de travail ASP.NET et


développement web.
Configurer MongoDB
Activez l’accès à MongoDB et Mongo DB Shell à partir de n’importe où sur l’ordinateur
de développement :

1. Sur Windows, MongoDB est installé par défaut dans C:\Program Files\MongoDB.
Ajoutez C:\Program Files\MongoDB\Server<numéro_version>\bin à la variable
d’environnement PATH .

2. Téléchargez l’interpréteur de commandes MongoDB et choisissez un répertoire


dans lequel l’extraire. Ajoutez le chemin d’accès obtenu pour mongosh.exe à la
variable d’environnement PATH .

3. Choisissez sur l’ordinateur de développement un répertoire pour le stockage des


données. Par exemple, C:\BooksData sous Windows. Le cas échéant, créez le
répertoire. L’interpréteur de commandes mongo ne crée pas nouveaux répertoires.

4. Dans l’interpréteur de commandes du système d’exploitation (et non l’interpréteur


de commandes MongoDB), utilisez la commande suivante pour vous connecter à
MongoDB sur le port par défaut 27017. Remplacez <data_directory_path> par le
répertoire choisi à l’étape précédente.

Console

mongod --dbpath <data_directory_path>


Utilisez l’interpréteur de commandes MongoDB précédemment installé dans les étapes
suivantes pour créer une base de données, des collections, et stocker des documents.
Pour plus d’informations sur les commandes MongoDB Shell, consultez mongosh .

1. Ouvrez une instance de l’interpréteur de commandes MongoDB en lançant


mongosh.exe .

2. Dans l’interpréteur de commandes, connectez-vous à la base de données de test


par défaut en exécutant la commande suivante :

Console

mongosh

3. Exécutez la commande suivante dans le shell de commandes :

Console

use BookStore

Une base de données nommée BookStore est créée si elle n’existe pas déjà. Si la
base de données existe déjà, sa connexion est ouverte pour les transactions.

4. Créez une collection Books à l’aide de la commande suivante :

Console

db.createCollection('Books')

Le résultat suivant s’affiche :

Console

{ "ok" : 1 }

5. Définissez un schéma pour la collection Books et insérez deux documents à l’aide


de la commande suivante :

Console

db.Books.insertMany([{ "Name": "Design Patterns", "Price": 54.93,


"Category": "Computers", "Author": "Ralph Johnson" }, { "Name": "Clean
Code", "Price": 43.15, "Category": "Computers","Author": "Robert C.
Martin" }])
Un résultat similaire à ce qui suit s’affiche :

Console

{
"acknowledged" : true,
"insertedIds" : [
ObjectId("61a6058e6c43f32854e51f51"),
ObjectId("61a6058e6c43f32854e51f52")
]
}

7 Notes

Les ObjectId affichés dans le résultat précédent ne correspondent pas à ceux


affichés dans l’interpréteur de commandes.

6. Affichez les documents de la base de données à l’aide de la commande suivante :

Console

db.Books.find().pretty()

Un résultat similaire à ce qui suit s’affiche :

Console

{
"_id" : ObjectId("61a6058e6c43f32854e51f51"),
"Name" : "Design Patterns",
"Price" : 54.93,
"Category" : "Computers",
"Author" : "Ralph Johnson"
}
{
"_id" : ObjectId("61a6058e6c43f32854e51f52"),
"Name" : "Clean Code",
"Price" : 43.15,
"Category" : "Computers",
"Author" : "Robert C. Martin"
}

Le schéma ajoute une propriété _id automatiquement générée de type ObjectId


pour chaque document.
Créer le projet d’API web ASP.NET Core
Visual Studio

1. Accédez à Fichier>Nouveau>Projet.

2. Sélectionnez le type de projet API web ASP.NET Core, puis Suivant.

3. Nommez le projet BookStoreApi, puis sélectionnez Suivant.

4. Sélectionnez le framework .NET 8.0 (prise en charge à long terme), puis


Créer.

5. Dans la fenêtre Console du Gestionnaire de Package, accédez à la racine du


projet. Exécutez la commande suivante afin d’installer le pilote .NET pour
MongoDB :

PowerShell

Install-Package MongoDB.Driver

Ajouter un modèle d’entité


1. Ajoutez un répertoire Models à la racine du projet.

2. Ajoutez une classe Book au répertoire Models avec le code suivant :

C#

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace BookStoreApi.Models;

public class Book


{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string? Id { get; set; }

[BsonElement("Name")]
public string BookName { get; set; } = null!;

public decimal Price { get; set; }


public string Category { get; set; } = null!;

public string Author { get; set; } = null!;


}

Dans la classe précédente, la propriété Id est :

Requise pour mapper l’objet Common Language Runtime (CLR) à la


collection MongoDB.
Annotée avec [BsonId] pour faire de cette propriété la clé primaire du
document.
Annotée avec [BsonRepresentation(BsonType.ObjectId)] pour autoriser le
passage du paramètre en tant que type string au lieu d’une structure
ObjectId . Mongo gère la conversion de string en ObjectId .

La propriété BookName est annotée avec l’attribut [BsonElement] . La valeur de


l’attribut de Name représente le nom de propriété dans la collection MongoDB.

Ajouter un modèle de configuration


1. Ajoutez les valeurs de configuration de base de données suivantes à
appsettings.json :

JSON

{
"BookStoreDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "BookStore",
"BooksCollectionName": "Books"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

2. Ajoutez une classe BookStoreDatabaseSettings au répertoire Models avec le code


suivant :

C#
namespace BookStoreApi.Models;

public class BookStoreDatabaseSettings


{
public string ConnectionString { get; set; } = null!;

public string DatabaseName { get; set; } = null!;

public string BooksCollectionName { get; set; } = null!;


}

La classe BookStoreDatabaseSettings précédente est utilisée pour stocker les


valeurs de propriété BookStoreDatabase du fichier appsettings.json . Les noms de
propriétés JSON et C# sont nommés de manière identique pour faciliter le
processus de mappage.

3. Ajoutez le code en surbrillance suivant à Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.Configure<BookStoreDatabaseSettings>(
builder.Configuration.GetSection("BookStoreDatabase"));

Dans le code précédent, l’instance de configuration à laquelle la section


BookStoreDatabase du fichier appsettings.json est liée est inscrite dans le

conteneur d’injection de dépendances. Par exemple, la propriété ConnectionString


de l’objet BookStoreDatabaseSettings est peuplée avec la propriété
BookStoreDatabase:ConnectionString dans appsettings.json .

4. Ajoutez le code suivant en haut de Program.cs pour résoudre la référence à


BookStoreDatabaseSettings :

C#

using BookStoreApi.Models;

Ajouter un service d’opérations CRUD


1. Ajoutez un répertoire Services à la racine du projet.
2. Ajoutez une classe BooksService au répertoire Services avec le code suivant :

C#

using BookStoreApi.Models;
using Microsoft.Extensions.Options;
using MongoDB.Driver;

namespace BookStoreApi.Services;

public class BooksService


{
private readonly IMongoCollection<Book> _booksCollection;

public BooksService(
IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
{
var mongoClient = new MongoClient(
bookStoreDatabaseSettings.Value.ConnectionString);

var mongoDatabase = mongoClient.GetDatabase(


bookStoreDatabaseSettings.Value.DatabaseName);

_booksCollection = mongoDatabase.GetCollection<Book>(
bookStoreDatabaseSettings.Value.BooksCollectionName);
}

public async Task<List<Book>> GetAsync() =>


await _booksCollection.Find(_ => true).ToListAsync();

public async Task<Book?> GetAsync(string id) =>


await _booksCollection.Find(x => x.Id ==
id).FirstOrDefaultAsync();

public async Task CreateAsync(Book newBook) =>


await _booksCollection.InsertOneAsync(newBook);

public async Task UpdateAsync(string id, Book updatedBook) =>


await _booksCollection.ReplaceOneAsync(x => x.Id == id,
updatedBook);

public async Task RemoveAsync(string id) =>


await _booksCollection.DeleteOneAsync(x => x.Id == id);
}

Dans le code précédent, une instance de BookStoreDatabaseSettings est récupérée


à partir de l’injection de dépendances via l’injection de constructeur. Cette
technique permet d’accéder aux valeurs de configuration d’ appsettings.json , qui
ont été ajoutées à la section Ajouter un modèle de configuration.

3. Ajoutez le code en surbrillance suivant à Program.cs :


C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.Configure<BookStoreDatabaseSettings>(
builder.Configuration.GetSection("BookStoreDatabase"));

builder.Services.AddSingleton<BooksService>();

Dans le code précédent, la classe BooksService est inscrite auprès de l’injection de


dépendances pour permettre la prise en charge de l’injection de constructeur dans
les classes consommatrices. La durée de vie de service de singleton est la plus
appropriée, car BooksService a une dépendance directe sur MongoClient . Selon les
recommandations officielles de réutilisation de Mongo Client , MongoClient doit
être inscrit auprès de l’injection de dépendances avec une durée de vie de service
de singleton.

4. Ajoutez le code suivant en haut de Program.cs pour résoudre la référence à


BooksService :

C#

using BookStoreApi.Services;

La classe BooksService utilise les membres MongoDB.Driver suivants pour exécuter des
opérations CRUD dans la base de données :

MongoClient : Lit l’instance de serveur qui permet d’exécuter des opérations de


base de données. Le constructeur de cette classe reçoit la chaîne de connexion
MongoDB :

C#

public BooksService(
IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
{
var mongoClient = new MongoClient(
bookStoreDatabaseSettings.Value.ConnectionString);

var mongoDatabase = mongoClient.GetDatabase(


bookStoreDatabaseSettings.Value.DatabaseName);

_booksCollection = mongoDatabase.GetCollection<Book>(
bookStoreDatabaseSettings.Value.BooksCollectionName);
}
IMongoDatabase : Représente la base de données Mongo qui permet d’exécuter
des opérations. Ce tutoriel utilise la méthode générique
GetCollection<TDocument>(collection) sur l’interface pour accéder aux données
d’une collection spécifique. Exécutez des opérations CRUD sur la collection, après
l’appel de cette méthode. Dans l’appel à la méthode GetCollection<TDocument>
(collection) :
collection représente le nom de la collection.

TDocument représente le type d’objet CLR stocké dans la collection.

GetCollection<TDocument>(collection) retourne un objet MongoCollection

représentant la collection. Dans ce didacticiel, les méthodes suivantes sont appelées sur
la collection :

DeleteOneAsync : Supprime un seul document correspondant aux critères de


recherche fournis.
Find<TDocument> : Retourne tous les documents de la collection
correspondant aux critères de recherche fournis.
InsertOneAsync : Insère l’objet fourni en tant que nouveau document dans la
collection.
ReplaceOneAsync : Remplace le seul document correspondant aux critères de
recherche fournis par l’objet indiqué.

Ajout d'un contrôleur


Ajoutez une classe BooksController au répertoire Contrôleurs avec le code suivant :

C#

using BookStoreApi.Models;
using BookStoreApi.Services;
using Microsoft.AspNetCore.Mvc;

namespace BookStoreApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
private readonly BooksService _booksService;

public BooksController(BooksService booksService) =>


_booksService = booksService;

[HttpGet]
public async Task<List<Book>> Get() =>
await _booksService.GetAsync();
[HttpGet("{id:length(24)}")]
public async Task<ActionResult<Book>> Get(string id)
{
var book = await _booksService.GetAsync(id);

if (book is null)
{
return NotFound();
}

return book;
}

[HttpPost]
public async Task<IActionResult> Post(Book newBook)
{
await _booksService.CreateAsync(newBook);

return CreatedAtAction(nameof(Get), new { id = newBook.Id },


newBook);
}

[HttpPut("{id:length(24)}")]
public async Task<IActionResult> Update(string id, Book updatedBook)
{
var book = await _booksService.GetAsync(id);

if (book is null)
{
return NotFound();
}

updatedBook.Id = book.Id;

await _booksService.UpdateAsync(id, updatedBook);

return NoContent();
}

[HttpDelete("{id:length(24)}")]
public async Task<IActionResult> Delete(string id)
{
var book = await _booksService.GetAsync(id);

if (book is null)
{
return NotFound();
}

await _booksService.RemoveAsync(id);

return NoContent();
}
}
Le contrôleur d’API web précédent :

Utilise la classe BooksService pour exécuter des opérations CRUD.


Contient des méthodes d’action pour prendre en charge les requêtes HTTP GET,
POST, PUT et DELETE.
Appelle CreatedAtAction dans la méthode d’action Create pour retourner une
réponse HTTP 201 . Le code d’état 201 est la réponse standard d’une méthode
HTTP POST qui crée une ressource sur le serveur. CreatedAtAction ajoute
également un en-tête Location à la réponse. L’en-tête Location spécifie l’URI du
livre qui vient d’être créé.

Tester l’API web


1. Générez et exécutez l'application.

2. Accédez à https://localhost:<port>/api/books , où <port> est le numéro de port


attribué automatiquement à l’application, pour tester la méthode d’action Get
sans paramètre du contrôleur. Une réponse JSON similaire à la suivante s’affiche :

JSON

[
{
"id": "61a6058e6c43f32854e51f51",
"bookName": "Design Patterns",
"price": 54.93,
"category": "Computers",
"author": "Ralph Johnson"
},
{
"id": "61a6058e6c43f32854e51f52",
"bookName": "Clean Code",
"price": 43.15,
"category": "Computers",
"author": "Robert C. Martin"
}
]

3. Accédez à https://localhost:<port>/api/books/{id here} pour tester la méthode


d’action Get surchargée du contrôleur. Une réponse JSON similaire à la suivante
s’affiche :

JSON
{
"id": "61a6058e6c43f32854e51f52",
"bookName": "Clean Code",
"price": 43.15,
"category": "Computers",
"author": "Robert C. Martin"
}

Configurer les options de sérialisation JSON


Vous devez changer deux détails concernant les réponses JSON retournées dans la
section Tester l’API web :

Vous devez changer la casse mixte par défaut des noms de propriétés pour qu’elle
corresponde à la casse Pascal des noms de propriétés de l’objet CLR.
La propriété bookName doit être retournée sous la forme Name .

Pour satisfaire les exigences précédentes, apportez les changements suivants :

1. Dans Program.cs , ajoutez le code en surbrillance suivant à l’appel de méthode


AddControllers :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.Configure<BookStoreDatabaseSettings>(
builder.Configuration.GetSection("BookStoreDatabase"));

builder.Services.AddSingleton<BooksService>();

builder.Services.AddControllers()
.AddJsonOptions(
options => options.JsonSerializerOptions.PropertyNamingPolicy =
null);

À la suite du changement effectué, les noms de propriétés de la réponse JSON


sérialisée de l’API web correspondent aux noms de propriétés équivalents du type
d’objet CLR. Par exemple, la propriété Author de la classe Book est sérialisée en
tant que Author au lieu de author .

2. Dans Models/Book.cs , annotez la propriété BookName avec l’attribut


[JsonPropertyName] :
C#

[BsonElement("Name")]
[JsonPropertyName("Name")]
public string BookName { get; set; } = null!;

La valeur de l’attribut [JsonPropertyName] de Name représente le nom de propriété


dans la réponse JSON sérialisée de l’API web.

3. Ajoutez le code suivant en haut de Models/Book.cs pour résoudre la référence


d’attribut [JsonProperty] :

C#

using System.Text.Json.Serialization;

4. Répétez les étapes définies dans la section Tester l’API web. Notez la différence des
noms de propriétés JSON.

Ajouter la prise en charge de l’authentification


à une API web
ASP.NET Core Identity ajoute la fonctionnalité de connexion de l’interface utilisateur aux
applications web ASP.NET Core. Pour sécuriser les API web et SPA, utilisez l’une des
options suivantes :

Microsoft Entra ID
Azure Active Directory B2C (Azure AD B2C)
Serveur Identity Duende

Duende Identity Server est un framework OpenID Connect et OAuth 2.0 pour ASP.NET
Core. Duende Identity Server active les fonctionnalités de sécurité suivantes :

Authentification en tant que service (AaaS)


Authentification/déconnexion unique (SSO) sur plusieurs types d’applications
Contrôle d’accès pour les API
Federation Gateway

) Important

Duende Software peut vous demander de payer des frais de licence pour une
utilisation en production de Duende Identity Server. Pour plus d’informations,
consultez Migrer de ASP.NET Core 5.0 vers 6.0.

Pour plus d’informations, consultez la documentation Duende Identity Server (site web
de Duende Software) .

Ressources supplémentaires
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Créer des API web avec ASP.NET Core
Types de retour des actions des contrôleurs dans l’API web ASP.NET Core
Créer une API web avec ASP.NET Core

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : Appeler une API web ASP.NET
Core avec JavaScript
Article • 06/12/2023

Par Rick Anderson

Ce tutoriel montre comment appeler une API web ASP.NET Core avec JavaScript à l’aide
de l’API Fetch .

Prérequis
Avoir effectué le Tutoriel : Créer une API web
Connaissance de CSS, HTML et JavaScript

Appelez l’API web avec JavaScript


Dans cette section, vous allez ajouter une page HTML contenant des formulaires
permettant de créer et de gérer des éléments de tâche. Des gestionnaires d’événements
sont joints aux éléments de la page. Les gestionnaires d’événements génèrent des
requêtes HTTP pour les méthodes d’action de l’API web. La fonction fetch de l’API
Fetch lance chaque requête HTTP.

La fonction fetch retourne un objet Promise qui contient une réponse HTTP
représentée sous la forme d’un objet Response . Un modèle courant consiste à extraire le
corps de réponse JSON en appelant la fonction json sur l’objet Response . JavaScript
met à jour la page avec les détails de la réponse de l’API Web.

L'appel fetch le plus simple accepte un seul paramètre représentant l’itinéraire. Un


deuxième paramètre, connu sous le nom d’objet init , est facultatif. init est utilisé
pour configurer la requête HTTP.

1. Configurez l’application pour traiter les fichiers statiques et activer le mappage du


fichier par défaut. Le code mis en surbrillance ci-dessous est nécessaire dans
Program.cs :

C#

using Microsoft.EntityFrameworkCore;
using TodoApi.Models;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));

var app = builder.Build();

if (builder.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseDefaultFiles();
app.UseStaticFiles();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

1. Créez un dossier wwwroot dans le répertoire du projet.

2. Créez un dossier css à l’intérieur du dossier wwwroot.

3. Créez un dossier js à l’intérieur du dossier wwwroot.

4. Ajoutez un fichier HTML nommé index.html au dossier wwwroot. Remplacez le


contenu de index.html par le balisage suivant :

HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<link rel="stylesheet" href="css/site.css" />
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST"
onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>

<div id="editForm">
<h3>Edit</h3>
<form action="javascript:void(0);" onsubmit="updateItem()">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Save">
<a onclick="closeInput()" aria-label="Close">&#10006;</a>
</form>
</div>

<p id="counter"></p>

<table>
<tr>
<th>Is Complete?</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>

<script src="js/site.js" asp-append-version="true"></script>


<script type="text/javascript">
getItems();
</script>
</body>
</html>

5. Ajoutez un fichier CSS nommé site.css au dossier wwwroot/css. Remplacez le


contenu de site.css par les styles suivants :

css

input[type='submit'], button, [aria-label] {


cursor: pointer;
}

#editForm {
display: none;
}

table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}

th {
background-color: #f8f8f8;
padding: 5px;
}

td {
border: 1px solid;
padding: 5px;
}

6. Ajoutez un fichier JavaScript nommé site.js au dossier wwwroot/js. Remplacez le


contenu de site.js par le code suivant :

JavaScript

const uri = 'api/todoitems';


let todos = [];

function getItems() {
fetch(uri)
.then(response => response.json())
.then(data => _displayItems(data))
.catch(error => console.error('Unable to get items.', error));
}

function addItem() {
const addNameTextbox = document.getElementById('add-name');

const item = {
isComplete: false,
name: addNameTextbox.value.trim()
};

fetch(uri, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(response => response.json())
.then(() => {
getItems();
addNameTextbox.value = '';
})
.catch(error => console.error('Unable to add item.', error));
}

function deleteItem(id) {
fetch(`${uri}/${id}`, {
method: 'DELETE'
})
.then(() => getItems())
.catch(error => console.error('Unable to delete item.', error));
}

function displayEditForm(id) {
const item = todos.find(item => item.id === id);
document.getElementById('edit-name').value = item.name;
document.getElementById('edit-id').value = item.id;
document.getElementById('edit-isComplete').checked = item.isComplete;
document.getElementById('editForm').style.display = 'block';
}

function updateItem() {
const itemId = document.getElementById('edit-id').value;
const item = {
id: parseInt(itemId, 10),
isComplete: document.getElementById('edit-isComplete').checked,
name: document.getElementById('edit-name').value.trim()
};

fetch(`${uri}/${itemId}`, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(() => getItems())
.catch(error => console.error('Unable to update item.', error));

closeInput();

return false;
}

function closeInput() {
document.getElementById('editForm').style.display = 'none';
}

function _displayCount(itemCount) {
const name = (itemCount === 1) ? 'to-do' : 'to-dos';

document.getElementById('counter').innerText = `${itemCount}
${name}`;
}

function _displayItems(data) {
const tBody = document.getElementById('todos');
tBody.innerHTML = '';

_displayCount(data.length);

const button = document.createElement('button');

data.forEach(item => {
let isCompleteCheckbox = document.createElement('input');
isCompleteCheckbox.type = 'checkbox';
isCompleteCheckbox.disabled = true;
isCompleteCheckbox.checked = item.isComplete;
let editButton = button.cloneNode(false);
editButton.innerText = 'Edit';
editButton.setAttribute('onclick', `displayEditForm(${item.id})`);

let deleteButton = button.cloneNode(false);


deleteButton.innerText = 'Delete';
deleteButton.setAttribute('onclick', `deleteItem(${item.id})`);

let tr = tBody.insertRow();

let td1 = tr.insertCell(0);


td1.appendChild(isCompleteCheckbox);

let td2 = tr.insertCell(1);


let textNode = document.createTextNode(item.name);
td2.appendChild(textNode);

let td3 = tr.insertCell(2);


td3.appendChild(editButton);

let td4 = tr.insertCell(3);


td4.appendChild(deleteButton);
});

todos = data;
}

Vous devrez peut-être changer les paramètres de lancement du projet ASP.NET Core
pour tester la page HTML localement :

1. Ouvrez Properties\launchSettings.json.
2. Supprimez la propriété launchUrl pour forcer l’ouverture de l’application au
niveau de index.html (fichier par défaut du projet).

Cet exemple appelle toutes les méthodes CRUD de l’API web. Les explications suivantes
traitent des demandes de l’API web.

Obtenir une liste de tâches


Dans le code suivant, une requête HTTP GET est envoyée à la route api/todoitems :

JavaScript

fetch(uri)
.then(response => response.json())
.then(data => _displayItems(data))
.catch(error => console.error('Unable to get items.', error));
Quand l’API web retourne un code d’état de réussite, la fonction _displayItems est
appelée. Chaque élément de tâche du paramètre de tableau accepté par _displayItems
est ajouté à une table avec les boutons Modifier et Supprimer. Si la demande de l’API
Web échoue, une erreur est consignée dans la console du navigateur.

Ajouter une tâche


Dans le code suivant :

Une variable item est déclarée pour construire une représentation littérale d’objet
de l’élément de tâche.
Une requête Fetch est configurée avec les options suivantes :
method – spécifie le verbe d’action POST HTTP.

body – spécifie la représentation JSON du corps de la demande. Le JSON est

généré en transmettant le littéral d’objet stocké dans item à la fonction JSON.


stringify .
headers – spécifie les en-têtes de requête HTTP Accept et Content-Type . Les

deux en-têtes sont définies sur application/json pour spécifier le type de


média respectivement reçu et envoyé.
Une requête HTTP POST est envoyée à la route api/todoitems.

JavaScript

function addItem() {
const addNameTextbox = document.getElementById('add-name');

const item = {
isComplete: false,
name: addNameTextbox.value.trim()
};

fetch(uri, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(response => response.json())
.then(() => {
getItems();
addNameTextbox.value = '';
})
.catch(error => console.error('Unable to add item.', error));
}
Quand l’API web retourne un code d’état de réussite, la fonction getItems est appelée
pour mettre à jour la table HTML. Si la demande de l’API Web échoue, une erreur est
consignée dans la console du navigateur.

Mettre à jour une tâche


La mise à jour d’un élément de tâche est semblable à l’ajout d’un élément. Toutefois, il y
a deux différences importantes :

L’itinéraire est suivi de l’identificateur unique de l’élément à mettre à jour. Par


exemple, api/todoitems/1.
Le verbe d’action HTTP est PUT, comme indiqué par l’option method .

JavaScript

fetch(`${uri}/${itemId}`, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(() => getItems())
.catch(error => console.error('Unable to update item.', error));

Supprimer une tâche


Pour supprimer un élément de tâche, définissez l’option de la demande method sur
DELETE et spécifiez l’identificateur unique de l’élément dans l’URL.

JavaScript

fetch(`${uri}/${id}`, {
method: 'DELETE'
})
.then(() => getItems())
.catch(error => console.error('Unable to delete item.', error));

Passez au tutoriel suivant pour apprendre à générer des pages d’aide d’API web :

Bien démarrer avec Swashbuckle et ASP.NET Core


6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Créer des services backend pour les
applications mobiles natives avec
ASP.NET Core
Article • 30/11/2023

Par James Montemagno

Les applications mobiles peuvent communiquer avec les services back-end ASP.NET
Core. Pour obtenir des instructions sur la connexion de services web locaux à partir de
simulateurs iOS et d’émulateurs Android, consultez Se connecter à des services web
locaux à partir de simulateurs iOS et d’émulateurs Android.

Afficher ou télécharger l’exemple de code de services backend

Exemple d’application mobile native


Ce didacticiel présente comment créer des services back-end en utilisant ASP.NET Core
pour prendre en charge des applications mobiles natives. Il utilise l’application
Xamarin.Forms TodoREST comme client natif, qui inclut des clients natifs distincts pour
des appareils Android, iOS et Windows. Vous pouvez suivre le didacticiel lié pour créer
l’application native (et installer les outils Xamarin gratuits nécessaires), ainsi que
télécharger l’exemple de solution Xamarin. L’exemple Xamarin inclut un projet de
services API web ASP.NET Core que l’application ASP.NET Core de cet article remplace
(sans que des modifications soient requises par le client).
Fonctionnalités
L’application TodoREST prend en charge l’énumération, l’ajout, la suppression et la
mise à jour d’éléments To-Do. Chaque élément a un ID, un nom, des remarques et une
propriété qui indique si la tâche a été effectuée.

La vue principale des éléments, reproduite ci-dessus, montre le nom de chaque élément
et indique si la tâche est effectuée avec une marque.

Le fait d’appuyer sur l'icône + ouvre une boîte de dialogue permettant l’ajout d’un
élément :
Le fait de cliquer sur un élément de l’écran de la liste principale ouvre une boîte de
dialogue où les valeurs pour Name, Notes et Done peuvent être modifiées, et où vous
pouvez supprimer l’élément :
Pour le tester vous-même par rapport à l’application ASP.NET Core créée dans la section
suivante et exécutée sur votre ordinateur, mettez à jour la constante RestUrl de
l’application.

Les émulateurs Android ne s’exécutent pas sur l’ordinateur local et utilisent une adresse
IP de bouclage (10.0.2.2) pour communiquer avec l’ordinateur local. Tirez parti de
Xamarin.Essentials DeviceInfo pour détecter le système d’exploitation qui s’exécute afin
d’utiliser l’URL correcte.

Accédez au projet TodoREST et ouvrez le fichier Constants.cs . Le fichier


Constants.cs contient la configuration qui suit.
C#

using Xamarin.Essentials;
using Xamarin.Forms;

namespace TodoREST
{
public static class Constants
{
// URL of REST service
//public static string RestUrl =
"https://YOURPROJECT.azurewebsites.net:8081/api/todoitems/{0}";

// URL of REST service (Android does not use localhost)


// Use http cleartext for local deployment. Change to https for
production
public static string RestUrl = DeviceInfo.Platform ==
DevicePlatform.Android ? "http://10.0.2.2:5000/api/todoitems/{0}" :
"http://localhost:5000/api/todoitems/{0}";
}
}

Vous pouvez éventuellement déployer le service web sur un service cloud tel qu’Azure et
mettre à jour le RestUrl .

Création du projet ASP.NET Core


réez une application web ASP.NET Core dans Visual Studio. Choisissez le modèle d’API
web. Nommez le projet TodoAPI.
L’application doit répondre à toutes les demandes effectuées sur le port 5000, y compris
le trafic HTTP en texte clair pour notre client mobile. Mettez à jour Startup.cs pour que
UseHttpsRedirection ne s’exécute donc pas dans le développement :

C#

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)


{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// For mobile apps, allow http traffic.
app.UseHttpsRedirection();
}

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
7 Notes

Exécutez l’application directement, plutôt que derrière IIS Express. IIS Express
ignore les requêtes non locales par défaut. Exécutez dotnet run à partir d’une invite
de commandes ou choisissez le profil du nom d’application dans la liste déroulante
Cible de débogage de la barre d’outils Visual Studio.

Ajoutez une classe de modèle pour représenter des éléments de tâche à effectuer.
Marquez les champs obligatoires avec l’attribut [Required] :

C#

using System.ComponentModel.DataAnnotations;

namespace TodoAPI.Models
{
public class TodoItem
{
[Required]
public string ID { get; set; }

[Required]
public string Name { get; set; }

[Required]
public string Notes { get; set; }

public bool Done { get; set; }


}
}

Les méthodes d’API requièrent un moyen d’utiliser des données. Utilisez la même
interface ITodoRepository que celle utilisée par l’exemple Xamarin d’origine :

C#

using System.Collections.Generic;
using TodoAPI.Models;

namespace TodoAPI.Interfaces
{
public interface ITodoRepository
{
bool DoesItemExist(string id);
IEnumerable<TodoItem> All { get; }
TodoItem Find(string id);
void Insert(TodoItem item);
void Update(TodoItem item);
void Delete(string id);
}
}

Pour cet exemple, l’implémentation utilise simplement une collection privée d’éléments :

C#

using System.Collections.Generic;
using System.Linq;
using TodoAPI.Interfaces;
using TodoAPI.Models;

namespace TodoAPI.Services
{
public class TodoRepository : ITodoRepository
{
private List<TodoItem> _todoList;

public TodoRepository()
{
InitializeData();
}

public IEnumerable<TodoItem> All


{
get { return _todoList; }
}

public bool DoesItemExist(string id)


{
return _todoList.Any(item => item.ID == id);
}

public TodoItem Find(string id)


{
return _todoList.FirstOrDefault(item => item.ID == id);
}

public void Insert(TodoItem item)


{
_todoList.Add(item);
}

public void Update(TodoItem item)


{
var todoItem = this.Find(item.ID);
var index = _todoList.IndexOf(todoItem);
_todoList.RemoveAt(index);
_todoList.Insert(index, item);
}

public void Delete(string id)


{
_todoList.Remove(this.Find(id));
}

private void InitializeData()


{
_todoList = new List<TodoItem>();

var todoItem1 = new TodoItem


{
ID = "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
Name = "Learn app development",
Notes = "Take Microsoft Learn Courses",
Done = true
};

var todoItem2 = new TodoItem


{
ID = "b94afb54-a1cb-4313-8af3-b7511551b33b",
Name = "Develop apps",
Notes = "Use Visual Studio and Visual Studio for Mac",
Done = false
};

var todoItem3 = new TodoItem


{
ID = "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
Name = "Publish apps",
Notes = "All app stores",
Done = false,
};

_todoList.Add(todoItem1);
_todoList.Add(todoItem2);
_todoList.Add(todoItem3);
}
}
}

Configurez l’implémentation dans Startup.cs :

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddSingleton<ITodoRepository, TodoRepository>();
services.AddControllers();
}

Création du contrôleur
Ajoutez un nouveau contrôleur au projet, TodoItemsController . Il doit hériter de
ControllerBase. Ajoutez un attribut Route pour indiquer que le contrôleur gère les
demandes effectuées via des chemins commençant par api/todoitems . Le jeton
[controller] de la route est remplacé par le nom du contrôleur (en omettant le suffixe

Controller ) et est particulièrement pratique pour les routes globales. Découvrez plus

d’informations sur le routage.

Le contrôleur nécessite un ITodoRepository pour fonctionner ; demandez une instance


de ce type via le constructeur du contrôleur. À l’exécution, cette instance est fournie via
la prise en charge par l’infrastructure de l’injection de dépendances.

C#

[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ControllerBase
{
private readonly ITodoRepository _todoRepository;

public TodoItemsController(ITodoRepository todoRepository)


{
_todoRepository = todoRepository;
}

Cette API prend en charge quatre verbes HTTP différents pour effectuer des opérations
CRUD (création, lecture, mise à jour, suppression) sur la source de données. La plus
simple d’entre elles est l’opération de lecture, qui correspond à une requête HTTP GET.

Lecture d’éléments
Demander une liste d’éléments se fait via une requête GET à la méthode List . L’attribut
[HttpGet] sur la méthode List indique que cette action doit gérer seulement les

requêtes GET. La route pour cette action est la route spécifiée sur le contrôleur. Le nom
de l’action ne doit pas nécessairement constituer une partie de la route. Il vous suffit de
faire en sorte que chaque action ait une route unique et non ambiguë. Des attributs de
routage peuvent être appliqués aux niveaux du contrôleur et des méthodes pour créer
des routes spécifiques.

C#

[HttpGet]
public IActionResult List()
{
return Ok(_todoRepository.All);
}

La méthode List retourne un code de réponse 200 OK et tous les éléments Todo,
sérialisés au format JSON.

Vous pouvez tester votre nouvelle méthode d’API via différents outils, comme
Postman , qui est montré ici :

Création d’éléments
Par convention, la création d’éléments de données est mappée au verbe HTTP POST. Un
attribut [HttpPost] est appliqué à la méthode Create , laquelle accepte une instance
TodoItem . Comme l’argument item est passé dans le corps de la requête POST, ce

paramètre spécifie l’attribut [FromBody] .

À l’intérieur de la méthode, la validité et l’existence préalable de l’élément dans le


magasin de données sont vérifiées et, si aucun problème ne se produit, il est ajouté via
le référentiel. La vérification ModelState.IsValid effectue la validation du modèle et doit
être effectuée dans chaque méthode d’API qui accepte une entrée utilisateur.

C#

[HttpPost]
public IActionResult Create([FromBody]TodoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return
BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
bool itemExists = _todoRepository.DoesItemExist(item.ID);
if (itemExists)
{
return StatusCode(StatusCodes.Status409Conflict,
ErrorCode.TodoItemIDInUse.ToString());
}
_todoRepository.Insert(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
}
return Ok(item);
}

L’exemple utilise une enum contenant des codes d’erreur qui sont passés au client
mobile :

C#

public enum ErrorCode


{
TodoItemNameAndNotesRequired,
TodoItemIDInUse,
RecordNotFound,
CouldNotCreateItem,
CouldNotUpdateItem,
CouldNotDeleteItem
}

Testez en ajoutant de nouveaux éléments avec Postman, en choisissant le verbe POST


qui fournit le nouvel objet au format JSON dans le corps de la requête. Vous devez
également ajouter un en-tête de requête spécifiant un Content-Type de
application/json .
La méthode retourne l’élément qui vient d’être créé dans la réponse.

Mise à jour d’éléments


La modification des enregistrements est effectuée via des requêtes HTTP PUT. Outre
cette modification, la méthode Edit est presque identique à Create . Notez que si
l’enregistrement n’est pas trouvé, l’action Edit retourne une réponse NotFound (404).

C#

[HttpPut]
public IActionResult Edit([FromBody] TodoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return
BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
var existingItem = _todoRepository.Find(item.ID);
if (existingItem == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_todoRepository.Update(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
}
return NoContent();
}

Pour tester avec Postman, changez le verbe en PUT. Spécifiez les données de l’objet mis
à jour dans le corps de la requête.

Cette méthode retourne une réponse NoContent (204) en cas de réussite, pour des
raisons de cohérence avec l’API préexistante.
Suppression d’éléments
La suppression d’enregistrements est effectuée via des requêtes DELETE adressées au
service, en passant l’ID de l’élément à supprimer. Comme pour les mises à jour, les
requêtes pour des éléments qui n’existent pas reçoivent des réponses NotFound . Sinon,
une requête qui réussit obtient une réponse NoContent (204).

C#

[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
try
{
var item = _todoRepository.Find(id);
if (item == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_todoRepository.Delete(id);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
}
return NoContent();
}

Notez que quand vous testez les fonctionnalités de suppression, rien n’est obligatoire
dans le corps de la requête.
Empêcher la sur-publication
Actuellement, l’exemple d’application expose l’ensemble de l’objet TodoItem . Les
applications de production limitent généralement les données entrées et retournées à
l’aide d’un sous-ensemble du modèle. Il y a plusieurs raisons à cela, et la sécurité en est
une majeure. Le sous-ensemble d’un modèle est généralement appelé objet de transfert
de données (DTO), modèle d’entrée ou modèle de vue. DTO est utilisé dans cet article.

Un DTO peut être utilisé pour :

Empêcher la sur-publication.
Masquer les propriétés que les clients ne sont pas censés voir.
Omettre certaines propriétés afin de réduire la taille de la charge utile.
Aplatir les graphes d’objets qui contiennent des objets imbriqués. Les graphiques
d’objets aplatis peuvent être plus pratiques pour les clients.

Pour illustrer l’approche DTO, consultez Empêcher la sur-publication


Conventions des API web courantes
Quand vous développez des services backend pour votre application, vous souhaitez
obtenir un ensemble cohérent de conventions ou de stratégies pour gérer les
problèmes transversaux. Par exemple, dans le service montré ci-dessus, les requêtes
pour des enregistrements spécifiques qui n’ont pas été trouvés ont reçu une réponse
NotFound et non pas une réponse BadRequest . De même, les commandes envoyées à ce
service qui ont passé des types liés au modèle ont toujours vérifié ModelState.IsValid
et retourné un BadRequest pour les types de modèle non valide.

Une fois que vous avez identifié une stratégie commune pour vos API, vous pouvez en
général l’encapsuler dans un filtre. Découvrez plus d’informations sur la façon
d’encapsuler des stratégies d’API courantes dans les applications ASP.NET Core MVC.

Ressources supplémentaires
Xamarin.Forms : authentification de service web
Xamarin.Forms : utiliser un RESTservice web complet
Consommer des services web REST dans des applications Xamarin
Créer une API web avec ASP.NET Core

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Publier une API web ASP.NET Core sur
API Management Azure avec Visual
Studio
Article • 30/11/2023

Par Matt Soucoup

Dans ce tutoriel, vous allez apprendre à créer un projet d’API web ASP.NET Core à l’aide
de Visual Studio, à vérifier qu’il dispose de la prise en charge d’OpenAPI, puis à publier
l’API web sur Azure App Service et Gestion des API Azure.

Configurer
Pour suivre le tutoriel, vous avez besoin d’un compte Azure.

Ouvrez un compte Azure gratuit si vous n’en avez pas.

Créer une API web ASP.NET Core


Visual Studio vous permet de créer facilement un projet d’API web ASP.NET Core à partir
d’un modèle. Suivez ces instructions pour créer un projet d’API web ASP.NET Core :

Dans le menu Fichier, sélectionnez Nouveau>Projet.


Entrez API web dans le champ de recherche.
Sélectionnez le modèle API web ASP.NET Core, puis Suivant.
Dans la boîte de dialogue Configurer votre nouveau projet, nommez le projet
WeatherAPI, puis sélectionnez Suivant.
Dans la boîte de dialogue Informations supplémentaires :
Vérifiez que le framework est .NET 6.0 (prise en charge à long terme).
Vérifiez que la case à cocher Utiliser des contrôleurs (décocher pour utiliser les
API minimales) est cochée.
Vérifiez que la case Activer la prise en charge d’OpenAPI est cochée.
Sélectionnez Create (Créer).

Explorer le code
Les définitions Swagger permettent à Gestion des API Azure de lire les définitions d’API
de l’application. En cochant la case Activer la prise en charge d’OpenAPI lors de la
création de l’application, Visual Studio ajoute automatiquement le code pour créer les
définitions Swagger. Ouvrez le fichier Program.cs , qui affiche le code suivant :

C#

...

builder.Services.AddSwaggerGen();

...

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

...

Vérifier que les définitions Swagger sont toujours


générées
Gestion des API Azure a besoin que les définitions Swagger soient toujours présentes,
quel que soit l’environnement de l’application. Pour vous assurer qu’elles sont toujours
générées, déplacez app.UseSwagger(); en dehors du bloc if
(app.Environment.IsDevelopment()) .

Le code mis à jour :

C#

...

app.UseSwagger();

if (app.Environment.IsDevelopment())
{
app.UseSwaggerUI();
}

...
Modifier le routage de l’API
Modifiez la structure d’URL nécessaire pour accéder à l’action Get du
WeatherForecastController . Suivez les étapes ci-dessous :

1. Ouvrez le fichier WeatherForecastController.cs .

2. Remplacez l’attribut de niveau classe [Route("[controller]")] par [Route("/")] .


Définition de classe mise à jour :

C#

[ApiController]
[Route("/")]
public class WeatherForecastController : ControllerBase

Publier l’API web sur Azure App Service


Effectuez les étapes suivantes pour publier l’API web ASP.NET Core sur Gestion des API
Azure :

1. Publiez l’API web sur Azure App Service.


2. Publiez l’application d’API web ASP.NET Core sur l’instance de service Gestion des
API Azure.

Publier l’API web sur Azure App Service


Effectuez les étapes suivantes pour publier l’API web ASP.NET Core sur Gestion des API
Azure :

1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet, puis
sélectionnez Publier.

2. Dans la boîte de dialogue Publier, sélectionnez Azure, puis le bouton Suivant.

3. Sélectionnez Azure App Service (Windows) et sélectionnez le bouton Suivant.

4. Sélectionnez Créer une instance Azure App Service.

La boîte de dialogue Créer App Service apparaît. Les champs d’entrée Nom de
l’application, Groupe de ressources et Plan App Service sont renseignés. Vous
pouvez conserver ces noms ou les changer.

5. Cliquez sur le bouton Créer.


6. Une fois le service d’application créé, sélectionnez le bouton Suivant.

7. Sélectionnez Créer un service Gestion des API.

La boîte de dialogue Créer un service de gestion des API s’affiche. Vous pouvez
laisser les champs d’entrée Nom de l’API, Nom de l’abonnement et Groupe de
ressources tels qu’ils sont. Sélectionnez le bouton Nouveau en regard de l’entrée
Service Gestion des API et entrez les champs requis de cette boîte de dialogue.

Sélectionnez le bouton OK pour créer le service Gestion des API.

8. Sélectionnez le bouton Créer pour poursuivre la création du service Gestion des


API. Cette étape peut prendre plusieurs minutes.

9. Une fois cette opération terminée, sélectionnez le bouton Terminer.

10. La boîte de dialogue se ferme et un écran de résumé s’affiche avec des


informations sur la publication. Cliquez sur le bouton Publier.

L’API web publie à la fois sur Azure App Service et Gestion des API Azure. Une
nouvelle fenêtre de navigateur s’affiche et affiche l’API en cours d’exécution dans
Azure App Service. Vous pouvez fermer cette fenêtre.

11. Ouvrez le Portail Azure dans un navigateur web et accédez à l’instance Gestion des
API que vous avez créée.

12. Sélectionnez l’option API dans le menu de gauche.

13. Sélectionnez l’API que vous avez créée dans les étapes précédentes. Elle est
maintenant remplie et vous pouvez l’explorer.

Configurer le nom de l’API publiée


Notez que l’API est nommée WeatherAPI ; toutefois, nous aimerions l’appeler Weather
Forecasts. Effectuez les étapes suivantes pour mettre à jour le nom :

1. Ajoutez ce qui suit à Program.cs immédiatement après servies.AddSwaggerGen();

C#

builder.Services.ConfigureSwaggerGen(setup =>
{
setup.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "Weather Forecasts",
Version = "v1"
});
});

2. Republiez l’API web ASP.NET Core et ouvrez l’instance Gestion des API Azure dans
le Portail Azure.

3. Actualisez la page dans votre navigateur. Vous verrez que le nom de l’API est
maintenant correct.

Vérifier que l’API web fonctionne


Vous pouvez tester l’API web ASP.NET Core déployée dans Gestion des API Azure à
partir du Portail Azure en procédant comme suit :

1. Ouvrez l’onglet Test.


2. Sélectionnez / ou l’opération GET.
3. Sélectionnez Envoyer.

Nettoyage
Après avoir testé l’application, accédez au portail Azure , puis supprimez l’application.

1. Sélectionnez Groupes de ressources, puis sélectionnez le groupe de ressources


que vous avez créé.

2. Dans la page Groupes de ressources, sélectionnez Supprimer.

3. Entrez le nom du groupe de ressources, puis sélectionnez Supprimer. Votre


application et toutes les autres ressources créées dans ce didacticiel sont
désormais supprimées d’Azure.

Ressources supplémentaires
Gestion des API Azure
Azure App Service

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et  Ouvrir un problème de
examiner les problèmes et les documentation
demandes de tirage. Pour plus
d’informations, consultez notre  Indiquer des commentaires sur
guide du contributeur.
le produit
Tutoriel : Créer une API minimale avec
ASP.NET Core
Article • 30/11/2023

Par Rick Anderson et Ryan Nowak

Les API minimales sont conçues pour créer des API HTTP ayant des dépendances
minimales. Elles sont idéales pour les microservices et les applications qui ne nécessitent
qu’un minimum de fichiers, de fonctionnalités et de dépendances dans ASP.NET Core.

Ce tutoriel décrit les principes fondamentaux liés à la génération d’une API minimale
avec ASP.NET Core. Une autre approche de la création d’API dans ASP.NET Core consiste
à utiliser des contrôleurs. Pour obtenir de l’aide sur le choix entre les API minimales et
les API basées sur un contrôleur, consultez Vue d’ensemble des API. Pour obtenir un
tutoriel sur la création d’un projet d’API basée sur des contrôleurs qui contiennent
d’autres fonctionnalités, consultez Créer une API web.

Vue d’ensemble
Ce didacticiel crée l’API suivante :

API Description Corps de la Corps de


requête réponse

GET /todoitems Obtenir toutes les tâches Aucune Tableau de


tâches

GET Obtenir les éléments de tâche None Tableau de


/todoitems/complete terminés tâches

GET /todoitems/{id} Obtenir un élément par ID Aucune Tâche

POST /todoitems Ajouter un nouvel élément Tâche Tâche

PUT /todoitems/{id} Mettre à jour un élément Tâche Aucune


existant

DELETE /todoitems/{id} Supprimer un élément Aucune None

Prerequisites
Visual Studio

Préversion de Visual Studio 2022 avec la charge de travail ASP.NET et


développement web.

Créez un projet d’API


Visual Studio

Démarrez la préversion Visual Studio 2022 et sélectionnez Créer un projet.

Dans la boîte de dialogue Créer un projet :


Entrez Empty dans la zone de recherche Rechercher des modèles.
Sélectionnez le modèle ASP.NET Core vide, puis Suivant.
Nommez le projet TodoApi, puis sélectionnez Suivant.

Dans la boîte de dialogue Informations supplémentaires :


Sélectionnez .NET 8.0 (support à long terme)
Décochez la case N’utilisez pas d’instructions de niveau supérieur
Sélectionnez Créer

Examiner le code
Le fichier Program.cs contient le code suivant :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Le code précédent :

Crée un WebApplicationBuilder et un WebApplication avec des valeurs


préconfigurées par défaut.
Crée un point de terminaison HTTP GET / qui retourne Hello World! :

Exécuter l’application

Visual Studio

Appuyez sur Ctrl+F5 pour exécuter sans le débogueur.

Visual Studio affiche la boîte de dialogue suivante :

Sélectionnez Oui si vous faites confiance au certificat SSL d’IIS Express.

La boîte de dialogue suivante s’affiche :


Sélectionnez Oui si vous acceptez d’approuver le certificat de développement.

Pour plus d’informations sur l’approbation du navigateur Firefox, consultez Erreur


de certificat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.

Visual Studio lance le Kestrel serveur web et ouvre une fenêtre de navigateur.

Hello World! s’affiche dans le navigateur. Le fichier Program.cs contient une application

minimale mais complète.

Ajouter des packages NuGet


Les packages NuGet doivent être ajoutés pour prendre en charge la base de données et
les diagnostics utilisés dans ce tutoriel.

Visual Studio

Dans le menu Outils, sélectionnez Gestionnaire de package NuGet > Gérer


les packages NuGet pour la solution.
Sélectionnez l’onglet Parcourir.
Sélectionnez Inclure la préversion.
Entrez Microsoft.EntityFrameworkCore.InMemory dans la zone de recherche,
puis sélectionnez Microsoft.EntityFrameworkCore.InMemory .
Cochez la case Projet dans le volet droit, puis sélectionnez Installer.
Suivez les instructions précédentes pour ajouter le package
Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore .

Classes de contexte de modèle et de base de


données
Dans le dossier du projet, créez un fichier nommé Todo.cs avec le code suivant :

C#

public class Todo


{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

Le code précédent crée le modèle pour cette application. Un modèle est un ensemble de
classes qui représentent les données gérées par l’application.

Créez un fichier nommé TodoDb.cs avec le code suivant :

C#

using Microsoft.EntityFrameworkCore;

class TodoDb : DbContext


{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }

public DbSet<Todo> Todos => Set<Todo>();


}

Le précédent code définit le contexte de base de données, qui est la classe principale qui
coordonne les fonctionnalités d’Entity Framework pour un modèle de données. Cette
classe dérive de la classe Microsoft.EntityFrameworkCore.DbContext.

Ajouter le code de l’API


Remplacez le contenu du fichier Program.cs par le code suivant :

C#
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

app.MapGet("/todoitems", async (TodoDb db) =>


await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>


await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>


await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


});

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}

return Results.NotFound();
});
app.Run();

Le code suivant mis en surbrillance ajoute le contexte de base de données au conteneur


d’injection de dépendances et permet d’afficher les exceptions liées à la base de
données :

C#

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

Le conteneur d’injection de dépendances fournit l’accès au contexte de base de


données et à d’autres services.

Visual Studio

Ce didacticiel utilise l’Explorateur des points de terminaison et les fichiers .http pour
tester l’API.

Testez les données de publication


Le code suivant dans Program.cs crée un point de terminaison HTTP POST /todoitems
qui ajoute des données à la base de données en mémoire :

C#

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


});

Exécutez l'application. Le navigateur affiche une erreur 404, car il n’y a plus de point de
terminaison / .

Utilisez le point de terminaison POST pour ajouter des données à l’application.


Visual Studio

Sélectionnez Afficher, Autres fenêtres et Explorateur de points de


terminaison.

Cliquez avec le bouton droit sur le point de terminaison POST, puis


sélectionnez Générer une requête.

Un nouveau fichier est créé dans le dossier de projet nommé TodoApi.http ,


avec un contenu similaire à l’exemple suivant :

@TodoApi_HostAddress = https://localhost:7031

Post {{TodoApi_HostAddress}}/todoitems

###

La première ligne crée une variable qui sera utilisée pour tous les points de
terminaison.
La ligne suivante définit une requête POST.
La triple hashtag ( ### ) ligne est un délimiteur de requête : ce qui arrive
après qu’il sera pour une autre requête.

La requête POST a besoin d’en-têtes et d’un corps. Pour définir ces parties de
la requête, ajoutez les lignes suivantes immédiatement après la ligne de
requête POST :

Content-Type: application/json

{
"name":"walk dog",
"isComplete":true
}

Le code précédent ajoute un en-tête Content-Type et un corps de la demande


ON JS. Le fichier TodoApi.http doit maintenant ressembler à l’exemple suivant,
mais avec votre numéro de port :

@TodoApi_HostAddress = https://localhost:7057

Post {{TodoApi_HostAddress}}/todoitems
Content-Type: application/json

{
"name":"walk dog",
"isComplete":true
}

###

Exécutez l'application.

Sélectionnez le lien Envoyer une requête au-dessus de la ligne de requête


POST .
La requête POST est envoyée à l’application et la réponse s’affiche dans le
volet Réponse.

Examinez les points de terminaison GET


L’échantillon d’application implémente plusieurs points de terminaison GET en appelant
MapGet :

API Description Corps de la Corps de


requête réponse

GET /todoitems Obtenir toutes les tâches Aucune Tableau de


tâches

GET Obtenez tous les éléments de None Tableau de


/todoitems/complete tâche terminés tâches

GET /todoitems/{id} Obtenir un élément par ID Aucune Tâche

C#

app.MapGet("/todoitems", async (TodoDb db) =>


await db.Todos.ToListAsync());

app.MapGet("/todoitems/complete", async (TodoDb db) =>


await db.Todos.Where(t => t.IsComplete).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>


await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());

Testez les points de terminaison GET


Visual Studio

Testez l’application en appelant les GET points d’extrémité à partir d’un navigateur
ou en utilisant l’Explorateur des points de terminaison. Les étapes suivantes
concernent l’Explorateur des points de terminaison.

Dans l’Explorateur de points de terminaison, cliquez avec le bouton droit de


la souris sur le premier GET un point de terminaison et sélectionnez Générer
une requête.

Le contenu suivant est ajouté au fichier TodoApi.http :

Get {{TodoApi_HostAddress}}/todoitems

###

Sélectionnez le lien Envoyer une requête au-dessus de la nouvelle ligne de


requête GET .

La requête GET est envoyée à l’application et la réponse s’affiche dans le volet


Réponse.

Le corps de la réponse est similaire à JSON suivant :

JSON

[
{
"id": 1,
"name": "walk dog",
"isComplete": false
}
]

Dans l’Explorateur de points de terminaison, cliquez avec le bouton droit de


la souris sur le troisième GET un point de terminaison et sélectionnez Générer
une requête. Le contenu suivant est ajouté au fichier TodoApi.http :
GET {{TodoApi_HostAddress}}/todoitems/{id}

###

Remplacez {id} par 1 .

Sélectionnez le lien Envoyer une requête au-dessus de la nouvelle ligne de


requête GET.

La requête GET est envoyée à l’application et la réponse s’affiche dans le volet


Réponse.

Le corps de la réponse est similaire à JSON suivant :

JSON

{
"id": 1,
"name": "walk dog",
"isComplete": false
}

Cette application utilise une base de données en mémoire. Si l’application est


redémarrée, la requête GET ne retourne aucune donnée. Si aucune donnée n’est
retournée, publiez (POST) les données dans l’application et réessayez la requête GET.

Valeurs de retour
ASP.NET Core sérialise automatiquement l’objet en JSON et écrit le JSON dans le
corps du message de réponse. Le code de réponse pour ce type de retour est 200 OK ,
en supposant qu’il n’existe pas d’exception non gérée. Les exceptions non gérées sont
converties en erreurs 5xx.

Les types de retour peuvent représenter une large plage de codes d’état HTTP. Par
exemple, GET /todoitems/{id} peut retourner deux valeurs d’état différentes :

Si aucun élément ne correspond à l’ID demandé, la méthode retourne un code


d’erreur d’état 404 NotFound.
Sinon, la méthode retourne 200 avec un corps de réponse JSON. Le retour de item
entraîne une réponse HTTP 200.
Examinez le point de terminaison PUT
L’échantillon d’application implémente un point de terminaison PUT unique à l’aide de
MapPut :

C#

app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return Results.NoContent();
});

Cette méthode est similaire à la méthode MapPost , sauf qu’elle utilise HTTP PUT. Une
réponse réussie retourne 204 (Aucun contenu). D’après la spécification HTTP, une
requête PUT nécessite que le client envoie toute l’entité mise à jour, et pas seulement
les changements. Pour prendre en charge les mises à jour partielles, utilisez HTTP
PATCH.

Testez le point de terminaison PUT


Cet exemple utilise une base de données en mémoire qui doit être initialisée à chaque
démarrage de l’application. La base de données doit contenir un élément avant que
vous ne passiez un appel PUT. Appelez GET pour vérifier qu’un élément existe dans la
base de données avant d’effectuer un appel PUT.

Mettez à jour la tâche avec l’Id = 1 et nommez-la "feed fish" .

Visual Studio

Dans l’Explorateur de points de terminaison, cliquez avec le bouton droit de


la souris sur le point de terminaison PUT et sélectionnez Générer une requête.

Le contenu suivant est ajouté au fichier TodoApi.http :


Put {{TodoApi_HostAddress}}/todoitems/{id}

###

Dans la ligne de requête PUT, remplacez {id} par 1 .

Ajoutez les lignes suivantes immédiatement après la ligne de requête PUT :

Content-Type: application/json

{
"id": 1,
"name": "feed fish",
"isComplete": false
}

Le code précédent ajoute un en-tête Content-Type et un corps de la demande


ON JS.

Sélectionnez le lien Envoyer une requête au-dessus de la nouvelle ligne de


requête GET.

La requête PUT est envoyée à l’application et la réponse s’affiche dans le volet


Réponse. Le corps de la réponse est vide et le code d’état est 204.

Examiner et tester le point de terminaison


DELETE
L’échantillon d’application implémente un seul point de terminaison DELETE à l’aide de
MapDelete :

C#

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}
return Results.NotFound();
});

Visual Studio

Dans l’Explorateur de points de terminaison, cliquez avec le bouton droit de


la souris sur le point de terminaison DELETE et sélectionnez Générer une
requête.

Une requête DELETE est ajoutée à TodoApi.http .

Remplacez {id} dans la ligne de requête DELETE par 1 . La requête DELETE


doit ressembler à l’exemple suivant :

DELETE {{TodoApi_HostAddress}}/todoitems/1

###

Sélectionnez le lien Envoyer une requête pour la demande DELETE.

La requête DELETE est envoyée à l’application et la réponse s’affiche dans le


volet Réponse. Le corps de la réponse est vide et le code d’état est 204.

Utiliser l’API MapGroup


L’échantillon de code d’application répète le préfixe d’URL todoitems chaque fois qu’il
configure un point de terminaison. Les API ont souvent des groupes de points de
terminaison avec un préfixe d’URL commun, et la méthode MapGroup est disponible
pour vous aider à organiser ces groupes. Cela réduit le code répétitif et permet de
personnaliser des groupes entiers de points de terminaison avec un seul appel à des
méthodes comme RequireAuthorization et WithMetadata.

Remplacez le contenu de Program.cs par le code suivant :

C#

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", async (TodoDb db) =>


await db.Todos.ToListAsync());

todoItems.MapGet("/complete", async (TodoDb db) =>


await db.Todos.Where(t => t.IsComplete).ToListAsync());

todoItems.MapGet("/{id}", async (int id, TodoDb db) =>


await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());

todoItems.MapPost("/", async (Todo todo, TodoDb db) =>


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return Results.Created($"/todoitems/{todo.Id}", todo);


});

todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return Results.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return Results.NoContent();
});

todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.NoContent();
}

return Results.NotFound();
});

app.Run();
Le code précédent dispose des modifications suivantes :

Ajoute var todoItems = app.MapGroup("/todoitems"); pour configurer le groupe à


l’aide du préfixe d’URL /todoitems .
Remplace toutes les méthodes app.Map<HttpVerb> par todoItems.Map<HttpVerb> .
Supprime le préfixe d’URL /todoitems des appels de méthode Map<HttpVerb> .

Testez les points de terminaison pour vérifier qu’ils fonctionnent de la même façon.

Utilisez l’API TypedResults


Retourner TypedResults plutôt que Results présente plusieurs avantages, notamment la
testabilité et le retour automatique des métadonnées de type de réponse pour OpenAPI
afin de décrire le point de terminaison. Pour plus d'informations, consultez TypedResults
vs Results.

Les méthodes Map<HttpVerb> peuvent appeler des méthodes de gestionnaire de routage


au lieu d’utiliser des expressions lambda. Pour voir un exemple, mettez à jour Program.cs
avec le code suivant :

C#

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.Where(t =>
t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)


{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return TypedResults.Created($"/todoitems/{todo.Id}", todo);


}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return TypedResults.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}

return TypedResults.NotFound();
}

Le code Map<HttpVerb> appelle maintenant des méthodes au lieu des lambda :

C#

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

Ces méthodes retournent des objets qui implémentent IResult et sont définis par
TypedResults :

C#

static async Task<IResult> GetAllTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.Where(t =>
t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)


{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)


{
db.Todos.Add(todo);
await db.SaveChangesAsync();

return TypedResults.Created($"/todoitems/{todo.Id}", todo);


}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)


{
var todo = await db.Todos.FindAsync(id);

if (todo is null) return TypedResults.NotFound();

todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;

await db.SaveChangesAsync();

return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}

return TypedResults.NotFound();
}

Les tests unitaires peuvent appeler ces méthodes et vérifier qu’elles retournent le type
correct. Par exemple, si la méthode est GetAllTodos :

C#

static async Task<IResult> GetAllTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

Le code de test unitaire peut vérifier qu’un objet de type Ok<Todo[]> est retourné à
partir de la méthode de gestionnaire. Par exemple :

C#

public async Task GetAllTodos_ReturnsOkOfTodosResult()


{
// Arrange
var db = CreateDbContext();

// Act
var result = await TodosApi.GetAllTodos(db);

// Assert: Check for the correct returned type


Assert.IsType<Ok<Todo[]>>(result);
}

Empêcher la sur-publication
Actuellement, l’exemple d’application expose l’ensemble de l’objet Todo . Les
applications de production limitent généralement les données entrées et retournées à
l’aide d’un sous-ensemble du modèle. Il y a plusieurs raisons à cela, et la sécurité en est
une majeure. Le sous-ensemble d’un modèle est généralement appelé objet de transfert
de données (DTO), modèle d’entrée ou modèle de vue. DTO est utilisé dans cet article.
Un DTO peut être utilisé pour :

Empêcher la sur-publication.
Masquer les propriétés que les clients ne sont pas censés voir.
Omettre certaines propriétés afin de réduire la taille de la charge utile.
Aplatir les graphes d’objets qui contiennent des objets imbriqués. Les graphiques
d’objets aplatis peuvent être plus pratiques pour les clients.

Pour illustrer l’approche DTO, mettez à jour la classe Todo pour inclure un champ secret :

C#

public class Todo


{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}

Le champ secret doit être masqué dans cette application, mais une application
administrative peut choisir de l’exposer.

Vérifiez que vous pouvez publier et obtenir le champ secret.

Créez un fichier nommé TodoItemDTO.cs avec le code suivant :

C#

public class TodoItemDTO


{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }

public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name,
todoItem.IsComplete);
}

Mettez à jour le code dans Program.cs pour utiliser ce modèle DTO :

C#

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();

RouteGroupBuilder todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();

static async Task<IResult> GetAllTodos(TodoDb db)


{
return TypedResults.Ok(await db.Todos.Select(x => new
TodoItemDTO(x)).ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db) {


return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).Select(x
=> new TodoItemDTO(x)).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)


{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(new TodoItemDTO(todo))
: TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)


{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};

db.Todos.Add(todoItem);
await db.SaveChangesAsync();

todoItemDTO = new TodoItemDTO(todoItem);

return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);


}

static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO,


TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();

todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;

await db.SaveChangesAsync();

return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)


{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.NoContent();
}

return TypedResults.NotFound();
}

Vérifiez que vous pouvez publier et obtenir tous les champs à l’exception du champ
secret.

Résolution des problèmes avec l’exemple


terminé
Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code
au projet terminé. Afficher ou télécharger le projet terminé (comment télécharger).

Étapes suivantes
Configurer les JSoptions de sérialisation ON.
Gérer les erreurs et les exceptions : la page d’exceptions du développeur est
activée par défaut dans l’environnement de développement pour les applications
API minimales. Pour plus d’informations sur la gestion des erreurs et des
exceptions, consultez Gérer les erreurs dans les API ASP.NET Core.
Pour obtenir un échantillon de test d’une application API minimale, consultez cet
échantillon GitHub .
Prise en charge d’OpenAPI dans les API minimales.
Démarrage rapide : Publier sur Azure.
Organisation d’API minimales ASP.NET Core
En savoir plus
Consultez Informations de référence rapides sur les API minimales.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : Bien démarrer avec ASP.NET
Core SignalR
Article • 30/11/2023

Ce tutoriel explique les principes de base de la création d’une application en temps réel
à l’aide de SignalR. Vous allez apprendre à effectuer les actions suivantes :

" Créez un projet web.


" Ajoutez la bibliothèque cliente SignalR.
" Créez un hub SignalR.
" Configurez le projet pour utiliser SignalR.
" Ajouter du code qui envoie des messages de n’importe quel client vers tous les
clients connectés.

À la fin, vous disposerez d’une application de conversation opérationnelle :

Prérequis
Visual Studio

Préversion de Visual Studio 2022 avec la charge de travail ASP.NET et


développement web.
Créer un projet d’application web
Visual Studio

Démarrez Visual Studio 2022 et sélectionnez Créer un projet.

Dans la boîte de dialogue Créer un projet, sélectionnez Application web ASP.NET


Core (Razor pages), puis Suivant.
Dans la boîte de dialogue Configurer votre nouveau projet, entrez SignalRChat
pour Nom du projet. Il est important de nommer le projet SignalRChat , y compris
la mise en majuscule, afin que les espaces de noms correspondent au code du
tutoriel.

Cliquez sur Suivant.

Dans la fenêtre de dialogue Informations supplémentaires, sélectionnez .NET 8.0


(prise en charge à long terme), puis sélectionnez Créer.
Ajouter la bibliothèque cliente SignalR
La bibliothèque de serveurs SignalR est incluse dans l’infrastructure partagée ASP.NET
Core. La bibliothèque cliente JavaScript n’est pas incluse automatiquement dans le
projet. Pour ce tutoriel, utiliser le gestionnaire de bibliothèque (LibMan) pour obtenir la
bibliothèque de client à partir de unpkg . unpkg est un réseau de distribution de
contenu rapide et global pour tout ce qui se trouve sur npm .

Visual Studio

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet, puis
sélectionnez Ajouter>Bibliothèque côté client.

Dans la boîte de dialogue Ajouter une bibliothèque côté client :

Sélectionnez unpkg pour fournisseur


Entrez @microsoft/signalr@latest pour bibliothèque.
Sélectionnez Choisir des fichiers spécifiques, développez le dossier
dist/browser, puis sélectionnez signalr.js et signalr.min.js .
Définissez Emplacement cible sur wwwroot/js/signalr/ .
Sélectionnez Installer.
LibMan crée un dossier wwwroot/js/signalr et copie les fichiers sélectionnés.

Créez un hub SignalR


Un hub est une classe servant de pipeline global qui gère les communications client-
serveur.

Dans le dossier du projet conversation SignalR, créez un dossier Hubs .

Dans le dossier Hubs , créez la classe ChatHub avec le code suivant :

C#

using Microsoft.AspNetCore.SignalR;

namespace SignalRChat.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}

La classe ChatHub hérite de la classe SignalRHub. La classe Hub gère les connexions, les
groupes et la messagerie.

La méthode SendMessage peut être appelée par un client connecté afin d’envoyer un
message à tous les clients. Le code client JavaScript qui appelle la méthode est indiqué
plus loin dans le didacticiel. Le code SignalR est asynchrone afin de fournir une
scalabilité maximale.

Configurer SignalR
Le serveur SignalR doit être configuré pour transmettre des requêtes SignalR à SignalR.
Ajoutez le code mis en surbrillance suivant au fichier Program.cs .

C#

using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddSignalR();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");

app.Run();
Le code mis en surbrillance précédent ajoute SignalR aux systèmes d’injection de
dépendances et de routage ASP.NET Core.

Ajouter du code client SignalR


Remplacez le contenu dans Pages/Index.cshtml par le code suivant :

CSHTML

@page
<div class="container">
<div class="row p-1">
<div class="col-1">User</div>
<div class="col-5"><input type="text" id="userInput" /></div>
</div>
<div class="row p-1">
<div class="col-1">Message</div>
<div class="col-5"><input type="text" class="w-100"
id="messageInput" /></div>
</div>
<div class="row p-1">
<div class="col-6 text-end">
<input type="button" id="sendButton" value="Send Message" />
</div>
</div>
<div class="row p-1">
<div class="col-6">
<hr />
</div>
</div>
<div class="row p-1">
<div class="col-6">
<ul id="messagesList"></ul>
</div>
</div>
</div>
<script src="~/js/signalr/dist/browser/signalr.js"></script>
<script src="~/js/chat.js"></script>

Le balisage précédent :

Crée des zones de texte et un bouton Envoyer.


Crée une liste avec id="messagesList" pour afficher les messages reçus à partir du
hub SignalR.
Inclut des références de script à SignalR et le code de l’application chat.js est
créé à l’étape suivante.

Dans le dossier wwwroot/js , créez un fichier chat.js avec le code suivant :


JavaScript

"use strict";

var connection = new


signalR.HubConnectionBuilder().withUrl("/chatHub").build();

//Disable the send button until connection is established.


document.getElementById("sendButton").disabled = true;

connection.on("ReceiveMessage", function (user, message) {


var li = document.createElement("li");
document.getElementById("messagesList").appendChild(li);
// We can assign user-supplied strings to an element's textContent
because it
// is not interpreted as markup. If you're assigning in any other way,
you
// should be aware of possible script injection concerns.
li.textContent = `${user} says ${message}`;
});

connection.start().then(function () {
document.getElementById("sendButton").disabled = false;
}).catch(function (err) {
return console.error(err.toString());
});

document.getElementById("sendButton").addEventListener("click", function
(event) {
var user = document.getElementById("userInput").value;
var message = document.getElementById("messageInput").value;
connection.invoke("SendMessage", user, message).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
});

JavaScript précédent :

Crée et lance une connexion.


Ajoute au bouton Envoyer un gestionnaire qui envoie des messages au hub.
Ajoute à l’objet de connexion un gestionnaire qui reçoit des messages à partir du
hub et les ajoute à la liste.

Exécuter l’application
Visual Studio
Sélectionnez Ctrl + F5 pour exécuter l’application sans débogage.

Copiez l’URL à partir de la barre d’adresse, ouvrez un autre onglet ou instance du


navigateur, puis collez l’URL dans la barre d’adresse.

Choisissez un des navigateurs, entrez un nom et un message, puis sélectionnez le


bouton Envoyer le message.

Le nom et le message sont affichés instantanément dans les deux pages.

 Conseil

Si l’application ne fonctionne pas, ouvrez vos outils de développement (F12) de


navigateur et accédez à la console. Recherchez les erreurs possibles liées au code
HTML et JavaScript. Par exemple, si signalr.js a été placé dans un dossier
différent de celui dirigé, la référence à ce fichier ne fonctionnera pas, ce qui
entraîne une erreur 404 dans la console.

Si une erreur ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY se produit dans Chrome,


exécutez les commandes suivantes pour mettre à jour le certificat de
développement :

CLI .NET
dotnet dev-certs https --clean
dotnet dev-certs https --trust

Publier sur Azure


Pour plus d’informations sur le déploiement sur Azure, consultez Démarrage rapide :
Déployer une application web ASP.NET. Pour plus d’informations sur Azure SignalR
Service, consultez Qu’est-ce qu’Azure SignalR Service ?.

Étapes suivantes
Utiliser des hubs
Hubs fortement typés
Authentification et autorisation dans ASP.NET Core SignalR
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : Bien démarrer avec ASP.NET
Core SignalR à l’aide de TypeScript et
Webpack
Article • 30/11/2023

By Sébastien Sougnez

Ce tutoriel montre comment utiliser Webpack dans une application web ASP.NET
Core SignalR pour regrouper et générer un client écrit en TypeScript . Webpack permet
aux développeurs de regrouper et générer les ressources côté client d’une application
web.

Dans ce tutoriel, vous allez apprendre à :

" Créer une application ASP.NET Core SignalR


" Configurer le serveur SignalR
" Configurer un pipeline de build à l’aide de Webpack
" Configurer le client TypeScript SignalR
" Activer la communication entre le client et le serveur

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Prérequis
Node.js avec npm

Visual Studio

Préversion de Visual Studio 2022 avec la charge de travail ASP.NET et


développement web.
Créer l’application web ASP.NET Core
Visual Studio

Par défaut, Visual Studio utilise la version de npm qu’il trouve dans son répertoire
d’installation. Pour configurer Visual Studio pour rechercher npm dans la variable
d’environnement PATH :

Lancez Visual Studio. Dans la fenêtre de démarrage, sélectionnez Continuer sans


code.

1. Accédez à Outils>Options>Projets et solutions>Gestion des packages


web>Outils web externes.

2. Sélectionnez l’entrée $(PATH) dans la liste. Sélectionnez la flèche vers le haut


pour déplacer l’entrée à la deuxième position de la liste, puis sélectionnez OK :
.

Pour créer une application web ASP.NET Core :

1. Utilisez l’option de menu Fichier>Nouveau>Projet et choisissez le modèle


ASP.NET Core Vide. Cliquez sur Suivant.
2. Nommez le projet SignalRWebpack , puis sélectionnez Créer.
3. Sélectionnez .NET 8.0 (prise en charge à long terme) dans la liste déroulante
Framework. Sélectionnez Créer.

Ajoutez le package NuGet Microsoft.TypeScript.MSBuild au projet :

1. Dans Explorateur de solutions, cliquez avec le bouton droit sur le nœud du


projet, puis sélectionnez Gérer les packages NuGet. Sous l’onglet Parcourir,
recherchez Microsoft.TypeScript.MSBuild , puis cliquez sur Installer à droite
pour installer le package.

Visual Studio ajoute le package NuGet sous le nœud Dépendances dans


Explorateur de solutions, ce qui permet la compilation TypeScript dans le projet.

Configurer le serveur
Dans cette section, vous allez configurer l’application web ASP.NET Core pour envoyer
et recevoir des messages SignalR.

1. Dans Program.cs , appelez AddSignalR :


C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSignalR();

2. Là encore, dans Program.cs , appelez UseDefaultFiles et UseStaticFiles :

C#

var app = builder.Build();

app.UseDefaultFiles();
app.UseStaticFiles();

Le code précédent permet au serveur de localiser et de servir le fichier index.html .


Le fichier est servi si l’utilisateur entre son URL complète ou l’URL racine de
l’application web.

3. Créez un répertoire nommé Hubs dans la racine du projet, SignalRWebpack/ , pour


la classe hub SignalR.

4. Créez un fichier, Hubs/ChatHub.cs , avec le code suivant :

C#

using Microsoft.AspNetCore.SignalR;

namespace SignalRWebpack.Hubs;

public class ChatHub : Hub


{
public async Task NewMessage(long username, string message) =>
await Clients.All.SendAsync("messageReceived", username,
message);
}

Le code précédent diffuse les messages reçus à tous les utilisateurs connectés, une
fois que le serveur les reçoit. Vous n’avez pas besoin d’appeler une méthode
générique on pour recevoir tous les messages. Une méthode nommée après le
nom du message est suffisante.

Dans cet exemple :

Le client TypeScript envoie un message identifié comme newMessage .


La méthode C# NewMessage attend les données envoyées par le client.
Un appel est effectué pour SendAsync sur Clients.All.
Les messages reçus sont envoyés à tous les clients connectés au hub.

5. Ajoutez l’instruction using suivante en haut de Program.cs pour résoudre la


référence ChatHub :

C#

using SignalRWebpack.Hubs;

6. Dans Program.cs , mappez l’itinéraire /hub au hub ChatHub . Remplacez le code qui
affiche Hello World! par le code suivant :

C#

app.MapHub<ChatHub>("/hub");

Configurer le client
Dans cette section, vous allez créer un projet Node.js pour convertir TypeScript en
JavaScript et regrouper des ressources côté client, notamment HTML et CSS, à l’aide de
Webpack.

1. Exécutez la commande suivante dans la racine du projet pour créer un fichier


package.json :

Console

npm init -y

2. Ajoutez la propriété mise en surbrillance au fichier package.json et enregistrez les


modifications apportées au fichier :

JSON

{
"name": "SignalRWebpack",
"version": "1.0.0",
"private": true,
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

La définition de la propriété private sur true empêche les avertissements


d’installation de package à l’étape suivante.

3. Installez les packages npm nécessaires. À partir de la racine du projet, exécutez la


commande suivante :

Console

npm i -D -E clean-webpack-plugin css-loader html-webpack-plugin mini-


css-extract-plugin ts-loader typescript webpack webpack-cli

L’option -E désactive le comportement par défaut de npm pour écrire contrôle de


version sémantique opérateurs de plage pour package.json . Par exemple,
"webpack": "5.76.1" peut être utilisé à la place de "webpack": "^5.76.1" . Cette

option empêche les mises à niveau involontaires vers des versions de package plus
récentes.

Pour plus d’informations, consultez la documentation npm-install .

4. Remplacez la propriété scripts du fichier package.json par le code suivant :

JSON

"scripts": {
"build": "webpack --mode=development --watch",
"release": "webpack --mode=production",
"publish": "npm run release && dotnet publish -c Release"
},

Les scripts suivants sont définis :

build : Regroupe les ressources côté client en mode de développement et

surveille les changements de fichier. L’observateur de fichiers force la


regénération du regroupement chaque fois qu’un fichier projet change.
L’option mode désactive les optimisations de production, comme la
minimisation de l’arborescence (tree shaking). utilisez build uniquement
dans le développement.
release : Regroupe les ressources côté client en mode de production.
publish : Exécute le script release pour regrouper les ressources côté client

en mode de production. La commande appelle la commande publish de CLI


.NET pour publier l’application.

5. Créez un fichier nommé webpack.config.js à la racine du projet avec le code


suivant :

JavaScript

const path = require("path");


const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "wwwroot"),
filename: "[name].[chunkhash].js",
publicPath: "/",
},
resolve: {
extensions: [".js", ".ts"],
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader",
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new MiniCssExtractPlugin({
filename: "css/[name].[chunkhash].css",
}),
],
};

Le fichier précédent configure le processus de compilation Webpack :

La propriété output remplace la valeur par défaut de dist . Le regroupement


est émis dans le répertoire wwwroot à la place.
Le tableau resolve.extensions inclut .js pour importer le code JavaScript
du client SignalR.

6. Créez un nouveau répertoire nommé src à la racine du projet, SignalRWebpack/ ,


pour le code client.

7. Copiez le répertoire src et son contenu de l'exemple de projet vers la racine du


projet. Le répertoire src contient les fichiers suivants :

index.html , qui définit le balisage réutilisable de la page d’accueil :

HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>ASP.NET Core SignalR with TypeScript and
Webpack</title>
</head>
<body>
<div id="divMessages" class="messages"></div>
<div class="input-zone">
<label id="lblMessage" for="tbMessage">Message:</label>
<input id="tbMessage" class="input-zone-input" type="text"
/>
<button id="btnSend">Send</button>
</div>
</body>
</html>

css/main.css , qui fournit des styles CSS pour la page d’accueil :

css

*,
*::before,
*::after {
box-sizing: border-box;
}

html,
body {
margin: 0;
padding: 0;
}

.input-zone {
align-items: center;
display: flex;
flex-direction: row;
margin: 10px;
}

.input-zone-input {
flex: 1;
margin-right: 10px;
}

.message-author {
font-weight: bold;
}

.messages {
border: 1px solid #000;
margin: 10px;
max-height: 300px;
min-height: 300px;
overflow-y: auto;
padding: 5px;
}

tsconfig.json , qui configure le compilateur TypeScript pour produire

javaScript compatible 5 ECMAScript :

JSON

{
"compilerOptions": {
"target": "es5"
}
}

index.ts :

TypeScript

import * as signalR from "@microsoft/signalr";


import "./css/main.css";

const divMessages: HTMLDivElement =


document.querySelector("#divMessages");
const tbMessage: HTMLInputElement =
document.querySelector("#tbMessage");
const btnSend: HTMLButtonElement =
document.querySelector("#btnSend");
const username = new Date().getTime();

const connection = new signalR.HubConnectionBuilder()


.withUrl("/hub")
.build();
connection.on("messageReceived", (username: string, message:
string) => {
const m = document.createElement("div");

m.innerHTML = `<div class="message-author">${username}</div>


<div>${message}</div>`;

divMessages.appendChild(m);
divMessages.scrollTop = divMessages.scrollHeight;
});

connection.start().catch((err) => document.write(err));

tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {


if (e.key === "Enter") {
send();
}
});

btnSend.addEventListener("click", send);

function send() {
connection.send("newMessage", username, tbMessage.value)
.then(() => (tbMessage.value = ""));
}

Le code précédent récupère les références aux éléments DOM et attache


deux gestionnaires d’événements :
keyup : se déclenche lorsque l’utilisateur tape dans la zone de texte

tbMessage et appelle la fonction send lorsque l’utilisateur appuie sur la

touche Entrée.
click : se déclenche lorsque l’utilisateur sélectionne le bouton Envoyer et

appelle send la fonction.

La classe HubConnectionBuilder crée un générateur pour configurer la


connexion de serveur. La fonction withUrl configure l’URL du hub.

SignalR permet l’échange de messages entre un client et un serveur. Chaque


message a un nom spécifique. Par exemple, les messages portant le nom
messageReceived peuvent exécuter la logique responsable de l’affichage du

nouveau message dans la zone de messages. L’écoute d’un message


spécifique peut être effectuée au moyen de la fonction on . N’importe quel
nombre de noms de messages peuvent être écoutés. Vous pouvez aussi
passer des paramètres au message, comme le nom de l’auteur et le contenu
du message reçu. Dès que le client reçoit un message, un élément div est
créé avec le nom de l’auteur et le contenu du message dans son attribut
innerHTML . Il est ajouté à l’élément div principal qui affiche les messages.

L’envoi d’un message au moyen de la connexion WebSocket nécessite l’appel


de la méthode send . Le premier paramètre de la méthode est le nom du
message. Les données du message se trouvent dans les autres paramètres.
Dans cet exemple, un message identifié comme newMessage est envoyé au
serveur. Le message se compose du nom d’utilisateur et de l’entrée de
l’utilisateur dans une zone de texte. Si l’envoi fonctionne, la valeur de la zone
de texte est effacée.

8. Exécutez la commande suivante à la racine du projet :

Console

npm i @microsoft/signalr @types/node

La commande précédente active :

Le SignalRclient TypeScript , ce qui permet au client d’envoyer des


messages au serveur.
Définitions de type TypeScript pour Node.js, qui permet la vérification au
moment de la compilation des types Node.js.

Test de l'application
Vérifiez que l’application fonctionne avec les étapes suivantes :

Visual Studio

1. Exécutez Webpack en mode release . Dans la fenêtre Console du


Gestionnaire de package, exécutez la commande suivante à la racine du
projet.

Console

npm run release

Cette commande génère les ressources côté client à délivrer pendant


l’exécution de l’application. Les ressources sont placées dans le dossier
wwwroot .
Webpack a effectué les tâches suivantes :

Vide le contenu du répertoire wwwroot .


Converti le TypeScript en JavaScript dans un processus appelé
transpilation.
Troncation du code JavaScript généré pour réduire la taille de fichier
dans un processus appelé minimisation.
Copie des fichiers JavaScript, CSS et HTML traités à partir de src dans le
répertoire wwwroot .
Injection des éléments suivants dans le fichier wwwroot/index.html :
Balise <link> , référençant le fichier wwwroot/main.<hash>.css . Cette
balise est placée immédiatement avant la balise </head> de
fermeture.
Balise <script> , référençant le fichier wwwroot/main.<hash>.js minifié.
Cette balise est placée immédiatement après la balise de fermeture
</title> .

2. Sélectionnez Déboguer>Démarrer sans débogage pour lancer l’application


dans un navigateur sans attacher le débogueur. Le fichier wwwroot/index.html
est servi à https://localhost:<port> .

En cas d’erreurs de compilation, essayez de fermer et de rouvrir la solution.

3. Ouvrez une autre instance du navigateur (n’importe quel navigateur) et collez


l’URL dans la barre d’adresse.

4. Choisissez un navigateur, tapez quelque chose dans la zone de texte Message,


puis cliquez sur le bouton Envoyer. Le nom unique de l’utilisateur et le
message sont affichés instantanément dans les deux pages.
Étapes suivantes
Hubs fortement typés
Authentification et autorisation dans ASP.NET Core SignalR
Protocole Hub MessagePack dans SignalR pour ASP.NET Core

Ressources supplémentaires
Client JavaScript SignalRASP.NET Core
Utiliser des hubs dans ASP.NET Core SignalR

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Utiliser ASP.NET Core SignalR avec
Blazor
Article • 09/02/2024

Ce didacticiel fournit une expérience de travail de base pour créer une application en
temps réel SignalR à l'aide de Blazor. Cet article s’adresse aux développeurs qui
connaissent déjà SignalR et qui cherchent à savoir comment utiliser SignalR dans une
application Blazor. Pour obtenir une aide détaillée sur les frameworks SignalR et Blazor,
consultez les ensembles de documentation de référence suivants et la documentation
des API :

Vue d’ensemble d’ASP.NET Core SignalR


ASP.NET Core Blazor
Navigateur d’API .NET

Apprenez à :

" Créer une application Blazor


" Ajouter la bibliothèque cliente SignalR
" Ajouter un hub SignalR
" Ajouter des services SignalR et un point de terminaison pour le hub SignalR
" Ajouter un code de composant Razor pour la conversation

À la fin de ce tutoriel, vous disposerez d’une application de conversation opérationnelle.

Prérequis
Visual Studio

Visual Studio 2022 version 16.4 ou ultérieure avec la charge de travail


Développement ASP.NET et web

Exemple d’application
Le téléchargement de l’exemple d’application de conversation du tutoriel n’est pas
requis pour ce tutoriel. L’exemple d’application est l’application de travail opérationnelle
finale produite en suivant les étapes de ce tutoriel.

Afficher ou télécharger l’exemple de code


Créer une application Web Blazor
Suivez l’aide pour votre choix d’outils :

Visual Studio

7 Notes

Visual Studio 2022 ou version ultérieure et .NET Core SDK 8.0.0 ou version
ultérieure sont requis.

Créez un projet.

Sélectionnez le modèle d'Blazorapplication Web. Sélectionnez Suivant.

Entrez BlazorSignalRApp dans le champ Nom du projet. Vérifiez que


l’entrée Emplacement est correcte ou indiquez un emplacement pour le projet.
Sélectionnez Suivant.

Confirmez que le Framework est .NET 8.0 ou version ultérieure. Sélectionnez Create
(Créer).

Ajouter la bibliothèque cliente SignalR


Visual Studio

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le


projet BlazorSignalRApp , puis sélectionnez Gérer les packages NuGet.

Dans la boîte de dialogue Gérer les packages NuGet, confirmez que la source du
package est définie sur nuget.org .

Avec Parcourir sélectionné, entrez Microsoft.AspNetCore.SignalR.Client dans la


zone de recherche.

Dans les résultats de la recherche, sélectionnez la dernière version du package


Microsoft.AspNetCore.SignalR.Client . Cliquez sur Installer.

Si la fenêtre de dialogue Aperçu des modifications s’affiche, sélectionnez OK.


Si la boîte de dialogue Acceptation de la licence s’affiche, sélectionnez J’accepte si
vous acceptez les termes du contrat de licence.

Ajouter un hub SignalR


Créez un dossier (plural) Hubs et ajoutez la classe ChatHub ( Hubs/ChatHub.cs ) suivante à
la racine de l’application :

C#

using Microsoft.AspNetCore.SignalR;

namespace BlazorSignalRApp.Hubs;

public class ChatHub : Hub


{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}

Ajouter des services et un point de terminaison


au hub SignalR
Ouvrez le fichier Program .

Ajoutez l’espace de noms de Microsoft.AspNetCore.ResponseCompression et de la


classe ChatHub en haut du fichier :

C#

using Microsoft.AspNetCore.ResponseCompression;
using BlazorSignalRApp.Hubs;

Ajouter des services de middleware de compression de réponse :

C#

builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});

Utilisez Response Compression Middleware en haut de la configuration du pipeline de


traitement :

C#

app.UseResponseCompression();

Ajoutez un point de terminaison pour le hub immédiatement après la ligne routant les
composants Razor ( app.MapRazorComponents<T>() ) :

C#

app.MapHub<ChatHub>("/chathub");

Ajouter le code de composant Razor pour la


conversation
Ouvrez le fichier Components/Pages/Home.razor .

Remplacez la balise par le code suivant :

razor

@page "/"
@rendermode InteractiveServer
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable

<PageTitle>Home</PageTitle>

<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>

<hr>

<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>

@code {
private HubConnection? hubConnection;
private List<string> messages = new List<string>();
private string? userInput;
private string? messageInput;

protected override async Task OnInitializedAsync()


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();

hubConnection.On<string, string>("ReceiveMessage", (user, message)


=>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
InvokeAsync(StateHasChanged);
});

await hubConnection.StartAsync();
}

private async Task Send()


{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput,
messageInput);
}
}

public bool IsConnected =>


hubConnection?.State == HubConnectionState.Connected;

public async ValueTask DisposeAsync()


{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
7 Notes

Lorsque vous utilisez le Rechargement à chaud, désactivez l’intergiciel de


compression de réponse dans l’environnement Development . Pour plus
d’informations, consultez Aide relative à ASP.NET Core BlazorSignalR.

Exécuter l’application
Suivez l’aide relative à vos outils :

Visual Studio

Appuyez sur F5 pour exécuter l’application avec le débogage ou sur Ctrl + F5

(Windows)/ ⌘ + F5 (macOS) pour exécuter l’application sans débogage.

Copiez l’URL à partir de la barre d’adresse, ouvrez un autre onglet ou instance du


navigateur, puis collez l’URL dans la barre d’adresse.

Choisissez un des navigateurs, entrez un nom et un message, puis sélectionnez le


bouton pour envoyer le message. Le nom et le message sont affichés instantanément
dans les deux pages :

Citations : Star Trek VI : Terre inconnue ©1991 Paramount

Étapes suivantes
Dans ce didacticiel, vous avez appris à :

" Créer une application Blazor


" Ajouter la bibliothèque cliente SignalR
" Ajouter un hub SignalR
" Ajouter des services SignalR et un point de terminaison pour le hub SignalR
" Ajouter un code de composant Razor pour la conversation

Pour obtenir une aide détaillée sur les frameworks SignalR et Blazor, consultez les
ensembles de documentation de référence suivants :

Vue d’ensemble d’ASP.NET Core SignalR ASP.NET Core Blazor

Ressources supplémentaires
Authentification par jeton du porteur avec Identity Server, WebSockets et Server-
Sent Events
Sécurisez un hub SignalR dans les applications Blazor WebAssembly hébergées
Négociation cross-origin SignalR pour l’authentification
Configuration SignalR
Déboguer des applications ASP.NET Core Blazor
Conseils d’atténuation des menaces pour le rendu statique côté serveur de Blazor
ASP.NET Core
Conseils d’atténuation des menaces pour le rendu interactif côté serveur de Blazor
ASP.NET Core
Blazor exemples de référentiel GitHub (dotnet/blazor-samples)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Tutoriel : Créer un serveur et un client
gRPC dans ASP.NET Core
Article • 30/11/2023

Ce tutoriel montre comment créer un client gRPC .NET Core et un serveur gRPC
ASP.NET Core. À la fin, vous disposerez d’un client gRPC qui communique avec le service
Greeter gRPC.

Dans ce tutoriel, vous allez :

" Créer un serveur gRPC.


" Créez un client gRPC.
" Testez le client gRPC avec le service Greeter gRPC.

Prérequis
Visual Studio

Préversion de Visual Studio 2022 avec la charge de travail ASP.NET et


développement web.

Créer un service gRPC


Visual Studio

Démarrez Visual Studio 2022 et sélectionnez Nouveau projet.


Dans la boîte de dialogue Créer un nouveau projet, recherchez gRPC .
Sélectionnez Service ASP.NET Core gRPC, puis sélectionnez Suivant.
Dans la boîte de dialogue Configurer votre nouveau projet,
entrez GrpcGreeter pour Nom du projet. Il est important de nommer le projet
GrpcGreeter pour que les espaces de noms correspondent quand vous copiez
et collez du code.
Cliquez sur Suivant.
Dans la fenêtre de dialogue Informations supplémentaires, sélectionnez
.NET 8.0 (prise en charge à long terme), puis sélectionnez Créer.

Exécuter le service

Visual Studio

Appuyez sur Ctrl+F5 pour exécuter sans le débogueur.

Visual Studio affiche la boîte de dialogue suivante lorsqu’un projet n’est pas
encore configuré pour utiliser SSL :

Sélectionnez Oui si vous faites confiance au certificat SSL d’IIS Express.

La boîte de dialogue suivante s’affiche :


Sélectionnez Oui si vous acceptez d’approuver le certificat de développement.

Pour plus d’informations sur l’approbation du navigateur Firefox, consultez


Erreur de certificat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.

Visual Studio :
Démarre le serveur Kestrel.
Lance un navigateur.
Accède à http://localhost:port , par exemple http://localhost:7042 .
port : numéro de port attribué de manière aléatoire pour l’application.
localhost : nom d’hôte standard de l’ordinateur local. Localhost traite

uniquement les requêtes web de l’ordinateur local.

Les journaux affichent le service à l’écoute sur https://localhost:<port> , où <port> est


le numéro de port localhost attribué de manière aléatoire lorsque le projet est créé et
défini dans Properties/launchSettings.json .

Console

info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:<port>
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
7 Notes

Le modèle gRPC est configuré pour utiliser le protocole Transport Layer Security
(TLS) . Les clients gRPC doivent utiliser le protocole HTTPS pour appeler le
serveur. Le numéro de port localhost du service gRPC est attribué de manière
aléatoire lorsque le projet est créé et défini dans le fichier
Properties\launchSettings.json du projet de service gRPC.

Examiner les fichiers projet


Fichiers projet GrpcGreeter :

Protos/greet.proto : définit le gRPC Greeter et est utilisé pour générer les

ressources du serveur gRPC. Pour plus d’informations, consultez Introduction à


gRPC.
Dossier Services : contient l’implémentation du service Greeter .
appSettings.json : contient des données de configuration, telles que le protocole

utilisé par Kestrel. Pour plus d’informations, consultez Configuration dans ASP.NET
Core.
Program.cs , qui contient :

le point d’entrée du service gRPC. Pour plus d’informations, consultez Hôte


générique .NET dans ASP.NET Core.
le code qui configure le comportement de l’application. Pour plus
d’informations, consultez Démarrage des applications.

Créer le client gRPC dans une application


console .NET
Visual Studio

Ouvrez une deuxième instance de Visual Studio et sélectionnez Nouveau


projet.
Dans la boîte de dialogue Créer un projet, sélectionnez Application console,
puis sélectionnez Suivant.
Dans la zone de texte Nom du projet, entrez GrpcGreeterClient et
sélectionnez Suivant.
Dans la fenêtre de dialogue Informations supplémentaires, sélectionnez
.NET 8.0 (prise en charge à long terme), puis sélectionnez Créer.

Ajouter les packages NuGet exigés


Le projet client gRPC requiert les packages NuGet suivants :

Grpc.Net.Client , qui contient le client .NET Core.


Google.Protobuf , qui contient des API de messages protobuf pour C#.
Grpc.Tools , qui contient la prise en charge des outils C# pour les
fichiers protobuf. Le package d’outils n’est pas nécessaire lors de l’exécution. La
dépendance est donc marquée avec PrivateAssets="All" .

Visual Studio

Installez les packages à l’aide de la console PMC (console du Gestionnaire de


package) ou à partir de Gérer les packages NuGet.

Option de la console du Gestionnaire de package pour installer


des packages
Dans Visual Studio, sélectionnez Outils>Gestionnaire de package
NuGet>Console du Gestionnaire de package.

Dans la fenêtre Gestionnaire de package, exécutez cd GrpcGreeterClient


pour accéder au dossier contenant les fichiers GrpcGreeterClient.csproj .

Exécutez les commandes suivantes :

PowerShell

Install-Package Grpc.Net.Client
Install-Package Google.Protobuf
Install-Package Grpc.Tools

Option Gérer les packages NuGet pour installer les packages

Cliquez avec le bouton droit sur le projet dans Explorateur de


solutions>Gérer les packages NuGet.
Sélectionnez l’onglet Parcourir.
Entrez Grpc.Net.Client dans la zone de recherche.
Sélectionnez le package Grpc.Net.Client sous l’onglet Parcourir et
sélectionnez Installer.
Répétez pour Google.Protobuf et Grpc.Tools .

Ajouter greet.proto
Créez un dossier Protos dans le projet du client gRPC.

Copiez le fichier Protos\greet.proto du service Greeter gRPC dans le dossier Protos


du projet du client gRPC.

Mettez à jour l’espace de noms à l’intérieur du fichier greet.proto vers l’espace de


noms du projet :

JSON

option csharp_namespace = "GrpcGreeterClient";

Modifier le fichier projet GrpcGreeterClient.csproj :

Visual Studio

Cliquez avec le bouton droit sur le projet et sélectionnez Modifier le fichier de


projet.

Ajoutez un groupe d’éléments avec un élément <Protobuf> qui fait référence au


fichier greet.proto :

XML

<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>

Créer le client Greeter


Générez le projet client pour créer les types dans l’espace de noms
GrpcGreeterClient .

7 Notes
Les types GrpcGreeterClient sont générés automatiquement par le processus de
génération. Le package d’outils Grpc.Tools génère les fichiers suivants en
fonction du fichier greet.proto :

GrpcGreeterClient\obj\Debug\[TARGET_FRAMEWORK]\Protos\Greet.cs : code de

mémoire tampon de protocole qui remplit, sérialise et récupère les types de


messages de requête et de réponse.
GrpcGreeterClient\obj\Debug\[TARGET_FRAMEWORK]\Protos\GreetGrpc.cs :

contient les classes clientes générées.

Pour plus d’informations sur les ressources C# générées automatiquement par


Grpc.Tools , consultez Services gRPC avec C# : ressources C# générées.

Mettez à jour le fichier Program.cs du client gRPC par le code suivant.

C#

using System.Threading.Tasks;
using Grpc.Net.Client;
using GrpcGreeterClient;

// The port number must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:7042");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();

Dans le code mis en surbrillance précédent, remplacez le numéro de port localhost


7042 par le numéro de port HTTPS spécifié dans Properties/launchSettings.json

au sein du projet de service GrpcGreeter .

Program.cs contient le point d’entrée et la logique du client gRPC.

Le client Greeter est créé en :

Instanciant un GrpcChannel contenant les informations de création de la connexion


au service gRPC.
Utilisant le GrpcChannel pour construire le client Greeter :

C#
// The port number must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:7042");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();

Le client Greeter appelle la méthode SayHello asynchrone. Le résultat de l’appel


SayHello s’affiche :

C#

// The port number must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:7042");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();

Tester le client gRPC avec le service Greeter


gRPC
Mettez à jour le fichier appsettings.Development.json en ajoutant les lignes en
surbrillance suivantes :

C#

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
,"Microsoft.AspNetCore.Hosting": "Information",
"Microsoft.AspNetCore.Routing.EndpointMiddleware": "Information"
}
}
}

Visual Studio
Dans le service Greeter, appuyez sur Ctrl+F5 pour démarrer le serveur sans le
débogueur.
Dans le projet GrpcGreeterClient , appuyez sur Ctrl+F5 pour démarrer le
client sans le débogueur.

Le client envoie une salutation au service avec un message contenant son nom,
GreeterClient. Le service envoie le message « Hello GreeterClient » comme réponse. La
réponse « Hello GreeterClient » s’affiche dans l’invite de commandes :

Console

Greeting: Hello GreeterClient


Press any key to exit...

Le service gRPC enregistre les détails de l’appel réussi dans les journaux écrits dans
l’invite de commandes :

Console

info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:<port>
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path:
C:\GH\aspnet\docs\4\Docs\aspnetcore\tutorials\grpc\grpc-
start\sample\GrpcGreeter
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 POST https://localhost:
<port>/Greet.Greeter/SayHello application/grpc
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'gRPC - /Greet.Greeter/SayHello'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'gRPC - /Greet.Greeter/SayHello'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 78.32260000000001ms 200 application/grpc

7 Notes

Le code de cet article requiert le certificat de développement ASP.NET Core HTTPS


pour sécuriser le service gRPC. Si le client gRPC .NET échoue avec le message The
remote certificate is invalid according to the validation procedure. ou The
SSL connection could not be established. , le certificat de développement n’est

pas approuvé. Pour régler ce problème, consultez Appeler un service gRPC avec
un certificat non approuvé/non valide.

Étapes suivantes
Affichez ou téléchargez l’exemple de code terminé pour ce tutoriel (comment
télécharger).
Vue d’ensemble de gRPC sur .NET
Services gRPC avec C#
Migrer gRPC de C-Core vers gRPC pour .NET

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Pages Razor avec Entity Framework Core
dans ASP.NET Core - Tutoriel 1 sur 8
Article • 30/11/2023

Par Tom Dykstra , Jeremy Likness et Jon P Smith

Il s’agit du premier tutoriel d’une série qui montre comment utiliser Entity Framework
(EF) Core dans une application Pages Razor ASP.NET Core. Dans ces tutoriels, un site
web est créé pour une université fictive nommée Contoso. Le site comprend des
fonctionnalités comme l’admission des étudiants, la création de cours et les affectations
des formateurs. Le tutoriel utilise l’approche code first. Pour plus d’informations sur la
façon de suivre ce tutoriel à l’aide de l’approche database first, consultez ce problème
Github .

Télécharger ou afficher l’application terminée. Télécharger les instructions.

Prérequis
Si vous débutez avec Pages Razor, parcourez le tutoriel Bien démarrer avec Pages
Razor avant de commencer celui-ci.

Visual Studio

Visual Studio 2022 avec la charge de travail Développement web et


ASP.NET.
SDK .NET 6.0

Moteurs de base de données


Les instructions Visual Studio utilisent la Base de données locale SQL Server, version
de SQL Server Express qui s’exécute uniquement sur Windows.

Résolution des problèmes


Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code
au projet terminé . Un bon moyen d’obtenir de l’aide est de poster une question sur
StackOverflow.com en utilisant le mot-clé ASP.NET Core ou le mot-clé EF Core .
Exemple d’application
L’application générée dans ces didacticiels est le site web de base d’une université. Les
utilisateurs peuvent afficher et mettre à jour les informations relatives aux étudiants, aux
cours et aux formateurs. Voici quelques-uns des écrans créés dans le didacticiel.
Le style de l’interface utilisateur de ce site repose sur les modèles de projet intégrés. Le
tutoriel traite essentiellement de l’utilisation d’EF Core avec ASP.NET Core, et non de la
façon de personnaliser l’interface utilisateur.

Facultatif : Générer l’exemple de téléchargement


Cette étape est facultative. Il est recommandé de générer l’application terminée lorsque
vous rencontrez des problèmes que vous ne pouvez pas résoudre. Si vous rencontrez un
problème que vous ne pouvez pas résoudre, comparez votre code au projet terminé .
Télécharger les instructions.

Visual Studio

Sélectionnez ContosoUniversity.csproj pour ouvrir le projet.

Créez le projet.

Dans la console du Gestionnaire de package (PMC), exécutez la commande


suivante :

PowerShell
Update-Database

Exécutez le projet pour amorcer la base de données.

Créer le projet d’application web


Visual Studio

1. Démarrez Visual Studio 2022 et sélectionnez Créer un projet.

2. Dans la boîte de dialogue Créer un nouveau projet, sélectionnez ASP.NET


Core Web App, puis Suivant.
3. Dans la boîte de dialogue Configurer votre nouveau projet, entrez
ContosoUniversity pour Nom du projet. Il est important de nommer le projet

ContosoUniversity, en respectant la casse, pour que les espaces de noms


correspondent quand vous copiez et collez l’exemple de code.

4. Cliquez sur Suivant.

5. Dans la boîte de dialogue Informations supplémentaires, sélectionnez


.NET 6.0 (prise en charge à long terme), puis sélectionnez Créer.
Configurer le style du site
Copiez et collez le code suivant dans le fichier Pages/Shared/_Layout.cshtml :

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
<link rel="stylesheet" href="~/ContosoUniversity.styles.css" asp-append-
version="true" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-
light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-
page="/Index">Contoso University</a>
<button class="navbar-toggler" type="button" data-bs-
toggle="collapse" data-bs-target=".navbar-collapse" aria-
controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle
navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex
justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
page="/About">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
page="/Students/Index">Students</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
page="/Courses/Index">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
page="/Instructors/Index">Instructors</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
page="/Departments/Index">Departments</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">


<div class="container">
&copy; 2021 - Contoso University - <a asp-area="" asp-
page="/Privacy">Privacy</a>
</div>
</footer>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>

@await RenderSectionAsync("Scripts", required: false)


</body>
</html>

Le fichier de layout définit l’en-tête, le pied de page et le menu du site. Le code


précédent apporte les modifications suivantes :

Remplacez chaque occurrence de « ContosoUniversity » par « Contoso


University ». Il y a trois occurrences.
Les entrées de menu Home et Privacy sont supprimées.
Les entrées sont ajoutées à À propos de, Étudiants, Cours, Instructeurset
Départements.

Dans Pages/Index.cshtml , remplacez le contenu du fichier par le code suivant :

CSHTML

@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}

<div class="row mb-auto">


<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 mb-4 ">
<p class="card-text">
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core Razor Pages web app.
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 d-flex flex-column position-static">
<p class="card-text mb-auto">
You can build the application by following the steps in
a series of tutorials.
</p>
<p>
@* <a
href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro"
class="stretched-link">See the tutorial</a>
*@ </p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 d-flex flex-column">
<p class="card-text mb-auto">
You can download the completed project from GitHub.
</p>
<p>
@* <a
href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef
-rp/intro/samples" class="stretched-link">See project source code</a>
*@ </p>
</div>
</div>
</div>
</div>

Le code précédent remplace le texte à propose de ASP.NET Core par le texte à propos
de cette application.

Exécutez l’application pour vérifier que la page d’accueil (« Home ») s’affiche.

Le modèle de données
Les sections suivantes créent un modèle de données :

Un étudiant peut s’inscrire à un nombre quelconque de cours et un cours peut avoir un


nombre quelconque d’élèves inscrits.

L’entité Student

Créez un dossier Models dans le dossier de projet.


Créez Models/Student.cs avec le code suivant :

C#

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

La propriété ID devient la colonne de clé primaire de la table de base de données qui


correspond à cette classe. Par défaut, EF Core interprète une propriété nommée ID ou
classnameID comme clé primaire. L’autre nom reconnu automatiquement de la clé

primaire de classe Student est StudentID . Pour plus d’informations, consultez EF Core -
Clés.

La propriété Enrollments est une propriété de navigation. Les propriétés de navigation


contiennent d’autres entités qui sont associées à cette entité. Dans ce cas, la propriété
Enrollments d’une entité Student contient toutes les entités Enrollment associées à cet

étudiant. Par exemple, si une ligne Student dans la base de données est associée à deux
lignes Enrollment, la propriété de navigation Enrollments contient ces deux entités
Enrollment.

Dans la base de données, une ligne Inscription est associée à une ligne Étudiant si sa
colonne StudentID contient la valeur d’ID de l’étudiant. Par exemple, supposez qu’une
ligne Student présente un ID égal à 1. Les lignes Inscription associées auront un
StudentID égal à 1. StudentID est une clé étrangère dans la table Inscription.

La propriété Enrollments est définie en tant que ICollection<Enrollment> , car plusieurs


entités Enrollment associées peuvent exister. D’autres types de collection peuvent être
utilisés, tels que List<Enrollment> ou HashSet<Enrollment> . Quand vous utilisez
ICollection<Enrollment> , EF Core crée une collection HashSet<Enrollment> par défaut.

L’entité Enrollment
Créez Models/Enrollment.cs avec le code suivant :

C#

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}

public class Enrollment


{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }

public Course Course { get; set; }


public Student Student { get; set; }
}
}

La propriété EnrollmentID est la clé primaire ; cette entité utilise le modèle classnameID
à la place de ID par lui-même. Pour un modèle de données de production, beaucoup
de développeurs choisissent un modèle et l’utilisent systématiquement. Ce tutoriel
utilise les deux pour montrer qu’ils fonctionnent tous les deux. L’utilisation de ID sans
classname facilite l’implémentation de certaines modifications du modèle de données.

La propriété Grade est un enum . La présence du point d’interrogation après la


déclaration de type Grade indique que la propriété Grade accepte les valeurs Null. Une
note (Grade) qui a la valeur Null est différente d’une note égale à zéro : la valeur Null
signifie qu’une note n’est pas connue ou n’a pas encore été affectée.
La propriété StudentID est une clé étrangère, et la propriété de navigation
correspondante est Student . Une entité Enrollment est associée à une entité Student .
Par conséquent, la propriété contient une seule entité Student .

La propriété CourseID est une clé étrangère, et la propriété de navigation


correspondante est Course . Une entité Enrollment est associée à une entité Course .

EF Core interprète une propriété en tant que clé étrangère si elle se nomme <navigation
property name><primary key property name> . Par exemple, StudentID est la clé étrangère

pour la propriété de navigation Student , car la clé primaire de l’entité Student est ID .
Les propriétés de clé étrangère peuvent également se nommer <primary key property
name> . Par exemple, CourseID puisque la clé primaire de l’entité Course est CourseID .

L’entité Course

Créez Models/Course.cs avec le code suivant :

C#

using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

La propriété Enrollments est une propriété de navigation. Une entité Course peut être
associée à un nombre quelconque d’entités Enrollment .
L’attribut DatabaseGenerated permet à l’application de spécifier la clé primaire, plutôt
que de la faire générer par la base de données.

Générez l'application. Le compilateur génère plusieurs avertissements sur la façon dont


les valeurs null sont gérées. Pour plus d’informations, consultez ce problème GitHub ,
Types de référence pouvant accepter la valeur Null et Tutoriel : Exprimer votre intention
de conception plus clairement avec des types de référence pouvant accepter la valeur
Null et non-nullables.

Pour éliminer les avertissements des types de référence pouvant accepter la valeur Null,
supprimez la ligne suivante du fichier ContosoUniversity.csproj :

XML

<Nullable>enable</Nullable>

Le moteur de génération de modèles automatique ne prend actuellement pas en charge


les types de référence pouvant accepter la valeur Null. Par conséquent, les modèles
utilisés dans la génération automatique de modèles ne le peuvent pas non plus.

Supprimez l’annotation ? de type de référence pouvant accepter la valeur Null de


public string? RequestId { get; set; } dans Pages/Error.cshtml.cs afin que le projet

génère sans avertissements du compilateur.

Générer automatiquement des modèles de


pages Student
Dans cette section, l’outil de génération de modèles automatique ASP.NET Core est
utilisé pour générer :

Une classe EF Core DbContext . Le contexte est la classe principale qui coordonne les
fonctionnalités d’Entity Framework pour un modèle de données déterminé. Il
dérive de la classe Microsoft.EntityFrameworkCore.DbContext.
Les pages Razor qui gèrent les opérations Créer, Lecture, Mettre à jour et
Supprimer (CRUD) pour l’entité Student .

Visual Studio

Créez un dossier Pages/Students.


Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le dossier
Pages/Students, puis sélectionnez Ajouter>Nouvel élément généré
automatiquement.
Dans la boîte de dialogue Ajouter un nouvel élément de génération
automatique de modèles :
Dans l’onglet de gauche, sélectionnez Pages >Razor courantes > installées
Sélectionnez Pages Razor utilisant Entity Framework (CRUD)>ADD.
Dans la boîte de dialogue Ajouter des Pages Razor avec Entity Framework
(CRUD) :
Dans la liste déroulante Classe de modèle, sélectionnez Student
(ContosoUniversity.Models).
Dans la ligne Classe du contexte de données, sélectionnez le signe +
(plus).
Modifiez le nom du contexte de données pour qu’il se termine par
SchoolContext plutôt que par ContosoUniversityContext . Le nom du

contexte mis à jour : ContosoUniversity.Data.SchoolContext


Sélectionnez Ajouter pour terminer l’ajout de la classe de contexte de
données.
Sélectionnez Ajouter pour terminer la boîte de dialogue Ajouter des
Pages Razor.

Les packages suivants sont automatiquement installés :

Microsoft.EntityFrameworkCore.SqlServer

Microsoft.EntityFrameworkCore.Tools
Microsoft.VisualStudio.Web.CodeGeneration.Design

Si l’étape précédente échoue, générez le projet et recommencez l’étape de génération


de modèles automatique.

Le processus de génération de modèles automatique :

Crée les pages Razor dans le dossier Pages/Étudiants :


Create.cshtml et Create.cshtml.cs

Delete.cshtml et Delete.cshtml.cs
Details.cshtml et Details.cshtml.cs

Edit.cshtml et Edit.cshtml.cs
Index.cshtml et Index.cshtml.cs

Crée Data/SchoolContext.cs .
Ajoute le contexte à l’injection de dépendances dans Program.cs .
Ajoute une chaîne de connexion de base de données à appsettings.json .
Chaîne de connexion de base de données
L’outil de génération de modèles automatique génère une chaîne de connexion dans le
fichier appsettings.json .

Visual Studio

La chaîne de connexion spécifie SQL Server LocalDB :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Server=
(localdb)\\mssqllocaldb;Database=SchoolContext-
0e9;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

LocalDB est une version allégée du moteur de base de données SQL Server Express.
Elle est destinée au développement d’applications, et non à une utilisation en
production. Par défaut, la Base de données locale crée des fichiers .mdf dans le
répertoire C:/Users/<user> .

Mettre à jour la classe du contexte de base de


données
La classe principale qui coordonne les fonctionnalités EF Core pour un modèle de
données déterminé est la classe du contexte de base de données. Le contexte de
données est dérivé de Microsoft.EntityFrameworkCore.DbContext. Il spécifie les entités
qui sont incluses dans le modèle de données. Dans ce projet, la classe est nommée
SchoolContext .

Mettez à jour Data/SchoolContext.cs à l’aide du code suivant :

C#
using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext (DbContextOptions<SchoolContext> options)
: base(options)
{
}

public DbSet<Student> Students { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}

Le code précédent passe du singulier DbSet<Student> Student au pluriel DbSet<Student>


Students . Pour que le code Pages Razor corresponde au nouveau nom de DBSet ,

apportez une modification globale à partir de : _context.Student. vers :


_context.Students.

Il y a 8 occurrences.

Étant donné qu’un jeu d’entités contient plusieurs entités, de nombreux développeurs
préfèrent que les noms de propriétés DBSet soient au pluriel.

Le code mis en mis en évidence :

Crée une propriété DbSet<TEntity> pour chaque jeu d’entités. Dans la


terminologie EF Core :
Un jeu d’entités correspond généralement à une table de base de données.
Une entité correspond à une ligne dans la table.
Appelle OnModelCreating. OnModelCreating :
Est appelée lorsque SchoolContext a été initialisé, mais avant que le modèle ne
soit verrouillé et utilisé pour initialiser le contexte.
Est obligatoire, car plus loin dans le tutoriel, l’entité Student aura des références
aux autres entités.
Nous espérons résoudre ce problème dans une version ultérieure.

Program.cs
ASP.NET Core comprend l’injection de dépendances. Des services, tels que
SchoolContext , sont inscrits avec l’injection de dépendances au démarrage de

l’application. Ces services sont affectés aux composants qui les nécessitent, par exemple
les Pages Razor, par le biais de paramètres de constructeur. Le code de constructeur qui
obtient une instance de contexte de base de données est indiqué plus loin dans le
tutoriel.

L’outil de génération de modèles automatique a inscrit automatiquement la classe du


contexte dans le conteneur d’injection de dépendances.

Visual Studio

Les lignes en surbrillance suivantes ont été ajoutées par l’outil de génération de
modèles automatique :

C#

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolCo
ntext")));

Le nom de la chaîne de connexion est transmis au contexte en appelant une méthode


sur un objet DbContextOptions. Pour le développement local, le système de
configuration ASP.NET Core lit la chaîne de connexion à partir du fichier
appsettings.json ou appsettings.Development.json .

Ajouter le filtre d’exception de la base de données


Ajoutez AddDatabaseDeveloperPageExceptionFilter et UseMigrationsEndPoint comme
indiqué dans le code suivant :
Visual Studio

C#

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolCo
ntext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}

Ajoutez le package NuGet


Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore .

Dans la Console du gestionnaire de package, entrez la commande suivante pour


ajouter le package NuGet :

PowerShell

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

Le package NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore fournit un


intergiciel ASP.NET Core pour les pages d’erreur Entity Framework Core. Cet intergiciel
permet de détecter et de diagnostiquer les erreurs de migration Entity Framework Core.

Le AddDatabaseDeveloperPageExceptionFilter fournit des informations utiles sur les


erreurs dans l’environnement de développement pour les erreurs de migration EF.
Création de la base de données
Mettez à jour Program.cs pour créer la base de données si elle n’existe pas :

Visual Studio

C#

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolCo
ntext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}

using (var scope = app.Services.CreateScope())


{
var services = scope.ServiceProvider;

var context = services.GetRequiredService<SchoolContext>();


context.Database.EnsureCreated();
// DbInitializer.Initialize(context);
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();
app.Run();

La méthode EnsureCreated n’effectue aucune action s’il existe une base de données
pour le contexte. S’il n’existe pas de base de données, elle crée la base de données et le
schéma. EnsureCreated active le workflow suivant pour gérer les modifications du
modèle de données :

Supprimez la base de données. Toutes les données existantes sont perdues.


Modifiez le modèle de données. Par exemple, ajoutez un champ EmailAddress .
Exécutez l’application.
EnsureCreated crée une base de données avec le nouveau schéma.

Ce workflow fonctionne à un stade précoce du développement, quand le schéma évolue


rapidement, aussi longtemps que vous n’avez pas besoin de conserver les données. La
situation est différente quand les données qui ont été entrées dans la base de données
doivent être conservées. Dans ce cas, procédez à des migrations.

Plus tard dans cette série de tutoriels, vous supprimerez la base de données créée
par EnsureCreated et procéderez à des migrations. Une base de données créée par
EnsureCreated ne peut pas être mise à jour via des migrations.

Test de l'application
Exécutez l’application.
Sélectionnez le lien Students, puis Créer nouveau.
Testez les liens Modifier, Détails et Supprimer.

Amorcer la base de données


La méthode EnsureCreated crée une base de données vide. Cette section ajoute du code
qui remplit la base de données avec des données de test.

Créez Data/DbInitializer.cs avec le code suivant :

C#

using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}

var students = new Student[]


{
new
Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.P
arse("2019-09-01")},
new
Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Pa
rse("2017-09-01")},
new
Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse
("2018-09-01")},
new
Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Pa
rse("2017-09-01")},
new
Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017
-09-01")},
new
Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Pars
e("2016-09-01")},
new
Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse
("2018-09-01")},
new
Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Pars
e("2019-09-01")}
};

context.Students.AddRange(students);
context.SaveChanges();

var courses = new Course[]


{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};

context.Courses.AddRange(courses);
context.SaveChanges();

var enrollments = new Enrollment[]


{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};

context.Enrollments.AddRange(enrollments);
context.SaveChanges();
}
}
}

Le code vérifie si des étudiants figurent dans la base de données. S’il n’y a pas
d’étudiants, il ajoute des données de test à la base de données. Il crée les données de
test dans des tableaux et non dans des collections List<T> afin d’optimiser les
performances.

Dans Program.cs , supprimez // de la ligne DbInitializer.Initialize :

C#

using (var scope = app.Services.CreateScope())


{
var services = scope.ServiceProvider;

var context = services.GetRequiredService<SchoolContext>();


context.Database.EnsureCreated();
DbInitializer.Initialize(context);
}

Visual Studio

Arrêtez l’application si elle est en cours d’exécution et exécutez la commande


suivante dans la Console du gestionnaire de package :

PowerShell

Drop-Database -Confirm
Répondez avec Y pour supprimer la base de données.

Redémarrez l’application.
Sélectionnez la page Students pour examiner les données amorcées.

Afficher la base de données


Visual Studio

Ouvrez l’Explorateur d’objets SQL Server (SSOX) à partir du menu Affichage


de Visual Studio.
Dans SSOX, sélectionnez (localdb)\MSSQLLocalDB > Databases >
SchoolContext-{GUID}. Le nom de la base de données est généré à partir du
nom de contexte indiqué précédemment, ainsi que d’un tiret et d’un GUID.
Développez le nœud Tables.
Cliquez avec le bouton droit sur la table Student et cliquez sur Afficher les
données pour voir les colonnes créées et les lignes insérées dans la table.
Cliquez avec le bouton droit sur la table Student et cliquez sur Afficher le
code pour voir comment le modèle Student est mappé au schéma de la table
Student .

Méthodes EF asynchrones dans les applications


web ASP.NET Core
La programmation asynchrone est le mode par défaut pour ASP.NET Core et EF Core.

Un serveur web a un nombre limité de threads disponibles et, dans les situations de
forte charge, tous les threads disponibles peuvent être utilisés. Quand cela se produit, le
serveur ne peut pas traiter de nouvelle requête tant que les threads ne sont pas libérés.
Avec le code synchrone, plusieurs threads peuvent être bloqués alors qu’ils n’effectuent
aucun travail, car ils attendent que des E/S se terminent. Avec le code asynchrone,
quand un processus attend que des E/S se terminent, son thread est libéré afin d’être
utilisé par le serveur pour traiter d’autres demandes. Il permet ainsi d’utiliser les
ressources serveur plus efficacement, et le serveur peut gérer plus de trafic sans retard.

Le code asynchrone introduit néanmoins une petite surcharge au moment de


l’exécution. Dans les situations de faible trafic, le gain de performances est négligeable,
tandis qu’en cas de trafic élevé l’amélioration potentielle des performances est
importante.

Dans le code suivant, le mot clé async, la valeur renvoyée Task , le mot clé await et la
méthode ToListAsync déclenchent l’exécution asynchrone du code.

C#

public async Task OnGetAsync()


{
Students = await _context.Students.ToListAsync();
}

Le mot clé async fait en sorte que le compilateur :


Génère des rappels pour les parties du corps de méthode.
Crée l’objet Task qui est retourné.
Le type de retour Task représente le travail en cours.
Le mot clé await indique au compilateur de fractionner la méthode en deux
parties. La première partie se termine par l’opération qui est démarrée de façon
asynchrone. La seconde partie est placée dans une méthode de rappel qui est
appelée quand l’opération se termine.
ToListAsync est la version asynchrone de la méthode d’extension ToList .

Voici quelques éléments à connaître lors de l’écriture de code asynchrone qui utilise EF
Core :

Seules les instructions qui provoquent l’envoi de requêtes ou de commandes vers


la base de données sont exécutées de façon asynchrone. Cela inclut ToListAsync ,
SingleOrDefaultAsync , FirstOrDefaultAsync et SaveChangesAsync , mais pas les

instructions qui ne font que changer un IQueryable , telles que var students =
context.Students.Where(s => s.LastName == "Davolio") .

Un contexte EF Core n’est pas thread-safe : n’essayez pas d’effectuer plusieurs


opérations en parallèle.
Pour tirer parti des avantages de performances du code asynchrone, vérifiez que
les packages de bibliothèque (par exemple pour la pagination) utilisent le mode
asynchrone s’ils appellent des méthodes EF Core qui envoient des requêtes à la
base de données.

Pour plus d’informations sur la programmation asynchrone dans .NET, consultez Vue
d’ensemble d’Async et Programmation asynchrone avec async et await.

2 Avertissement
L’implémentation asynchrone de Microsoft.Data.SqlClient présente certains
problèmes connus (#593 , #601 , etc.). Si vous rencontrez des problèmes de
performances inattendus, essayez plutôt d’utiliser l’exécution des commandes de
synchronisation, en particulier lorsque vous traitez de grandes valeurs de texte ou
binaires.

Considérations relatives aux performances


En général, une page web ne doit pas charger un nombre arbitraire de lignes.
Une requête doit utiliser la pagination ou une approche limitative. Par exemple, la
requête précédente peut utiliser Take pour limiter les lignes retournées :

C#

public async Task OnGetAsync()


{
Student = await _context.Students.Take(10).ToListAsync();
}

L’énumération d’une table volumineuse dans une vue peut renvoyer une réponse HTTP
200 partiellement construite si une exception de base de données se produit pendant
l’énumération.

La pagination est abordée plus loin dans le tutoriel.

Pour plus d’informations, consultez Considérations sur les performances (EF).

Étapes suivantes
Utiliser SQLite pour le développement, SQL Server pour la production

Tutoriel suivant

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus
d’informations, consultez notre  Ouvrir un problème de
guide du contributeur.
documentation

 Indiquer des commentaires sur


le produit
Partie 2, Pages Razor avec EF Core dans
ASP.NET Core - CRUD
Article • 30/11/2023

Par Tom Dykstra , Jeremy Likness et Jon P Smith

L’application web Contoso University montre comment créer des applications web
Pages Razor avec EF Core et Visual Studio. Pour obtenir des informations sur la série de
didacticiels, consultez le premier didacticiel.

Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez
l’application finale et comparez ce code à celui que vous avez créé en suivant le
tutoriel.

Dans ce didacticiel, nous allons examiner et personnaliser le code CRUD (créer, lire,
mettre à jour, supprimer) généré automatiquement.

Aucun référentiel
Certains développeurs utilisent une couche de service ou un modèle de référentiel pour
créer une couche d’abstraction entre l’interface utilisateur (Pages Razor) et la couche
d’accès aux données. Ce n’est pas le cas de ce tutoriel. Pour que ce tutoriel soit moins
complexe et traite exclusivement de EF Core, le code EF Core est directement ajouté aux
classes de modèle de page.

Mettre à jour la page Details


Le code généré automatiquement pour les pages Students n’inclut pas les données
d’inscription (« enrollment »). Dans cette section, les inscriptions sont ajoutées à la page
Details .

Lire les inscriptions


Pour afficher les données d’inscription d’un étudiant sur la page, elles doivent être lues.
Le code généré automatiquement dans Pages/Students/Details.cshtml.cs lit
uniquement les données Student , sans les données Enrollment :

C#
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}

Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

if (Student == null)
{
return NotFound();
}
return Page();
}

Remplacez la méthode OnGetAsync par le code suivant pour lire les données
d’inscription de l’étudiant sélectionné. Les modifications sont mises en surbrillance.

C#

public async Task<IActionResult> OnGetAsync(int? id)


{
if (id == null)
{
return NotFound();
}

Student = await _context.Students


.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

if (Student == null)
{
return NotFound();
}
return Page();
}

Les méthodes Include et ThenInclude forcent le contexte à charger la propriété de


navigation Student.Enrollments et, dans chaque inscription, la propriété de navigation
Enrollment.Course . Ces méthodes sont examinées en détail dans le tutoriel Lire les

données associées.

La méthode AsNoTracking améliore les performances dans les scénarios lorsque les
entités retournées ne sont pas mises à jour dans le contexte actuel. Le sujet
AsNoTracking est abordé plus loin dans ce didacticiel.

Afficher les inscriptions


Remplacez le code dans Pages/Students/Details.cshtml par le code suivant pour
afficher une liste d’inscriptions. Les modifications sont mises en surbrillance.

CSHTML

@page
@model ContosoUniversity.Pages.Students.DetailsModel

@{
ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
<h4>Student</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.Enrollments)
</dt>
<dd class="col-sm-10">
<table class="table">
<tr>
<th>Course Title</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Student.Enrollments)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Course.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>

Le code précédent effectue une itération sur les entités dans la propriété de navigation
Enrollments . Pour chaque inscription, il affiche le titre du cours et le niveau. Le titre du

cours est récupéré à partir de l’entité Course qui est stockée dans la propriété de
navigation Course de l’entité Inscriptions.

Exécutez l’application, sélectionnez l’onglet Students, puis cliquez sur le lien Details
pour un étudiant. La liste des cours et les notes de l’étudiant sélectionné s’affiche.

Méthodes pour lire une entité


Le code généré utilise FirstOrDefaultAsync pour lire une entité. Cette méthode retourne
la valeur Null si rien n’est trouvé ; sinon, elle retourne la première ligne trouvée qui
répond aux critères de filtre de requête. FirstOrDefaultAsync est généralement un
meilleur choix que les autres solutions suivantes :

SingleOrDefaultAsync – Lève une exception si plusieurs entités répondent au filtre


de requête. Pour déterminer si plusieurs lignes peuvent être retournées par la
requête, SingleOrDefaultAsync tente de récupérer plusieurs lignes. Ce travail
supplémentaire est inutile si la requête ne peut retourner qu’une seule entité,
comme quand elle effectue une recherche sur une clé unique.
FindAsync – Recherche une entité avec la clé primaire. Si une entité avec la clé
primaire est suivie par le contexte, elle est retournée sans qu’aucune requête soit
envoyée à la base de données. Cette méthode est optimisée pour la recherche
d’une seule entité, mais vous ne pouvez pas appeler Include avec FindAsync . Par
conséquent, si des données associées sont nécessaires, FirstOrDefaultAsync est le
meilleur choix.

Données de route/chaîne de requête


L’URL de la page Details est https://localhost:<port>/Students/Details?id=1 . La valeur
de clé primaire de l’entité se trouve dans la chaîne de requête. Certains développeurs
préfèrent passer la valeur de clé dans des données de route : https://localhost:
<port>/Students/Details/1 . Pour plus d'informations, consultez Mettre à jour le code

généré.

Mettre à jour la page Create


Le code OnPostAsync généré automatiquement pour la page Create est vulnérable aux
sur-publications. Remplacez la méthode OnPostAsync dans
Pages/Students/Create.cshtml.cs par le code suivant.

C#

public async Task<IActionResult> OnPostAsync()


{
var emptyStudent = new Student();

if (await TryUpdateModelAsync<Student>(
emptyStudent,
"student", // Prefix for form value.
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
_context.Students.Add(emptyStudent);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

return Page();
}

TryUpdateModelAsync
Le code précédent crée un objet Student, puis utilise des champs de formulaire publiés
pour mettre à jour les propriétés de l’objet Student. La méthode TryUpdateModelAsync :

Utilise les valeurs de formulaire publiées de la propriété PageContext dans le


PageModel.
Met à jour uniquement les propriétés listées ( s => s.FirstMidName, s =>
s.LastName, s => s.EnrollmentDate ).

Recherche les champs de formulaire dotés d’un préfixe « Student ». Par exemple :
Student.FirstMidName . Il ne respecte pas la casse.

Utilise le système de liaison de modèles pour convertir les valeurs de formulaire de


chaînes en types dans le modèle Student . Par exemple, EnrollmentDate est
converti en DateTime .

Exécutez l’application, puis créez une entité Student pour tester la page Create.

Sur-publication
L’utilisation de TryUpdateModel pour mettre à jour des champs avec des valeurs publiées
est une bonne pratique de sécurité, car cela empêche la sur-publication. Par exemple,
supposez que l’entité Student comprend une propriété Secret que cette page web ne
doit pas mettre à jour ou ajouter :

C#

public class Student


{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public string Secret { get; set; }
}

Même si l’application n’a pas de champ Secret dans la page Razor de création ou de
mise à jour, un pirate pourrait définir la valeur Secret par sur-publication. Un pirate
pourrait utiliser un outil tel que Fiddler, ou écrire du JavaScript, pour publier une valeur
de formulaire Secret . Le code d’origine ne limite pas les champs que le classeur de
modèles utilise quand il crée une instance de Student.

La valeur spécifiée par le pirate pour le champ de formulaire Secret , quelle qu’elle soit,
est mise à jour dans la base de données. L’illustration suivante montre l’outil Fiddler en
train d’ajouter le champ Secret , avec la valeur « OverPost », aux valeurs du formulaire
envoyé.
La valeur « OverPost » est ajoutée avec succès à la propriété Secret de la ligne insérée.
Cela se produit même si le concepteur de l’application n’avait jamais prévu que la
propriété Secret serait définie avec la page Create.

Afficher le modèle
Les modèles d’affichage fournissent une alternative pour empêcher la sur-publication.

Le modèle d’application est souvent appelé modèle de domaine. En règle générale, le


modèle de domaine contient toutes les propriétés requises par l’entité correspondante
dans la base de données. Le modèle d’affichage contient uniquement les propriétés
nécessaires pour la page d’interface utilisateur, par exemple, la page Créer.

En plus du modèle d’affichage, certaines applications utilisent un modèle de liaison ou


d’entrée pour transmettre des données entre la classe de modèles de pages de Pages
Razor et le navigateur.

Considérez le modèle d’affichage StudentVM suivant :

C#

public class StudentVM


{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}

Le code suivant utilise le modèle d’affichage StudentVM pour créer un étudiant :

C#

[BindProperty]
public StudentVM StudentVM { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

var entry = _context.Add(new Student());


entry.CurrentValues.SetValues(StudentVM);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

La méthode SetValues définit les valeurs de cet objet en lisant les valeurs d’un autre
objet PropertyValues. SetValues utilise la correspondance de nom de propriété. Type de
modèle d’affichage :

N’a pas besoin d’être lié au type de modèle.


Doit avoir des propriétés qui correspondent.

L’utilisation de StudentVM nécessite que la page Créer utilise StudentVM plutôt que
Student :

CSHTML

@page
@model CreateVMModel

@{
ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Student</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="StudentVM.LastName" class="control-label">
</label>
<input asp-for="StudentVM.LastName" class="form-control" />
<span asp-validation-for="StudentVM.LastName" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="StudentVM.FirstMidName" class="control-
label"></label>
<input asp-for="StudentVM.FirstMidName" class="form-control"
/>
<span asp-validation-for="StudentVM.FirstMidName"
class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StudentVM.EnrollmentDate" class="control-
label"></label>
<input asp-for="StudentVM.EnrollmentDate" class="form-
control" />
<span asp-validation-for="StudentVM.EnrollmentDate"
class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary"
/>
</div>
</form>
</div>
</div>

<div>
<a asp-page="Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Mettre à jour la page Edit


Dans Pages/Students/Edit.cshtml.cs , remplacez les méthodes OnGetAsync et
OnPostAsync par le code suivant.

C#

public async Task<IActionResult> OnGetAsync(int? id)


{
if (id == null)
{
return NotFound();
}

Student = await _context.Students.FindAsync(id);

if (Student == null)
{
return NotFound();
}
return Page();
}

public async Task<IActionResult> OnPostAsync(int id)


{
var studentToUpdate = await _context.Students.FindAsync(id);

if (studentToUpdate == null)
{
return NotFound();
}

if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"student",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

return Page();
}

Les modifications de code sont semblables à celles de la page Create, à quelques


exceptions près :

FirstOrDefaultAsync a été remplacé par FindAsync. Quand vous n’êtes pas tenu

d’inclure des données associées, FindAsync est plus efficace.


OnPostAsync contient un paramètre id .
Plutôt que de créer un étudiant vide, l’étudiant actuel est récupéré à partir de la
base de données.

Exécutez l’application et testez-la en créant et modifiant un étudiant.

États des entités


Le contexte de base de données effectue un suivi pour déterminer si les entités en
mémoire sont synchronisées avec les lignes correspondantes de la base de données. Ces
informations de suivi déterminent ce qui se passe quand SaveChangesAsync est appelé.
Par exemple, quand une nouvelle entité est passée à la méthode AddAsync, l’état de
cette entité prend la valeur Added. Quand SaveChangesAsync est appelé, le contexte de
base de données émet une commande SQL INSERT .

Une entité peut être dans l’un des états suivants :

Added : L’entité n’existe pas encore dans la base de données. La méthode


SaveChanges émet une instruction INSERT .

Unchanged : Aucune modification ne doit être enregistrée avec cette entité. Une

entité est dans cet état quand elle est lue à partir de la base de données.

Modified : Tout ou une partie des valeurs de propriété de l’entité ont été

modifiées. La méthode SaveChanges émet une instruction UPDATE .

Deleted : L’entité a été marquée pour suppression. La méthode SaveChanges émet

une instruction DELETE .

Detached : L’entité n’est pas suivie par le contexte de base de données.

Dans une application de bureau, les changements d’état sont généralement définis
automatiquement. Une entité est lue, des modifications sont apportées et l’état d’entité
passe automatiquement à Modified . L’appel de SaveChanges génère une instruction SQL
UPDATE qui met à jour uniquement les propriétés modifiées.

Dans une application web, le DbContext qui lit une entité et affiche les données est
supprimé après le rendu d’une page. Quand la méthode OnPostAsync d’une page est
appelée, une nouvelle requête web est faite avec une nouvelle instance du DbContext . La
relecture de l’entité dans ce nouveau contexte simule le traitement de bureau.

Mettre à jour la page Delete


Dans cette section, un message d’erreur personnalisé est implémenté quand l’appel à
SaveChanges échoue.

Remplacez le code de Pages/Students/Delete.cshtml.cs par le code suivant :

C#

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
private readonly ILogger<DeleteModel> _logger;

public DeleteModel(ContosoUniversity.Data.SchoolContext context,


ILogger<DeleteModel> logger)
{
_context = context;
_logger = logger;
}

[BindProperty]
public Student Student { get; set; }
public string ErrorMessage { get; set; }

public async Task<IActionResult> OnGetAsync(int? id, bool?


saveChangesError = false)
{
if (id == null)
{
return NotFound();
}

Student = await _context.Students


.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

if (Student == null)
{
return NotFound();
}

if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = String.Format("Delete {ID} failed. Try
again", id);
}

return Page();
}

public async Task<IActionResult> OnPostAsync(int? id)


{
if (id == null)
{
return NotFound();
}
var student = await _context.Students.FindAsync(id);

if (student == null)
{
return NotFound();
}

try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateException ex)
{
_logger.LogError(ex, ErrorMessage);

return RedirectToAction("./Delete",
new { id, saveChangesError = true });
}
}
}
}

Le code précédent :

Ajoute Journalisation.
Ajoute le paramètre facultatif saveChangesError à la signature de méthode
OnGetAsync . saveChangesError indique si la méthode a été appelée après un échec

de suppression de l’objet Student.

L’opération de suppression peut échouer en raison de problèmes réseau temporaires.


Vous avez plus de chances de rencontrer des erreurs réseau temporaires quand la base
de données est dans le cloud. Le paramètre saveChangesError a la valeur false quand
la page Delete OnGetAsync est appelée à partir de l’interface utilisateur. Quand
OnGetAsync est appelée par OnPostAsync (car l’opération de suppression a échoué), le

paramètre saveChangesError a la valeur true .

La méthode OnPostAsync récupère l’entité sélectionnée, puis appelle la méthode


Remove pour définir l’état de l’entité sur Deleted . Lorsque SaveChanges est appelée, une
commande SQL DELETE est générée. Si Remove échoue :

L’exception de la base de données est interceptée.


La méthode OnGetAsync des pages est appelée avec saveChangesError=true .

Ajoute un message d'erreur à Pages/Students/Delete.cshtml :


CSHTML

@page
@model ContosoUniversity.Pages.Students.DeleteModel

@{
ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>


<div>
<h4>Student</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
</dl>

<form method="post">
<input type="hidden" asp-for="Student.ID" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>

Exécutez l’application et supprimez un étudiant pour tester la page Delete.

Étapes suivantes
Tutoriel précédent Tutoriel suivant
6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 3, Razor Pages avec EF Core dans
ASP.NET Core - Tri, Filtrage, Pagination
Article • 30/11/2023

Par Tom Dykstra , Jeremy Likness et Jon P Smith

L’application web Contoso University montre comment créer des applications web
Pages Razor avec EF Core et Visual Studio. Pour obtenir des informations sur la série de
didacticiels, consultez le premier didacticiel.

Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez
l’application finale et comparez ce code à celui que vous avez créé en suivant le
tutoriel.

Ce tutoriel ajoute des fonctionnalités de tri, de filtrage et de pagination aux pages des
étudiants.

L’illustration suivante présente une page complète. Les en-têtes de colonne sont des
liens hypertexte permettant de trier la colonne. Cliquez de façon répétée sur un en-tête
de colonne pour changer l’ordre de tri (croissant ou décroissant).
Ajouter la fonctionnalité de tri
Remplacez le code de Pages/Students/Index.cshtml.cs par le code suivant pour ajouter
le tri.

C#

public class IndexModel : PageModel


{
private readonly SchoolContext _context;
public IndexModel(SchoolContext context)
{
_context = context;
}

public string NameSort { get; set; }


public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }

public IList<Student> Students { get; set; }


public async Task OnGetAsync(string sortOrder)
{
// using System;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";

IQueryable<Student> studentsIQ = from s in _context.Students


select s;

switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s =>
s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}

Students = await studentsIQ.AsNoTracking().ToListAsync();


}
}

Le code précédent :

Nécessite l’ajout de using System; .


Ajoute des propriétés devant contenir les paramètres de tri.
Remplace le nom de la propriété Student par Students .
Remplace le code de la méthode OnGetAsync .

La méthode OnGetAsync reçoit un paramètre sortOrder à partir de la chaîne de requête


dans l’URL. L’URL et la chaîne de requête sont générées par le Tag Helper d’ancre.

Le paramètre sortOrder est Name ou Date . Le paramètre sortOrder peut être suivi de
_desc pour spécifier l’ordre décroissant. L’ordre de tri par défaut est croissant.

Quand la page Index est demandée à partir du lien Students, il n’existe aucune chaîne
de requête. Les étudiants sont affichés par nom de famille dans l’ordre croissant. L’ordre
croissant par nom est le default dans l’instruction switch . Quand l’utilisateur clique sur
un lien d’en-tête de colonne, la valeur sortOrder appropriée est fournie dans la valeur
de chaîne de requête.
NameSort et DateSort sont utilisés par la page Razor pour configurer les liens hypertexte

d’en-tête de colonne avec les valeurs de chaîne de requête appropriées :

C#

NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";


DateSort = sortOrder == "Date" ? "date_desc" : "Date";

Le code utilise l’opérateur conditionnel C# ?:. L’opérateur ?: est un opérateur ternaire ;


il prend trois opérandes. La première ligne indique que quand sortOrder est null ou
vide, NameSort prend la valeur name_desc . Si sortOrder n’est pas null ou vide, NameSort
prend pour valeur une chaîne vide.

Ces deux instructions permettent à la page de définir les liens hypertexte d’en-tête de
colonne comme suit :

Ordre de tri actuel Lien hypertexte Nom de famille Lien hypertexte Date

Nom de famille croissant descending ascending

Nom de famille décroissant ascending ascending

Date croissante ascending descending

Date décroissante ascending ascending

La méthode utilise LINQ to Entities pour spécifier la colonne d’après laquelle effectuer le
tri. Le code initialise un IQueryable<Student> avant l’instruction switch, et le modifie
dans l’instruction switch :

C#

IQueryable<Student> studentsIQ = from s in _context.Students


select s;

switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}

Students = await studentsIQ.AsNoTracking().ToListAsync();

Quand un IQueryable est créé ou modifié, aucune requête n’est envoyée à la base de
données. La requête n’est pas exécutée tant que l’objet IQueryable n’a pas été converti
en collection. Les IQueryable sont convertis en collection en appelant une méthode telle
que ToListAsync . Ainsi, le code IQueryable génère une requête unique qui n’est pas
exécutée avant l’instruction suivante :

C#

Students = await studentsIQ.AsNoTracking().ToListAsync();

OnGetAsync peut contenir un grand nombre de colonnes triables. Pour connaître les
autres méthodes permettant de coder cette fonctionnalité, consultez Utiliser du code
dynamique LINQ pour simplifier le code dans la version MVC de cette série de tutoriels.

Ajouter des liens hypertexte d’en-tête de colonne à la


page d’index des étudiants
Remplacez le code dans Students/Index.cshtml par le code suivant. Les modifications
sont mises en surbrillance.

CSHTML

@page
@model ContosoUniversity.Pages.Students.IndexModel

@{
ViewData["Title"] = "Students";
}

<h2>Students</h2>
<p>
<a asp-page="Create">Create New</a>
</p>

<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model =>
model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model =>
model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model =>
model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-
id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-
id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Le code précédent :

Ajoute des liens hypertexte aux en-têtes de colonne LastName et EnrollmentDate .


Utilise les informations contenues dans NameSort et DateSort pour définir des liens
hypertexte avec les valeurs d’ordre de tri actuelles.
Remplace l’en-tête Index de la page par l’en-tête Students.
Remplace Model.Student par Model.Students .

Pour vérifier que le tri fonctionne


Exécutez l’application et sélectionnez l’onglet Students.
Cliquez sur les en-têtes de colonne.

Ajouter la fonctionnalité de filtrage


Pour ajouter le filtrage à la page d’index des étudiants :

Une zone de texte et un bouton d’envoi sont ajoutés à la page Razor. La zone de
texte fournit une chaîne de recherche sur le prénom ou le nom de famille.
Le modèle de page est mis à jour pour utiliser la valeur de zone de texte.

Mettre à jour la méthode OnGetAsync


Remplacez le code de Students/Index.cshtml.cs par le code suivant pour ajouter le
filtrage :

C#

public class IndexModel : PageModel


{
private readonly SchoolContext _context;

public IndexModel(SchoolContext context)


{
_context = context;
}

public string NameSort { get; set; }


public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }

public IList<Student> Students { get; set; }

public async Task OnGetAsync(string sortOrder, string searchString)


{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";

CurrentFilter = searchString;

IQueryable<Student> studentsIQ = from s in _context.Students


select s;
if (!String.IsNullOrEmpty(searchString))
{
studentsIQ = studentsIQ.Where(s =>
s.LastName.Contains(searchString)
||
s.FirstMidName.Contains(searchString));
}

switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s =>
s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}

Students = await studentsIQ.AsNoTracking().ToListAsync();


}
}

Le code précédent :

Ajoute le paramètre searchString à la méthode OnGetAsync , et enregistre la valeur


de paramètre dans la propriété CurrentFilter . La valeur de chaîne de recherche
est reçue à partir d’une zone de texte qui est ajoutée dans la section suivante.
Ajoute une clause Where à l’instruction LINQ. La clause Where sélectionne
uniquement les étudiants dont le prénom ou le nom de famille contient la chaîne
de recherche. L’instruction LINQ est exécutée uniquement s’il y a une valeur à
rechercher.

IQueryable et IEnumerable
Le code appelle la méthode Where de l’objet IQueryable , et le filtre est traité sur le
serveur. Dans certains scénarios, l’application peut appeler la méthode Where en tant
que méthode d’extension sur une collection en mémoire. Par exemple, supposez que
_context.Students passe de EF Core DbSet à une méthode de référentiel qui retourne

une collection IEnumerable . Le résultat serait normalement le même, mais dans certains
cas il peut être différent.
Par exemple, l’implémentation .NET Framework de Contains effectue par défaut une
comparaison respectant la casse. Dans SQL Server, le respect de la casse de Contains
est déterminé par le paramètre de classement de l’instance de SQL Server. Par défaut,
SQL Server ne respecte pas la casse. Par défaut, SQLite est sensible à la casse. ToUpper
peut être appelée pour que le test ne respecte pas la casse de manière explicite :

C#

Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())`

Le code précédent garantit que le filtre n’est pas sensible à la casse, même si la
méthode Where est appelée sur un IEnumerable ou s’exécute sur SQLite.

Quand Contains est appelée sur une collection IEnumerable , l’implémentation .NET
Core est utilisée. Quand Contains est appelée sur un objet IQueryable , l’implémentation
de base de données est utilisée.

Pour des raisons de performances, il est généralement préférable d’appeler Contains sur
un IQueryable . Avec IQueryable , le filtrage est effectué par le serveur de base de
données. Si un IEnumerable est créé en premier, toutes les lignes doivent être
retournées à partir du serveur de base de données.

Il existe un coût en matière de performances en cas d’appel à ToUpper . Le code ToUpper


ajoute une fonction dans la clause WHERE de l’instruction TSQL SELECT. La fonction
ajoutée empêche l’optimiseur d’utiliser un index. Étant donné que SQL est installé sans
respect de la casse, il est préférable d’éviter l’appel à ToUpper quand il n’est pas
nécessaire.

Pour plus d’informations, consultez How to use case-insensitive query with Sqlite
provider .

Mettre à jour la page Razor


Remplacez le code dans Pages/Students/Index.cshtml pour ajouter un bouton
Rechercher.

CSHTML

@page
@model ContosoUniversity.Pages.Students.IndexModel

@{
ViewData["Title"] = "Students";
}

<h2>Students</h2>

<p>
<a asp-page="Create">Create New</a>
</p>

<form asp-page="./Index" method="get">


<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString"
value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-primary" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>

<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model =>
model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model =>
model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model =>
model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-
id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-
id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Le code précédent utilise le Tag Helper <form> pour ajouter le bouton et la zone de texte
de recherche. Par défaut, le Tag Helper <form> envoie les données de formulaire avec un
POST. Avec POST, les paramètres sont passés dans le corps du message HTTP et non
dans l’URL. Quand HTTP GET est utilisé, les données du formulaire sont transmises dans
l’URL sous forme de chaînes de requête. La transmission des données avec des chaînes
de requête permet aux utilisateurs d’ajouter l’URL aux favoris. Les recommandations du
W3C stipulent que GET doit être utilisé quand l’action ne produit pas de mise à jour.

Testez l’application :

Sélectionnez l’onglet Students et entrez une chaîne de recherche. Si vous utilisez


SQLite, le filtre n’est pas sensible à la casse seulement si vous avez implémenté le
code ToUpper facultatif indiqué plus haut.

Sélectionnez Recherche.

Notez que l’URL contient la chaîne de recherche. Par exemple :

browser-address-bar

https://localhost:5001/Students?SearchString=an

Si la page est dans les favoris, le favori contient l’URL de la page et la chaîne de requête
SearchString . method="get" dans la balise form est ce qui a provoqué la génération de

la chaîne de requête.

Actuellement, quand un lien de tri d’en-tête de colonne est sélectionné, la valeur du


filtre de la zone Search est perdue. La valeur de filtre perdue est corrigée dans la section
suivante.

Ajouter la fonctionnalité de pagination


Dans cette section, nous allons créer une classe PaginatedList pour prendre en charge
la pagination. La classe PaginatedList utilise des instructions Skip et Take pour filtrer
les données sur le serveur au lieu de récupérer toutes les lignes de la table. L’illustration
suivante montre les boutons de pagination.

Créer la classe PaginatedList


Dans le dossier du projet, créez PaginatedList.cs avec le code suivant :

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }

public PaginatedList(List<T> items, int count, int pageIndex, int


pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);

this.AddRange(items);
}

public bool HasPreviousPage => PageIndex > 1;

public bool HasNextPage => PageIndex < TotalPages;

public static async Task<PaginatedList<T>> CreateAsync(


IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip(
(pageIndex - 1) * pageSize)
.Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}

La méthode CreateAsync dans le code précédent prend la taille de page et le numéro de


page, et applique les instructions Skip et Take appropriées au IQueryable . Quand
ToListAsync est appelée sur le IQueryable , elle retourne une liste contenant

uniquement la page demandée. Les propriétés HasPreviousPage et HasNextPage sont


utilisées pour activer ou désactiver les boutons de pagination Previous et Next.

La méthode CreateAsync est utilisée pour créer le PaginatedList<T> . Un constructeur ne


peut pas créer l’objet PaginatedList<T> , car les constructeurs ne peuvent pas exécuter
du code asynchrone.

Ajouter la taille de page à la configuration


Ajoutez PageSize au fichier de appsettings.json Configuration :

JSON

{
"PageSize": 3,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-
1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

Ajouter la pagination à IndexModel


Remplacez le code dans Students/Index.cshtml.cs pour ajouter la pagination.

C#

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
private readonly IConfiguration Configuration;

public IndexModel(SchoolContext context, IConfiguration


configuration)
{
_context = context;
Configuration = configuration;
}

public string NameSort { get; set; }


public string DateSort { get; set; }
public string CurrentFilter { get; set; }
public string CurrentSort { get; set; }
public PaginatedList<Student> Students { get; set; }

public async Task OnGetAsync(string sortOrder,


string currentFilter, string searchString, int? pageIndex)
{
CurrentSort = sortOrder;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}

CurrentFilter = searchString;

IQueryable<Student> studentsIQ = from s in _context.Students


select s;
if (!String.IsNullOrEmpty(searchString))
{
studentsIQ = studentsIQ.Where(s =>
s.LastName.Contains(searchString)
||
s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s =>
s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s =>
s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}

var pageSize = Configuration.GetValue("PageSize", 4);


Students = await PaginatedList<Student>.CreateAsync(
studentsIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
}
}
}

Le code précédent :
Remplace le type IList<Student> de la propriété Students par le type
PaginatedList<Student> .

Ajoute l’index de page, le sortOrder actuel et le currentFilter à la signature de


méthode OnGetAsync .
Enregistre l’ordre de tri dans la propriété CurrentSort .
Rétablit la valeur 1 pour l’index de la page lorsqu’il existe une nouvelle chaîne de
recherche.
Utilise la classe PaginatedList pour accéder aux entités d’étudiants.
Définit pageSize sur 3 à partir de Configuration, 4 si la configuration échoue.

Tous les paramètres reçus par OnGetAsync sont Null si :

La page est appelée à partir du lien Students.


L’utilisateur n’a pas cliqué sur un lien de pagination ou de tri.

Quand l’utilisateur clique sur un lien de pagination, la variable d’index de page contient
le numéro de page à afficher.

La propriété CurrentSort fournit l’ordre de tri actuel à la page Razor. L’ordre de tri
actuel doit être inclus dans les liens de pagination afin de conserver l’ordre de tri lors de
la pagination.

La propriété CurrentFilter fournit la chaîne de filtrage actuelle à la page Razor. La


valeur CurrentFilter :

Doit être incluse dans les liens de pagination afin de conserver les paramètres de
filtre lors de la pagination.
Doit être restaurée à la zone de texte quand la page est réaffichée.

Si la chaîne de recherche est modifiée pendant la pagination, la page est réinitialisée à 1.


La page doit être réinitialisée à 1, car le nouveau filtre peut entraîner l’affichage de
données différentes. Quand une valeur de recherche est entrée et que le bouton Submit
est sélectionné :

La chaîne de recherche est changée.


Le paramètre searchString n’est pas null.

La méthode PaginatedList.CreateAsync convertit la requête d’étudiant en une seule


page d’étudiants dans un type de collection qui prend en charge la pagination. Cette
page unique d’étudiants est passée à la page Razor.

Les deux points d’interrogation situés après pageIndex dans l’appel


PaginatedList.CreateAsync représentent l’opérateur de fusion avec valeur Null.
L’opérateur de fusion de Null définit une valeur par défaut pour un type nullable.
L’expression pageIndex ?? 1 retourne la valeur pageIndex si elle a une valeur ; sinon, elle
retourne 1.

Ajouter des liens de pagination


Remplacez le code dans Students/Index.cshtml par le code suivant. Les modifications
apparaissent en surbrillance :

CSHTML

@page
@model ContosoUniversity.Pages.Students.IndexModel

@{
ViewData["Title"] = "Students";
}

<h2>Students</h2>

<p>
<a asp-page="Create">Create New</a>
</p>

<form asp-page="./Index" method="get">


<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString"
value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-primary" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>

<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model =>
model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model =>
model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model =>
model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-
id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-
id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

@{
var prevDisabled = !Model.Students.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.Students.HasNextPage ? "disabled" : "";
}

<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @nextDisabled">
Next
</a>
Les liens d’en-tête de colonne utilisent la chaîne de requête pour passer la chaîne de
recherche actuelle à la méthode OnGetAsync :

CSHTML

<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"


asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>

Les boutons de changement de page sont affichés par des Tag Helpers :

CSHTML

<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @nextDisabled">
Next
</a>

Exécutez l’application et accédez à la page des étudiants.

Pour vérifier que la pagination fonctionne, cliquez sur les liens de pagination dans
différents ordres de tri.
Pour vérifier que la pagination fonctionne correctement avec le tri et le filtrage,
entrez une chaîne de recherche et essayez de changer de page.
Regroupement
Cette section crée la page About (À propos) qui indique le nombre d’étudiants inscrits
pour chaque date d’inscription. La mise à jour utilise le regroupement et comprend les
étapes suivantes :

Créer un modèle de vue pour les données utilisées par la page About .
Mettre à jour la page About pour utiliser le modèle de vue.

Créer le modèle d’affichage


Créez un dossier Models/SchoolViewModels.

Créez SchoolViewModels/EnrollmentDateGroup.cs avec le code suivant :

C#

using System;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }

public int StudentCount { get; set; }


}
}

Créer la page Razor


Créez un fichier Pages/About.cshtml avec le code suivant :

CSHTML

@page
@model ContosoUniversity.Pages.AboutModel

@{
ViewData["Title"] = "Student Body Statistics";
}

<h2>Student Body Statistics</h2>

<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>

@foreach (var item in Model.Students)


{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>
Créer le modèle de page
Mettez à jour le fichier Pages/About.cshtml.cs avec le code suivant :

C#

using ContosoUniversity.Models.SchoolViewModels;
using ContosoUniversity.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ContosoUniversity.Models;

namespace ContosoUniversity.Pages
{
public class AboutModel : PageModel
{
private readonly SchoolContext _context;

public AboutModel(SchoolContext context)


{
_context = context;
}

public IList<EnrollmentDateGroup> Students { get; set; }

public async Task OnGetAsync()


{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};

Students = await data.AsNoTracking().ToListAsync();


}
}
}

L’instruction LINQ regroupe les entités Student par date d’inscription, calcule le nombre
d’entités dans chaque groupe et stocke les résultats dans une collection d’objets de
modèle de vue EnrollmentDateGroup .

Exécutez l’application et accédez à la page About. Le nombre d’étudiants pour chaque


date d’inscription s’affiche dans une table.
Étapes suivantes
Dans le didacticiel suivant, l’application utilise des migrations pour mettre à jour le
modèle de données.

Tutoriel précédent Tutoriel suivant

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 4, Razor Pages avec migrations EF
Core dans ASP.NET Core
Article • 30/11/2023

Par Tom Dykstra , Jon P Smith et Rick Anderson

L’application web Contoso University montre comment créer des applications web Razor
Pages avec EF Core et Visual Studio. Pour obtenir des informations sur la série de
didacticiels, consultez le premier didacticiel.

Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez
l’application finale et comparez ce code à celui que vous avez créé en suivant le
tutoriel.

Ce tutoriel présente la fonctionnalité de migrations EF Core pour gérer les modifications


du modèle de données.

Quand une nouvelle application est développée, le modèle de données change


fréquemment. Chaque fois que le modèle change, il est désynchronisé avec la base de
données. Cette série de tutoriels a commencé par la configuration d’Entity Framework
pour créer la base de données si elle n’existait pas. Chaque fois que le modèle de
données change, la base de données doit être supprimée. À l’exécution suivante de
l’application, l’appel à EnsureCreated a pour effet de recréer la base de données en
fonction du nouveau modèle de données. La classe DbInitializer s’exécute ensuite
pour amorcer la nouvelle base de données.

Cette approche pour conserver la synchronisation de la base de données avec le modèle


de données fonctionne bien jusqu’à ce que l’application doive être déployée en
production. Quand l’application s’exécute en production, elle stocke généralement des
données qui doivent être tenues à jour. L’application ne peut pas commencer avec une
base de données de test chaque fois qu’une modification est apportée (par exemple en
cas d’ajout d’une nouvelle colonne). La fonctionnalité de migrations EF Core résout ce
problème en permettant à EF Core de mettre à jour le schéma de base de données au
lieu de créer une nouvelle base de données.

Au lieu de supprimer et de recréer la base de données quand le modèle de données


change, les migrations mettent à jour le schéma et conservent les données existantes.

7 Notes

Limitations de SQLite
Ce tutoriel utilise la fonctionnalité Migrations d’Entity Framework Core lorsque cela
est possible. Les migrations mettent à jour le schéma de la base de données pour
qu’elle corresponde aux modifications dans le modèle de données. Toutefois, les
migrations effectuent uniquement les types de modifications qui sont pris en
charge par le moteur de base de données. En outre, les fonctionnalités de
modification du schéma SQLite sont limitées. Par exemple, l’ajout d’une colonne est
pris en charge, mais pas sa suppression. Si vous créez une migration pour
supprimer une colonne, la commande ef migrations add réussit mais la
commande ef database update échoue.

Pour remédier aux limitations de SQLite, vous devez écrire le code de migrations
manuellement pour regénérer le tableau lorsqu’un élément est modifié. Ce code se
place dans les méthodes Up et Down pour une migration et implique les tâches
suivantes :

La création d’un nouveau tableau.


La copie de données de l’ancien tableau vers le nouveau.
La suppression de l’ancien tableau.
Renommer la nouvelle table.

L’écriture d’un tel code propre à une base de données n’est pas abordée dans ce
tutoriel. En effet, ce tutoriel supprime et recrée la base de données chaque fois
qu’une tentative d’application d’une migration échoue. Pour plus d’informations,
consultez les ressources suivantes :

Limites d’un fournisseur de base de données EF Core SQLite


Personnaliser le code de migration
Amorçage des données
Instruction SQLite ALTER TABLE

Supprimer la base de données


Visual Studio

Utilisez l’Explorateur d’objets SQL Server (SSOX) pour supprimer la base de


données ou exécutez la commande suivante dans la console du Gestionnaire de
package (PMC) :

PowerShell
Drop-Database

Créer une migration initiale


Visual Studio

Exécutez les commandes suivantes dans PMC :

PowerShell

Add-Migration InitialCreate
Update-Database

Supprimer EnsureCreated
Cette série de tutoriels a commencé en utilisant EnsureCreated. La méthode
EnsureCreated ne crée pas de table d’historique des migrations et ne peut donc pas être

utilisée avec les migrations. Elle est destinée à effectuer des tests et un prototypage
rapide, où la base de données est supprimée et recréée fréquemment.

À partir de là, les tutoriels utilisent des migrations.

Dans Program.cs , supprimez la ligne suivante :

C#

context.Database.EnsureCreated();

Exécutez l’application et vérifiez que la base de données est amorcée.

Méthodes Up et Down
La commande EF Core migrations add a généré du code pour créer la base de données.
Ce code de migration se trouve dans le fichier Migrations\
<timestamp>_InitialCreate.cs . La méthode Up de la classe InitialCreate crée les tables

de base de données qui correspondent aux jeux d’entités du modèle de données. La


méthode Down les supprime, comme indiqué dans l’exemple suivant :
C#

using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;

namespace ContosoUniversity.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Course",
columns: table => new
{
CourseID = table.Column<int>(nullable: false),
Title = table.Column<string>(nullable: true),
Credits = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Course", x => x.CourseID);
});

migrationBuilder.CreateTable(
name: "Student",
columns: table => new
{
ID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
LastName = table.Column<string>(nullable: true),
FirstMidName = table.Column<string>(nullable: true),
EnrollmentDate = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Student", x => x.ID);
});

migrationBuilder.CreateTable(
name: "Enrollment",
columns: table => new
{
EnrollmentID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
CourseID = table.Column<int>(nullable: false),
StudentID = table.Column<int>(nullable: false),
Grade = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Enrollment", x => x.EnrollmentID);
table.ForeignKey(
name: "FK_Enrollment_Course_CourseID",
column: x => x.CourseID,
principalTable: "Course",
principalColumn: "CourseID",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Enrollment_Student_StudentID",
column: x => x.StudentID,
principalTable: "Student",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
});

migrationBuilder.CreateIndex(
name: "IX_Enrollment_CourseID",
table: "Enrollment",
column: "CourseID");

migrationBuilder.CreateIndex(
name: "IX_Enrollment_StudentID",
table: "Enrollment",
column: "StudentID");
}

protected override void Down(MigrationBuilder migrationBuilder)


{
migrationBuilder.DropTable(
name: "Enrollment");

migrationBuilder.DropTable(
name: "Course");

migrationBuilder.DropTable(
name: "Student");
}
}
}

Le code précédent concerne la migration initiale. Le code :

A été généré par la commande migrations add InitialCreate .


Est exécuté par la commande database update .
Crée une base de données pour le modèle de données spécifié par la classe du
contexte de base de données.

Le paramètre de nom de migration ( InitialCreate dans l’exemple) est utilisé comme


nom de fichier. Le nom de la migration peut être n’importe quel nom de fichier valide.
Nous vous conseillons néanmoins de choisir un mot ou une expression qui résume ce
qui est effectué dans la migration. Par exemple, une migration ajoutant une table de
département pourrait se nommer « TableAjoutDépartement ».

Table d’historique des migrations


Utilisez SSOX ou l’outil SQLite pour inspecter la base de données.
Notez l’ajout d’une table __EFMigrationsHistory . La table __EFMigrationsHistory
effectue le suivi des migrations qui ont été appliquées à la base de données.
Examinez les données contenues dans la table __EFMigrationsHistory . Elle
présente une ligne pour la première migration.

Capture instantanée du modèle de données


Migrations crée une capture instantanée du modèle de données actuel dans
Migrations/SchoolContextModelSnapshot.cs . Quand une migration est ajoutée, EF

détermine ce qui a changé en comparant le modèle de données actif au fichier de


capture instantanée.

Comme le fichier de capture instantané suit l’état du modèle de données, il n’est pas
possible de supprimer une migration en supprimant le fichier
<timestamp>_<migrationname>.cs . Pour annuler la migration la plus récente, utilisez la

commande migrations remove. migrations remove supprime la migration et garantit


que la capture instantanée est correctement réinitialisée. Pour plus d’informations,
consultez dotnet ef migrations remove.

Consultez Réinitialiser toutes les migrations pour supprimer toutes les migrations.

Application de migrations en production


Nous déconseillons l’appel de Database.Migrate dans les applications de production
pendant leur démarrage. Migrate ne doit pas être appelé à partir d’une application
déployée sur une batterie de serveurs. Si un scale-out de plusieurs instances de serveur
a lieu sur l’application, il est difficile de vérifier que les mises à jour du schéma de base
de données ne se produisent pas à partir de plusieurs serveurs ou qu’elles ne sont pas
en conflit avec un accès en lecture/écriture.

La migration de base de données doit être effectuée dans le cadre du déploiement et de


manière contrôlée. Parmi les approches de migration de base de données de
production, citons :
L’utilisation de migrations pour créer des scripts SQL et l’utilisation de scripts SQL
dans le déploiement
L’exécution de dotnet ef database update à partir d’un environnement contrôlé

Résolution des problèmes


Si l’application utilise la Base de données locale SQL Server et affiche l’exception
suivante :

text

SqlException: Cannot open database "ContosoUniversity" requested by the


login.
The login failed.
Login failed for user 'user name'.

La solution peut consister à exécuter dotnet ef database update à partir d’une invite de
commandes.

Ressources supplémentaires
EF Core CLI.
commandes CLI dotnet ef migrations
Console du Gestionnaire de package (Visual Studio)

Étapes suivantes
Le tutoriel suivant crée le modèle de données en ajoutant des propriétés d’entité et de
nouvelles entités.

Tutoriel précédent Tutoriel suivant

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre  Indiquer des commentaires sur
guide du contributeur.
le produit
Partie 5, Razor Pages avec EF Core dans
ASP.NET Core - Modèle de données
Article • 30/11/2023

Par Tom Dykstra , Jeremy Likness et Jon P Smith

L’application web Contoso University montre comment créer des applications web
Pages Razor avec EF Core et Visual Studio. Pour obtenir des informations sur la série de
didacticiels, consultez le premier didacticiel.

Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez
l’application finale et comparez ce code à celui que vous avez créé en suivant le
tutoriel.

Dans les didacticiels précédents, nous avons travaillé avec un modèle de données de
base composé de trois entités. Dans ce tutoriel, vous allez :

Nous allons ajouter d’autres entités et relations


Nous allons personnaliser le modèle de données en spécifiant des règles de mise
en forme, de validation et de mappage de base de données.

Le modèle de données final est présenté dans l’illustration suivante :


Le diagramme de base de données suivant a été créé avec Dataedo :
Pour créer un diagramme de base de données avec Dataedo :

Déploiement de l’application dans Azure


Téléchargez et installez Dataedo sur votre ordinateur.
Suivez les instructions Générer la documentation pour Azure SQL Database en
5 minutes

Dans le diagramme Dataedo précédent, le CourseInstructor est une table de jointure


créée par Entity Framework. Pour plus d’informations, consultez Plusieurs-à-plusieurs.

L’entité Student
Remplacez le code de Models/Student.cs par le code suivant :

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than
50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}

public ICollection<Enrollment> Enrollments { get; set; }


}
}

Le code précédent ajoute une propriété FullName et les attributs suivants aux propriétés
existantes :

[DataType]
[DisplayFormat]
[StringLength]
[Column]
[Required]
[Display]

Propriété calculée FullName


FullName est une propriété calculée qui retourne une valeur créée par concaténation de

deux autres propriétés. FullName ne pouvant pas être définie, elle n’a qu’un seul
accesseur get. Aucune colonne FullName n’est créée dans la base de données.

Attribut DataType
C#
[DataType(DataType.Date)]

Pour les dates d’inscription des étudiants, toutes les pages affichent actuellement
l’heure du jour avec la date, alors que seule la date présente un intérêt. Vous pouvez
avoir recours aux attributs d’annotation de données pour apporter une modification au
code, permettant de corriger le format d’affichage dans chaque page qui affiche ces
données.

L’attribut DataType spécifie un type de données qui est plus spécifique que le type
intrinsèque de la base de données. Ici, seule la date doit être affichée (pas la date et
l’heure). L’énumération DataType fournit de nombreux types de données, tels que Date,
Heure, Numéro de téléphone, Devise, Adresse e-mail, etc. L’attribut DataType peut
également permettre à l’application de fournir automatiquement des fonctionnalités
spécifiques au type. Par exemple :

Le lien mailto: est créé automatiquement pour DataType.EmailAddress .


Le sélecteur de date est fourni pour DataType.Date dans la plupart des navigateurs.

L’attribut DataType émet des attributs HTML 5 data- (prononcé data dash en anglais).
Les attributs DataType ne fournissent aucune validation.

Attribut DisplayFormat
C#

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode =


true)]

DataType.Date ne spécifie pas le format de la date qui s’affiche. Par défaut, le champ de

date est affiché conformément aux formats par défaut basés sur l’objet CultureInfo du
serveur.

L’attribut DisplayFormat sert à spécifier explicitement le format de date. Le paramètre


ApplyFormatInEditMode spécifie que la mise en forme doit également être appliquée à

l’interface utilisateur de modification. Certains champs ne doivent pas utiliser


ApplyFormatInEditMode . Par exemple, le symbole monétaire ne doit généralement pas

être affiché dans une zone de texte d’édition.

L’attribut DisplayFormat peut être utilisé seul. Il est généralement préférable d’utiliser
l’attribut DataType avec l’attribut DisplayFormat . L’attribut DataType transmet la
sémantique des données, plutôt que la manière de l’afficher à l’écran. L’attribut
DataType offre les avantages suivants qui ne sont pas disponibles dans DisplayFormat :

Le navigateur peut activer des fonctionnalités HTML5 (par exemple, pour afficher
un contrôle de calendrier, le symbole monétaire correspondant aux paramètres
régionaux, des liens de messagerie, une validation d’entrées côté client).
Par défaut, le navigateur affiche les données à l’aide du format correspondant aux
paramètres régionaux.

Pour plus d’informations, consultez la documentation relative au Tag Helper <input>.

Attribut StringLength
C#

[StringLength(50, ErrorMessage = "First name cannot be longer than 50


characters.")]

Vous pouvez également spécifier des règles de validation de données et des messages
d’erreur de validation à l’aide d’attributs. L’attribut StringLength spécifie les longueurs
minimale et maximale de caractères autorisées dans un champ de données. Le code
présenté limite la longueur des noms à 50 caractères. Un exemple qui définit la longueur
de chaîne minimale est présenté plus loin.

L’attribut StringLength fournit également la validation côté client et côté serveur. La


valeur minimale n’a aucun impact sur le schéma de base de données.

L’attribut StringLength n’empêche pas un utilisateur d’entrer un espace blanc comme


nom. L’attribut RegularExpression peut être utilisé pour appliquer des restrictions à
l’entrée. Par exemple, le code suivant exige que le premier caractère soit en majuscule et
que les autres caractères soient alphabétiques :

C#

[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]

Visual Studio

Dans l’Explorateur d’objets SQL Server (SSOX), ouvrez le concepteur de tables


Student en double-cliquant sur la table Student.
L’image précédente montre le schéma pour la table Student . Les champs de nom
sont de type nvarchar(MAX) . Quand une migration est créée et appliquée plus loin
dans ce tutoriel, les champs de nom deviennent nvarchar(50) en raison des
attributs de longueur de chaîne.

Attribut Column
C#

[Column("FirstName")]
public string FirstMidName { get; set; }

Les attributs peuvent contrôler comment les classes et les propriétés sont mappées à la
base de données. Dans le modèle Student , l’attribut Column sert à mapper le nom de la
propriété FirstMidName à « FirstName » dans la base de données.

Pendant la création de la base de données, les noms de propriétés du modèle sont


utilisés comme noms de colonnes (sauf quand l’attribut Column est utilisé). Le modèle
Student utilise FirstMidName pour le champ de prénom, car le champ peut également

contenir un deuxième prénom.

Avec l’attribut [Column] , dans le modèle de données, Student.FirstMidName est mappé à


la colonne FirstName de la table Student . L’ajout de l’attribut Column change le modèle
sur lequel repose SchoolContext . Le modèle sur lequel repose le SchoolContext ne
correspond plus à la base de données. Cette différence sera résolue en ajoutant une
migration plus loin dans ce tutoriel.

Attribut Required
C#

[Required]

L’attribut Required fait des propriétés de nom des champs obligatoires. L’attribut
Required n’est pas nécessaire pour les types qui n’autorisent pas les valeurs Null comme

les types valeur (par exemple, DateTime , int et double ). Les types qui n’acceptent pas
les valeurs Null sont traités automatiquement comme des champs obligatoires.

L'attribut Required doit être utilisé avec MinimumLength pour appliquer MinimumLength .

C#

[Display(Name = "Last Name")]


[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }

MinimumLength et Required autorisent un espace blanc pour satisfaire la validation.

Utilisez l'attribut RegularExpression pour un contrôle total sur la chaîne.

Attribut Display
C#

[Display(Name = "Last Name")]

L’attribut Display spécifie que la légende pour les zones de texte doit être « First
Name », « Last Name », « Full Name » et « Enrollment Date ». Par défaut, les légendes
n’avaient pas d’espace pour séparer les mots, par exemple « Lastname ».

Créer une migration


Exécutez l’application et accédez à la page des étudiants. Une exception est levée. En
raison de l’attribut [Column] , EF s’attend à trouver une colonne nommée FirstName ,
mais le nom de la colonne dans la base de données est toujours FirstMidName .
Visual Studio

Le message d’erreur est semblable à l’exemple suivant :

SqlException: Invalid column name 'FirstName'.


There are pending model changes
Pending model changes are detected in the following:

SchoolContext

Dans PMC, entrez les commandes suivantes pour créer une migration et
mettre à jour la base de données :

PowerShell

Add-Migration ColumnFirstName
Update-Database

La première de ces commandes génère le message d’avertissement suivant :

text

An operation was scaffolded that may result in the loss of data.


Please review the migration for accuracy.

Cet avertissement est généré, car les champs de nom sont désormais limités à
50 caractères. Si la base de données comporte un nom de plus 50 caractères,
tous les caractères au-delà du cinquantième sont perdus.

Ouvrez la table Student dans SSOX :


Avant l’application de la migration, les colonnes de noms étaient de type
nvarchar(MAX). Les colonnes de nom sont maintenant nvarchar(50) . Le nom
de la colonne est passé de FirstMidName à FirstName .

Exécutez l’application et accédez à la page des étudiants.


Notez que les heures ne sont pas entrées ou affichées avec les dates.
Sélectionnez Create New et essayez d’entrer un nom de plus de 50 caractères.

7 Notes

Dans les sections suivantes, la génération de l’application à certaines étapes génère


des erreurs du compilateur. Les instructions indiquent quand générer l’application.

Entité Instructor
Créez Models/Instructor.cs avec le code suivant :

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }

[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }

[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }

[Display(Name = "Full Name")]


public string FullName
{
get { return LastName + ", " + FirstMidName; }
}

public ICollection<Course> Courses { get; set; }


public OfficeAssignment OfficeAssignment { get; set; }
}
}

Plusieurs attributs peuvent être sur une seule ligne. Les attributs HireDate peuvent être
écrits comme suit :

C#

[DataType(DataType.Date),Display(Name = "Hire
Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]

Propriétés de navigation
Les propriétés Courses et OfficeAssignment sont des propriétés de navigation.

Un formateur pouvant animer un nombre quelconque de cours, Courses est défini


comme une collection.
C#

public ICollection<Course> Courses { get; set; }

Un formateur ne pouvant avoir au plus un bureau, la propriété OfficeAssignment


contient une seule entité OfficeAssignment . OfficeAssignment a la valeur Null si aucun
bureau n’est affecté.

C#

public OfficeAssignment OfficeAssignment { get; set; }

Entité OfficeAssignment

Créez Models/OfficeAssignment.cs avec le code suivant :

C#

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }

public Instructor Instructor { get; set; }


}
}

Attribut Key
L’attribut [Key] est utilisé pour identifier une propriété comme clé primaire quand le
nom de la propriété est autre que classnameID ou ID .

Il existe une relation un-à-zéro-ou-un entre les entités Instructor et OfficeAssignment .


Une affectation de bureau existe uniquement relativement au formateur auquel elle est
assignée. La clé primaire OfficeAssignment est également sa clé étrangère pour l’entité
Instructor . Une relation un-à-zéro-ou-un se produit lorsqu’une clé primaire dans une

table est à la fois une clé primaire et une clé étrangère dans une autre table.

EF Core ne peut pas reconnaître automatiquement InstructorID comme clé primaire de


OfficeAssignment , car InstructorID ne respecte pas la convention d’affectation de

noms d’ID ou de classnameID. Ainsi, l’attribut Key est utilisé pour identifier
InstructorID comme clé primaire :

C#

[Key]
public int InstructorID { get; set; }

Par défaut, EF Core traite la clé comme n’étant pas générée par la base de données, car
la colonne est utilisée pour une relation d’identification. Pour plus d’informations,
consultez Clés EF.

Propriété de navigation Instructor


La propriété de navigation Instructor.OfficeAssignment peut avoir la valeur null, car il
n’est pas certain qu’il existe une ligne OfficeAssignment pour un formateur donné. Un
formateur peut ne pas avoir d’affectation de bureau.

La propriété de navigation OfficeAssignment.Instructor aura toujours une entité


Instructor, car le type InstructorID de clé étrangère est int , type valeur qui n’autorise
pas les valeurs Null. Une affectation de bureau ne peut pas exister sans un formateur.

Quand une entité Instructor a une entité OfficeAssignment associée, chaque entité a
une référence à l’autre dans sa propriété de navigation.

Entité Course
Mettez à jour Models/Course.cs à l’aide du code suivant :

C#
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Title { get; set; }

[Range(0, 5)]
public int Credits { get; set; }

public int DepartmentID { get; set; }

public Department Department { get; set; }


public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<Instructor> Instructors { get; set; }
}
}

L’entité Course a une propriété de clé étrangère DepartmentID . DepartmentID pointe vers
l’entité Department associée. L’entité Course a une propriété de navigation Department .

EF Core n’exige pas de propriété de clé étrangère pour un modèle de données quand le
modèle a une propriété de navigation pour une entité associée. EF Core crée
automatiquement des clés étrangères dans la base de données partout où elles sont
nécessaires. EF Core crée des propriétés cachées pour les clés étrangères créées
automatiquement. Cependant, le fait d’inclure la clé étrangère dans le modèle de
données peut rendre les mises à jour plus simples et plus efficaces. Par exemple,
considérez un modèle où la propriété de clé étrangère DepartmentID n’est pas incluse.
Quand une entité Course est récupérée en vue d’une modification :

La propriété Department a la valeur null si elle n’est pas chargée explicitement.


Pour mettre à jour l’entité Course, vous devez d’abord récupérer l’entité
Department .

Quand la propriété de clé étrangère DepartmentID est incluse dans le modèle de


données, il n’est pas nécessaire de récupérer l’entité Department avant une mise à jour.

Attribut DatabaseGenerated
L’attribut [DatabaseGenerated(DatabaseGeneratedOption.None)] indique que la clé
primaire est fournie par l’application plutôt que générée par la base de données.

C#

[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

Par défaut, EF Core part du principe que les valeurs de clé primaire sont générées par la
base de données. La génération par la base de données est généralement la meilleure
approche. Pour les entités Course , l’utilisateur spécifie la clé primaire. Par exemple, un
numéro de cours comme une série 1000 pour le département Mathématiques et une
série 2000 pour le département Anglais.

Vous pouvez aussi utiliser l’attribut DatabaseGenerated pour générer des valeurs par
défaut. Par exemple, la base de données peut générer automatiquement un champ de
date pour enregistrer la date de création ou de mise à jour d’une ligne. Pour plus
d’informations, consultez Propriétés générées.

Propriétés de clé étrangère et de navigation


Les propriétés de clé étrangère et les propriétés de navigation dans l’entité Course
reflètent les relations suivantes :

Un cours étant affecté à un seul département, il y a une clé étrangère DepartmentID et


une propriété de navigation Department .

C#

public int DepartmentID { get; set; }


public Department Department { get; set; }

Un cours pouvant avoir un nombre quelconque d’étudiants inscrits, la propriété de


navigation Enrollments est une collection :

C#

public ICollection<Enrollment> Enrollments { get; set; }

Un cours pouvant être animé par plusieurs formateurs, la propriété de navigation


Instructors est une collection :
C#

public ICollection<Instructor> Instructors { get; set; }

Entité Department
Créez Models/Department.cs avec le code suivant :

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Name { get; set; }

[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }

public int? InstructorID { get; set; }

public Instructor Administrator { get; set; }


public ICollection<Course> Courses { get; set; }
}
}

Attribut Column
Précédemment, vous avez utilisé l’attribut Column pour changer le mappage des noms
de colonne. Dans le code de l’entité Department , l’attribut Column est utilisé pour
changer le mappage des types de données SQL. La colonne Budget est définie à l’aide
du type monétaire SQL Server dans la base de données :
C#

[Column(TypeName="money")]
public decimal Budget { get; set; }

Le mappage de colonnes n’est généralement pas nécessaire. EF Core choisit le type de


données SQL Server approprié en fonction du type CLR de la propriété. Le type CLR
decimal est mappé à un type SQL Server decimal . Budget étant une valeur monétaire, le

type de données money est plus approprié.

Propriétés de clé étrangère et de navigation


Les propriétés de clé étrangère et de navigation reflètent les relations suivantes :

Un département peut avoir ou ne pas avoir un administrateur.


Un administrateur est toujours un formateur. Ainsi, la propriété InstructorID est
incluse en tant que clé étrangère de l’entité Instructor .

La propriété de navigation se nomme Administrator , mais elle contient une entité


Instructor :

C#

public int? InstructorID { get; set; }


public Instructor Administrator { get; set; }

Le ? dans le code précédent indique que la propriété peut accepter la valeur Null.

Un département pouvant avoir de nombreux cours, il existe une propriété de navigation


Courses :

C#

public ICollection<Course> Courses { get; set; }

Par convention, EF Core autorise la suppression en cascade pour les clés étrangères non
nullables et pour les relations plusieurs à plusieurs. Ce comportement par défaut peut
engendrer des règles de suppression en cascade circulaires. Les règles de suppression
en cascade circulaires provoquent une exception quand une migration est ajoutée.

Par exemple, si la propriété Department.InstructorID a été définie comme n’acceptant


pas les valeurs Null, EF Core configure une règle de suppression en cascade. Dans ce
cas, le service est supprimé quand le formateur désigné comme étant son
administrateur est supprimé. Dans ce scénario, une règle de restriction est plus logique.
L’API Fluent suivante définit une règle de restriction et désactive la suppression en
cascade.

C#

modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)

Propriétés de clé étrangère Enrollment et de navigation


Un enregistrement d’inscription correspond à un cours suivi par un étudiant.

Mettez à jour Models/Enrollment.cs à l’aide du code suivant :

C#

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}

public class Enrollment


{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }

public Course Course { get; set; }


public Student Student { get; set; }
}
}

Les propriétés de clé étrangère et de navigation reflètent les relations suivantes :

Un enregistrement d’inscription ne concernant qu’un seul cours, il y a une propriété de


clé étrangère CourseID et une propriété de navigation Course :

C#

public int CourseID { get; set; }


public Course Course { get; set; }

Un enregistrement d’inscription ne concernant qu’un seul étudiant, il y a une propriété


de clé étrangère StudentID et une propriété de navigation Student :

C#

public int StudentID { get; set; }


public Student Student { get; set; }

Relations plusieurs-à-plusieurs
Il existe une relation plusieurs-à-plusieurs entre les entités Student et Course . L’entité
Enrollment joue le rôle de table de jointure plusieurs-à-plusieurs avec charge utile dans

la base de données. Avec charge utile signifie que la table Enrollment contient des
données supplémentaires en plus des clés étrangères pour les tables jointes. Dans
l’entité Enrollment , les données supplémentaires en plus des clés étrangères sont la clé
primaire et Grade .

L’illustration suivante montre à quoi ressemblent ces relations dans un diagramme


d’entité. (Ce diagramme a été généré à l’aide d’EF Power Tools pour EF 6.x. La création
du diagramme ne fait pas partie du tutoriel.)
Chaque ligne de relation comporte un 1 à une extrémité et un astérisque (*) à l’autre, ce
qui indique une relation un-à-plusieurs.

Si la table Enrollment n’incluait pas d’informations de notes, elle aurait uniquement


besoin de contenir les deux clés étrangères, CourseID et StudentID . Une table de
jointure plusieurs-à-plusieurs sans charge utile est parfois appelée « table de jointure
pure ».

Les entités Instructor et Course ont une relation plusieurs-à-plusieurs à l’aide d’une
table de jointure pure (PJT).

Mettre à jour le contexte de base de données


Mettez à jour Data/SchoolContext.cs à l’aide du code suivant :

C#

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable(nameof(Course))
.HasMany(c => c.Instructors)
.WithMany(i => i.Courses);
modelBuilder.Entity<Student>().ToTable(nameof(Student));
modelBuilder.Entity<Instructor>().ToTable(nameof(Instructor));
}
}
}

Le code précédent ajoute les nouvelles entités et configure la relation plusieurs-à-


plusieurs entre les entités Instructor et Course .

Alternative d’API Fluent aux attributs


La méthode OnModelCreating du code précédent utilise l’API Fluent pour configurer le
comportement de EF Core. L’API est appelée « Fluent », car elle est souvent utilisée en
enchaînant une série d’appels de méthode en une seule instruction. Le code suivant est
un exemple de l’API Fluent :

C#

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}

Dans ce tutoriel, l’API Fluent est utilisée uniquement pour le mappage de base de
données qui ne peut pas être effectué avec des attributs. Toutefois, l’API Fluent peut
spécifier la plupart des règles de mise en forme, de validation et de mappage pouvant
être spécifiées à l’aide d’attributs.

Certains attributs, tels que MinimumLength , ne peuvent pas être appliqués avec l’API
Fluent. MinimumLength ne change pas le schéma. Il applique uniquement une règle de
validation de longueur minimale.

Certains développeurs préfèrent utiliser exclusivement l’API Fluent afin de conserver des
classes d’entité propres. Vous pouvez combiner des attributs et l’API Fluent. Certaines
configurations peuvent être effectuées uniquement avec l’API Fluent, par exemple
spécification d’une clé primaire composite. Certaines autres peuvent être effectuées
uniquement avec des attributs ( MinimumLength ). Voici ce que nous recommandons pour
l’utilisation des API Fluent ou des attributs :

Choisissez l’une de ces deux approches.


Dans la mesure du possible, utilisez l’approche choisie de manière cohérente.

Certains des attributs utilisés dans ce tutoriel sont utilisés pour :

La validation uniquement (par exemple, MinimumLength ).


La configuration de EF Core uniquement (par exemple, HasKey ).
La validation et la configuration de EF Core (par exemple, [StringLength(50)] ).

Pour plus d’informations sur les attributs et l’API Fluent, consultez Méthodes de
configuration.

Amorcer la base de données


Mettez à jour le code dans Data/DbInitializer.cs :

C#

using ContosoUniversity.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}

var alexander = new Student


{
FirstMidName = "Carson",
LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2016-09-01")
};

var alonso = new Student


{
FirstMidName = "Meredith",
LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2018-09-01")
};

var anand = new Student


{
FirstMidName = "Arturo",
LastName = "Anand",
EnrollmentDate = DateTime.Parse("2019-09-01")
};

var barzdukas = new Student


{
FirstMidName = "Gytis",
LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2018-09-01")
};

var li = new Student


{
FirstMidName = "Yan",
LastName = "Li",
EnrollmentDate = DateTime.Parse("2018-09-01")
};

var justice = new Student


{
FirstMidName = "Peggy",
LastName = "Justice",
EnrollmentDate = DateTime.Parse("2017-09-01")
};

var norman = new Student


{
FirstMidName = "Laura",
LastName = "Norman",
EnrollmentDate = DateTime.Parse("2019-09-01")
};

var olivetto = new Student


{
FirstMidName = "Nino",
LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2011-09-01")
};

var students = new Student[]


{
alexander,
alonso,
anand,
barzdukas,
li,
justice,
norman,
olivetto
};

context.AddRange(students);

var abercrombie = new Instructor


{
FirstMidName = "Kim",
LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11")
};

var fakhouri = new Instructor


{
FirstMidName = "Fadi",
LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06")
};

var harui = new Instructor


{
FirstMidName = "Roger",
LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01")
};

var kapoor = new Instructor


{
FirstMidName = "Candace",
LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15")
};

var zheng = new Instructor


{
FirstMidName = "Roger",
LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12")
};

var instructors = new Instructor[]


{
abercrombie,
fakhouri,
harui,
kapoor,
zheng
};

context.AddRange(instructors);

var officeAssignments = new OfficeAssignment[]


{
new OfficeAssignment {
Instructor = fakhouri,
Location = "Smith 17" },
new OfficeAssignment {
Instructor = harui,
Location = "Gowan 27" },
new OfficeAssignment {
Instructor = kapoor,
Location = "Thompson 304" }
};

context.AddRange(officeAssignments);

var english = new Department


{
Name = "English",
Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = abercrombie
};

var mathematics = new Department


{
Name = "Mathematics",
Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = fakhouri
};

var engineering = new Department


{
Name = "Engineering",
Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = harui
};

var economics = new Department


{
Name = "Economics",
Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = kapoor
};
var departments = new Department[]
{
english,
mathematics,
engineering,
economics
};

context.AddRange(departments);

var chemistry = new Course


{
CourseID = 1050,
Title = "Chemistry",
Credits = 3,
Department = engineering,
Instructors = new List<Instructor> { kapoor, harui }
};

var microeconomics = new Course


{
CourseID = 4022,
Title = "Microeconomics",
Credits = 3,
Department = economics,
Instructors = new List<Instructor> { zheng }
};

var macroeconmics = new Course


{
CourseID = 4041,
Title = "Macroeconomics",
Credits = 3,
Department = economics,
Instructors = new List<Instructor> { zheng }
};

var calculus = new Course


{
CourseID = 1045,
Title = "Calculus",
Credits = 4,
Department = mathematics,
Instructors = new List<Instructor> { fakhouri }
};

var trigonometry = new Course


{
CourseID = 3141,
Title = "Trigonometry",
Credits = 4,
Department = mathematics,
Instructors = new List<Instructor> { harui }
};
var composition = new Course
{
CourseID = 2021,
Title = "Composition",
Credits = 3,
Department = english,
Instructors = new List<Instructor> { abercrombie }
};

var literature = new Course


{
CourseID = 2042,
Title = "Literature",
Credits = 4,
Department = english,
Instructors = new List<Instructor> { abercrombie }
};

var courses = new Course[]


{
chemistry,
microeconomics,
macroeconmics,
calculus,
trigonometry,
composition,
literature
};

context.AddRange(courses);

var enrollments = new Enrollment[]


{
new Enrollment {
Student = alexander,
Course = chemistry,
Grade = Grade.A
},
new Enrollment {
Student = alexander,
Course = microeconomics,
Grade = Grade.C
},
new Enrollment {
Student = alexander,
Course = macroeconmics,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = calculus,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = trigonometry,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = composition,
Grade = Grade.B
},
new Enrollment {
Student = anand,
Course = chemistry
},
new Enrollment {
Student = anand,
Course = microeconomics,
Grade = Grade.B
},
new Enrollment {
Student = barzdukas,
Course = chemistry,
Grade = Grade.B
},
new Enrollment {
Student = li,
Course = composition,
Grade = Grade.B
},
new Enrollment {
Student = justice,
Course = literature,
Grade = Grade.B
}
};

context.AddRange(enrollments);
context.SaveChanges();
}
}
}

Le code précédent fournit des données de valeur initiale pour les nouvelles entités. La
majeure partie de ce code crée des objets d’entités et charge des exemples de données.
Les exemples de données sont utilisés à des fins de test.

Appliquer la migration ou supprimer et recréer


Avec la base de données existante, il existe deux approches pour modifier la base de
données :
Supprimer et recréer la base de données. Choisissez cette section lorsque vous
utilisez SQLite.
Appliquer la migration à la base de données Les instructions de cette section
valent uniquement pour SQL Server, pas pour SQLite.

Les deux options fonctionnent pour SQL Server. Bien que la méthode d’application de la
migration soit plus longue et complexe, il s’agit de l’approche privilégiée pour les
environnements de production réels.

Supprimer et recréer la base de données


Pour forcer EF Core à créer une base de données, annulez et mettez à jour la base de
données :

Visual Studio

Supprimez le dossier Migrations.


Dans la console du Gestionnaire de package, exécutez les commandes
suivantes :

PowerShell

Drop-Database
Add-Migration InitialCreate
Update-Database

Exécutez l'application. L’exécution de l’application entraîne l’exécution de la méthode


DbInitializer.Initialize . La méthode DbInitializer.Initialize remplit la nouvelle

base de données.

Visual Studio

Ouvrez la base de données dans SSOX :

Si SSOX était déjà ouvert, cliquez sur le bouton Actualiser.


Développez le nœud Tables. Les tables créées sont affichées.

Étapes suivantes
Les deux tutoriels suivants montrent comment lire et mettre à jour des données
associées.

Tutoriel précédent Tutoriel suivant

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 6, Razor Pages avec EF Core dans
ASP.NET Core - Lire les données
associées
Article • 30/11/2023

Par Tom Dykstra , Jon P Smith et Rick Anderson

L’application web Contoso University montre comment créer des applications web Razor
Pages avec EF Core et Visual Studio. Pour obtenir des informations sur la série de
didacticiels, consultez le premier didacticiel.

Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez
l’application finale et comparez ce code à celui que vous avez créé en suivant le
tutoriel.

Ce tutoriel montre comment lire et afficher des données associées. Les données
associées sont des données qu’EF Core charge dans des propriétés de navigation.

Les illustrations suivantes montrent les pages terminées pour ce didacticiel :


Chargement hâtif, explicite et différé
EF Core peut charger des données associées dans les propriétés de navigation d’une
entité de plusieurs manières :

Chargement hâtif. Le chargement hâtif a lieu quand une requête pour un type
d’entité charge également des entités associées. Quand une entité est lue, ses
données associées sont récupérées. Cela génère en général une requête de
jointure unique qui récupère toutes les données nécessaires. EF Core émet
plusieurs requêtes pour certains types de chargement hâtif. Il peut s’avérer plus
efficace d’émettre plusieurs requêtes plutôt qu’une seule grande. Le chargement
hâtif est spécifié avec les méthodes Include et ThenInclude.
Le chargement hâtif envoie plusieurs requêtes quand une navigation dans la
collection est incluse :
Une requête pour la requête principale
Une requête pour chaque « périmètre » de collection dans l’arborescence de la
charge.

Requêtes distinctes avec Load : les données peuvent être récupérées dans des
requêtes distinctes, et EF Core « corrige » les propriétés de navigation. Quand EF
Core « corrige », cela signifie que les propriétés de navigation sont renseignées
automatiquement. Les requêtes distinctes avec Load s’apparentent plus au
chargement explicite qu’au chargement hâtif.

Remarque :EF Core corrige automatiquement les propriétés de navigation vers


d’autres entités qui étaient précédemment chargées dans l’instance de contexte.
Même si les données pour une propriété de navigation ne sont pas explicitement
incluses, la propriété peut toujours être renseignée si toutes ou une partie des
entités associées ont été précédemment chargées.

Chargement explicite. Quand l’entité est lue pour la première fois, les données
associées ne sont pas récupérées. Vous devez écrire du code pour récupérer les
données associées en cas de besoin. En cas de chargement explicite avec des
requêtes distinctes, plusieurs requêtes sont envoyées à la base de données. Avec le
chargement explicite, le code spécifie les propriétés de navigation à charger.
Utilisez la méthode Load pour effectuer le chargement explicite. Par exemple :
Chargement différé. Quand l’entité est lue pour la première fois, les données
associées ne sont pas récupérées. Lors du premier accès à une propriété de
navigation, les données requises pour cette propriété de navigation sont
récupérées automatiquement. Une requête est envoyée à la base de données
chaque fois qu’une propriété de navigation fait pour la première fois l’objet d’un
accès. Le chargement différé peut nuire aux performances, par exemple lorsque les
développeurs utilisent des requêtes N+1 . Les requêtes N+1 chargent un parent
et énumèrent via des enfants.

Créer des pages Course


L’entité Course comprend une propriété de navigation qui contient l’entité Department
associée.

Pour afficher le nom du service (« department ») affecté pour un cours (« course ») :

Chargez l’entité Department associée dans la propriété de navigation


Course.Department .

Obtenez le nom à partir de la propriété Name de l’entité Department .


Générer automatiquement des modèles de pages Course

Visual Studio

Suivez les instructions dans Générer automatiquement des modèles de pages


Student avec les exceptions suivantes :
Créez un dossier Pages/Courses.
Utilisez Course pour la classe de modèle.
Utilisez la classe de contexte existante au lieu d’en créer une nouvelle.

Ouvrez Pages/Courses/Index.cshtml.cs et examinez la méthode OnGetAsync . Le


moteur de génération de modèles automatique a spécifié le chargement hâtif pour
la propriété de navigation Department . La méthode Include spécifie le chargement
hâtif.

Exécutez l’application et sélectionnez le lien Courses. La colonne Department


affiche le DepartmentID , ce qui n’est pas utile.

Afficher le nom du service (« department »)


Mettez à jour Pages/Courses/Index.cshtml.cs avec le code suivant :

C#

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public IndexModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

public IList<Course> Courses { get; set; }

public async Task OnGetAsync()


{
Courses = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
}
}

Le code précédent remplace la propriété Course par Courses et ajoute AsNoTracking .

Les requêtes sans suivi sont utiles lorsque les résultats sont utilisés dans un scénario en
lecture seule. Leur exécution est généralement plus rapide, car il n’est pas nécessaire de
configurer les informations de suivi des modifications. Si les entités récupérées à partir
de la base de données n’ont pas besoin d’être mises à jour, une requête sans suivi est
susceptible de fonctionner mieux qu’une requête avec suivi.

Dans certains cas, une requête avec suivi est plus efficace qu’une requête sans suivi.
Pour plus d’informations, consultez Requêtes avec suivi et non-suivi. Dans le code
précédent, AsNoTracking est appelé, car les entités ne sont pas mises à jour dans le
contexte actuel.

Mettez à jour Pages/Courses/Index.cshtml à l’aide du code suivant.

CSHTML

@page
@model ContosoUniversity.Pages.Courses.IndexModel

@{
ViewData["Title"] = "Courses";
}

<h1>Courses</h1>

<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Courses[0].CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Courses[0].Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Courses)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.CourseID">Edit</a>
|
<a asp-page="./Details" asp-route-
id="@item.CourseID">Details</a> |
<a asp-page="./Delete" asp-route-
id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Les modifications suivantes ont été apportées au code généré automatiquement :

Le nom de propriété Course est changé en Courses .

Ajout d’une colonne Number qui affiche la valeur de la propriété CourseID . Par
défaut, les clés primaires ne sont pas générées automatiquement, car elles ne sont
normalement pas significatives pour les utilisateurs finaux. Toutefois, dans le cas
présent la clé primaire est significative.

Modification de la colonne Department afin d’afficher le nom du département. Le


code affiche la propriété Name de l’entité Department qui est chargée dans la
propriété de navigation Department :

HTML

@Html.DisplayFor(modelItem => item.Department.Name)


Exécutez l’application et sélectionnez l’onglet Courses pour afficher la liste avec les
noms des départements.

Chargement de données associées avec Select


La méthode OnGetAsync charge les données associées avec la méthode Include . La
méthode Select est autre solution qui charge uniquement les données associées
nécessaires. Pour les éléments uniques, comme Department.Name , il utilise SQL INNER
JOIN . Pour les collections, il utilise un autre accès à la base de données, mais c’est aussi

le cas de l’opérateur Include sur les collections.

Le code suivant charge les données associées avec la méthode Select :

C#

public IList<CourseViewModel> CourseVM { get; set; }

public async Task OnGetAsync()


{
CourseVM = await _context.Courses
.Select(p => new CourseViewModel
{
CourseID = p.CourseID,
Title = p.Title,
Credits = p.Credits,
DepartmentName = p.Department.Name
}).ToListAsync();
}
Le code précédent ne retourne aucun type d’entité. Par conséquent, aucun suivi n’est
effectué. Pour plus d’informations sur le suivi EF, consultez Requêtes avec suivi et sans
suivi.

CourseViewModel :

C#

public class CourseViewModel


{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}

Consultez IndexSelectModel pour des instructions Razor Pages complètes.

Créer des pages Instructor


Cette section génère automatiquement des modèles de pages Instructor et ajoute les
cours et les inscriptions associés à la page d’index des formateurs.
Cette page lit et affiche les données associées comme suit :

La liste des formateurs affiche des données associées de l’entité OfficeAssignment


(Office dans l’image précédente). Il existe une relation un-à-zéro-ou-un entre les
entités Instructor et OfficeAssignment . Le chargement hâtif est utilisé pour les
entités OfficeAssignment . Le chargement hâtif est généralement plus efficace
quand les données associées doivent être affichées. Ici, les affectations de bureau
pour les formateurs sont affichées.
Quand l’utilisateur sélectionne un formateur, les entités Course associées sont
affichées. Il existe une relation plusieurs-à-plusieurs entre les entités Instructor et
Course . Le chargement hâtif est utilisé pour les entités Course et leurs entités
Department associées. Dans le cas présent, des requêtes distinctes peuvent être
plus efficaces, car seuls les cours du formateur sélectionné sont nécessaires. Cet
exemple montre comment utiliser le chargement hâtif pour des propriétés de
navigation dans des entités qui se trouvent dans des propriétés de navigation.
Quand l’utilisateur sélectionne un cours, les données associées de l’entité
Enrollments s’affichent. Dans l’image précédente, le nom et la note de l’étudiant

sont affichés. Il existe une relation un-à-plusieurs entre les entités Course et
Enrollment .

Création d'un modèle de vue


La page sur les formateurs affiche les données de trois tables différentes. Un modèle de
vue comprenant trois propriétés représentant les trois tables est nécessaire.

Créez Models/SchoolViewModels/InstructorIndexData.cs avec le code suivant :

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}

Générer automatiquement des modèles de pages


Instructor

Visual Studio

Suivez les instructions dans Générer automatiquement des modèles de pages


Student avec les exceptions suivantes :
Créez un dossier Pages/Instructors.
Utilisez Instructor pour la classe de modèle.
Utilisez la classe de contexte existante au lieu d’en créer une nouvelle.

Exécutez l’application et accédez à la page des formateurs.

Mettez à jour Pages/Instructors/Index.cshtml.cs à l’aide du code suivant :

C#

using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels; // Add VM
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public IndexModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

public InstructorIndexData InstructorData { get; set; }


public int InstructorID { get; set; }
public int CourseID { get; set; }

public async Task OnGetAsync(int? id, int? courseID)


{
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.ThenInclude(c => c.Department)
.OrderBy(i => i.LastName)
.ToListAsync();

if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.Courses;
}

if (courseID != null)
{
CourseID = courseID.Value;
IEnumerable<Enrollment> Enrollments = await
_context.Enrollments
.Where(x => x.CourseID == CourseID)
.Include(i=>i.Student)
.ToListAsync();
InstructorData.Enrollments = Enrollments;
}
}
}
}

La méthode OnGetAsync accepte des données de route facultatives pour l’ID du


formateur sélectionné.

Examinez la requête dans le fichier Pages/Instructors/Index.cshtml.cs :

C#

InstructorData = new InstructorIndexData();


InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.ThenInclude(c => c.Department)
.OrderBy(i => i.LastName)
.ToListAsync();

Le code spécifie un chargement hâtif pour les propriétés de navigation suivantes :

Instructor.OfficeAssignment
Instructor.Courses

Course.Department

Le code suivant s’exécute quand un formateur est sélectionné, à savoir id != null .

C#

if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.Courses;
}

Le formateur sélectionné est récupéré à partir de la liste des formateurs dans le modèle
d’affichage. La propriété Courses du modèle d’affichage est chargée avec les entités
Course de la propriété de navigation Courses sélectionnée de ce formateur.
La méthode Where retourne une collection. Dans ce cas, le filtre sélectionne une entité
unique, de sorte que la méthode Single est appelée pour convertir la collection en une
seule entité Instructor . L’entité Instructor fournit l’accès à la propriété de navigation
Course .

La méthode Single est utilisée sur une collection quand la collection ne compte qu’un
seul élément. La méthode Single lève une exception si la collection est vide ou s’il y a
plusieurs éléments. Une alternative est SingleOrDefault, qui renvoie une valeur par
défaut si la collection est vide. Pour cette requête, null dans la valeur par défaut
retournée.

Le code suivant renseigne la propriété Enrollments du modèle d’affichage quand un


cours est sélectionné :

C#

if (courseID != null)
{
CourseID = courseID.Value;
IEnumerable<Enrollment> Enrollments = await _context.Enrollments
.Where(x => x.CourseID == CourseID)
.Include(i=>i.Student)
.ToListAsync();
InstructorData.Enrollments = Enrollments;
}

Mettre à jour la page d’index des formateurs


Mettez à jour Pages/Instructors/Index.cshtml à l’aide du code suivant.

CSHTML

@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel

@{
ViewData["Title"] = "Instructors";
}

<h2>Instructors</h2>

<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.InstructorData.Instructors)
{
string selectedRow = "";
if (item.ID == Model.InstructorID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.Courses)
{
@course.CourseID @: @course.Title <br />
}
}
</td>
<td>
<a asp-page="./Index" asp-route-id="@item.ID">Select</a>
|
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-
id="@item.ID">Details</a> |
<a asp-page="./Delete" asp-route-
id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

@if (Model.InstructorData.Courses != null)


{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>

@foreach (var item in Model.InstructorData.Courses)


{
string selectedRow = "";
if (item.CourseID == Model.CourseID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
<a asp-page="./Index" asp-route-
courseID="@item.CourseID">Select</a>
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}

</table>
}

@if (Model.InstructorData.Enrollments != null)


{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.InstructorData.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}

Le code précédent apporte les modifications suivantes :

Met à jour la directive page en @page "{id:int?}" . "{id:int?}" est un modèle de


route. Le modèle de route change les chaînes de requête entières dans l’URL en
données de route. Par exemple, si vous cliquez sur le lien Select pour un formateur
avec seulement la directive @page , une URL comme celle-ci est générée :

https://localhost:5001/Instructors?id=2

Quand la directive de page est @page "{id:int?}" , l’URL est :


https://localhost:5001/Instructors/2

Ajoute une colonne Office qui affiche item.OfficeAssignment.Location seulement


si item.OfficeAssignment n’a pas la valeur Null. Comme il s’agit d’une relation un-
à-zéro-ou-un, il se peut qu’il n’y ait pas d’entité OfficeAssignment associée.

HTML

@if (item.OfficeAssignment != null)


{
@item.OfficeAssignment.Location
}

Ajoute une colonne Courses qui affiche les cours animés par chaque formateur.
Consultez Conversion de ligne explicite pour en savoir plus sur cette syntaxe razor.

Ajoute du code qui ajoute dynamiquement class="table-success" à l’élément tr


du formateur et du cours sélectionnés. Cela définit une couleur d’arrière-plan pour
la ligne sélectionnée à l’aide d’une classe d’amorçage.

HTML

string selectedRow = "";


if (item.CourseID == Model.CourseID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
Ajoute un nouveau lien hypertexte libellé Select. Ce lien envoie l’ID du formateur
sélectionné à la méthode Index , et définit une couleur d’arrière-plan.

HTML

<a asp-action="Index" asp-route-id="@item.ID">Select</a> |

Ajoute un tableau de cours pour l’instructeur sélectionné.

Ajoute un tableau d’inscriptions d’étudiants pour le cours sélectionné.

Exécutez l’application et sélectionnez l’onglet Instructeurs. La page affiche le Location


(bureau) à partir de l’entité OfficeAssignment associée. Si OfficeAssignment a la valeur
Null, une cellule de tableau vide est affichée.

Cliquez sur le lien Select pour un formateur. Le style de ligne change et les cours
attribués à ce formateur s’affichent.

Sélectionnez un cours pour afficher la liste des étudiants inscrits et leurs notes.
Étapes suivantes
Le didacticiel suivant montre comment mettre à jour les données associées.

Tutoriel précédent Tutoriel suivant

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
 Ouvrir un problème de
examiner les problèmes et les
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 7, Razor Pages avec EF Core dans
ASP.NET Core - Mettre à jour les
données associées
Article • 30/11/2023

Par Tom Dykstra , Jon P Smith et Rick Anderson

L’application web Contoso University montre comment créer des applications web Razor
Pages avec EF Core et Visual Studio. Pour obtenir des informations sur la série de
didacticiels, consultez le premier didacticiel.

Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez
l’application finale et comparez ce code à celui que vous avez créé en suivant le
tutoriel.

Le tutoriel suivant montre comment mettre à jour les données associées. Les illustrations
suivantes montrent certaines des pages finalisées.
Mettre à jour les pages de création et de
modification du cours
Le code généré automatiquement pour les pages de création et de modification du
cours contient la liste déroulante des départements de l’université qui affiche
DepartmentID , un int . La liste déroulante doit afficher le nom du département. Ces deux

pages ont donc besoin d’une liste de noms de départements. Pour fournir cette liste,
utilisez une classe de base pour les pages Create et Edit.

Créer une classe de base pour la création et la


modification d’un cours
Créez un fichier Pages/Courses/DepartmentNamePageModel.cs avec le code suivant :
C#

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace ContosoUniversity.Pages.Courses
{
public class DepartmentNamePageModel : PageModel
{
public SelectList DepartmentNameSL { get; set; }

public void PopulateDepartmentsDropDownList(SchoolContext _context,


object selectedDepartment = null)
{
var departmentsQuery = from d in _context.Departments
orderby d.Name // Sort by name.
select d;

DepartmentNameSL = new
SelectList(departmentsQuery.AsNoTracking(),
nameof(Department.DepartmentID),
nameof(Department.Name),
selectedDepartment);
}
}
}

Le code précédent crée un SelectList pour contenir la liste des noms de département. Si
selectedDepartment est spécifié, ce département est sélectionné dans le SelectList .

Les classes de modèle de page Create et Edit doivent dériver de


DepartmentNamePageModel .

Mettre à jour le modèle de page de création de cours


Un cours est affecté à un département. La classe de base des pages Create et Edit
fournit un SelectList pour la sélection du département. La liste déroulante qui utilise
SelectList définit la propriété de clé étrangère Course.DepartmentID . EF Core utilise la

clé étrangère Course.DepartmentID pour charger la propriété de navigation Department .


Mettez à jour Pages/Courses/Create.cshtml.cs à l’aide du code suivant :

C#

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
public class CreateModel : DepartmentNamePageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public CreateModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

public IActionResult OnGet()


{
PopulateDepartmentsDropDownList(_context);
return Page();
}

[BindProperty]
public Course Course { get; set; }

public async Task<IActionResult> OnPostAsync()


{
var emptyCourse = new Course();

if (await TryUpdateModelAsync<Course>(
emptyCourse,
"course", // Prefix for form value.
s => s.CourseID, s => s.DepartmentID, s => s.Title, s =>
s.Credits))
{
_context.Courses.Add(emptyCourse);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

// Select DepartmentID if TryUpdateModelAsync fails.


PopulateDepartmentsDropDownList(_context,
emptyCourse.DepartmentID);
return Page();
}
}
}

Si vous souhaitez voir les commentaires de code traduits dans une langue autre que
l’anglais, dites-le nous dans cette discussion GitHub .

Le code précédent :

Dérive de DepartmentNamePageModel .
Utilise TryUpdateModelAsync pour empêcher la sur-validation.
Supprime ViewData["DepartmentID"] . Le DepartmentNameSL SelectList est un
modèle fortement typé et sera utilisé par la page Razor. Les modèles fortement
typés sont préférables aux modèles faiblement typés. Pour plus d’informations,
consultez Données faiblement typées (ViewData et ViewBag).

Mettre à jour la page Razor de création de cours


Mettez à jour Pages/Courses/Create.cshtml à l’aide du code suivant :

CSHTML
@page
@model ContosoUniversity.Pages.Courses.CreateModel
@{
ViewData["Title"] = "Create Course";
}
<h2>Create</h2>
<h4>Course</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Course.CourseID" class="control-label">
</label>
<input asp-for="Course.CourseID" class="form-control" />
<span asp-validation-for="Course.CourseID" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Course.Title" class="control-label"></label>
<input asp-for="Course.Title" class="form-control" />
<span asp-validation-for="Course.Title" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Course.Credits" class="control-label">
</label>
<input asp-for="Course.Credits" class="form-control" />
<span asp-validation-for="Course.Credits" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Course.Department" class="control-label">
</label>
<select asp-for="Course.DepartmentID" class="form-control"
asp-items="@Model.DepartmentNameSL">
<option value="">-- Select Department --</option>
</select>
<span asp-validation-for="Course.DepartmentID" class="text-
danger" />
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary"
/>
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Le code précédent apporte les modifications suivantes :

Modifie la légende de DepartmentID en Department.


Il remplace "ViewBag.DepartmentID" par DepartmentNameSL (à partir de la classe de
base).
Il ajoute l’option « Select Department ». Si vous n’avez pas encore sélectionné de
département, ce changement affiche l’option « Select Department » dans la liste
déroulante, plutôt que le premier département.
Il ajoute un message de validation quand le département n’est pas sélectionné.

La Page Razor utilise le Tag Helper Select :

CSHTML

<div class="form-group">
<label asp-for="Course.Department" class="control-label"></label>
<select asp-for="Course.DepartmentID" class="form-control"
asp-items="@Model.DepartmentNameSL">
<option value="">-- Select Department --</option>
</select>
<span asp-validation-for="Course.DepartmentID" class="text-danger" />
</div>

Testez la page Create. La page Create affiche le nom du département plutôt que son ID.

Mettre à jour le modèle de page de modification du cours


Mettez à jour Pages/Courses/Edit.cshtml.cs à l’aide du code suivant :

C#

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Courses
{
public class EditModel : DepartmentNamePageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Course Course { get; set; }

public async Task<IActionResult> OnGetAsync(int? id)


{
if (id == null)
{
return NotFound();
}

Course = await _context.Courses


.Include(c => c.Department).FirstOrDefaultAsync(m =>
m.CourseID == id);

if (Course == null)
{
return NotFound();
}

// Select current DepartmentID.


PopulateDepartmentsDropDownList(_context, Course.DepartmentID);
return Page();
}

public async Task<IActionResult> OnPostAsync(int? id)


{
if (id == null)
{
return NotFound();
}

var courseToUpdate = await _context.Courses.FindAsync(id);

if (courseToUpdate == null)
{
return NotFound();
}

if (await TryUpdateModelAsync<Course>(
courseToUpdate,
"course", // Prefix for form value.
c => c.Credits, c => c.DepartmentID, c => c.Title))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}

// Select DepartmentID if TryUpdateModelAsync fails.


PopulateDepartmentsDropDownList(_context,
courseToUpdate.DepartmentID);
return Page();
}
}
}

Les modifications sont semblables à celles effectuées dans le modèle de page Create.
Dans le code précédent, PopulateDepartmentsDropDownList passe l’ID du département,
ce qui sélectionne le département correspondant dans la liste déroulante.

Mettre à jour la de page Razor de modification du cours


Mettez à jour Pages/Courses/Edit.cshtml à l’aide du code suivant :

CSHTML

@page
@model ContosoUniversity.Pages.Courses.EditModel

@{
ViewData["Title"] = "Edit";
}

<h2>Edit</h2>

<h4>Course</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<input type="hidden" asp-for="Course.CourseID" />
<div class="form-group">
<label asp-for="Course.CourseID" class="control-label">
</label>
<div>@Html.DisplayFor(model => model.Course.CourseID)</div>
</div>
<div class="form-group">
<label asp-for="Course.Title" class="control-label"></label>
<input asp-for="Course.Title" class="form-control" />
<span asp-validation-for="Course.Title" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Course.Credits" class="control-label">
</label>
<input asp-for="Course.Credits" class="form-control" />
<span asp-validation-for="Course.Credits" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Course.Department" class="control-label">
</label>
<select asp-for="Course.DepartmentID" class="form-control"
asp-items="@Model.DepartmentNameSL"></select>
<span asp-validation-for="Course.DepartmentID" class="text-
danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>

<div>
<a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Le code précédent apporte les modifications suivantes :

Affiche l’identificateur du cours. En général la clé primaire (PK) d’une entité n’est
pas affichée. Les clés primaires sont généralement sans signification pour les
utilisateurs. Dans ce cas, la clé est le numéro de cours.
Change la légende de la liste déroulante en remplaçant DepartmentID par
Department.
Remplace "ViewBag.DepartmentID" par DepartmentNameSL , qui est dans la classe de
base.

La page contient un champ masqué ( <input type="hidden"> ) pour le numéro de cours.


L’ajout d’un Tag Helper <label> avec asp-for="Course.CourseID" n’élimine pas la
nécessité de la présence du champ masqué. <input type="hidden"> est obligatoire pour
que le numéro de cours soit inclus dans les données publiées quand l’utilisateur
sélectionne Enregistrer.

Mettre à jour les modèles de pages du cours


AsNoTracking peut améliorer les performances quand le suivi n’est pas nécessaire.

Mettez à jour Pages/Courses/Delete.cshtml.cs et Pages/Courses/Details.cshtml.cs en


ajoutant AsNoTracking aux méthodes OnGetAsync :

C#
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}

Course = await _context.Courses


.AsNoTracking()
.Include(c => c.Department)
.FirstOrDefaultAsync(m => m.CourseID == id);

if (Course == null)
{
return NotFound();
}
return Page();
}

Mettre à jour les pages Razor des cours


Mettez à jour Pages/Courses/Delete.cshtml à l’aide du code suivant :

CSHTML

@page
@model ContosoUniversity.Pages.Courses.DeleteModel

@{
ViewData["Title"] = "Delete";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>


<div>
<h4>Course</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.CourseID)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Department.Name)
</dd>
</dl>

<form method="post">
<input type="hidden" asp-for="Course.CourseID" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>

Apportez les mêmes modifications à la page Details.

CSHTML

@page
@model ContosoUniversity.Pages.Courses.DetailsModel

@{
ViewData["Title"] = "Details";
}

<h2>Details</h2>

<div>
<h4>Course</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.CourseID)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Course.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Course.Department.Name)
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Course.CourseID">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>

Tester les pages de cours


Testez les pages de création, de modification, de détails et de suppression.

Mettre à jour les pages de création et de


modification du formateur
Les instructeurs peuvent enseigner dans n’importe quel nombre de cours. L’illustration
suivante montre la page de modification du formateur avec un tableau comportant les
cases à cocher qui correspondent aux cours.
Les cases à cocher permettent de modifier les cours auxquels un formateur est affecté.
Une case à cocher est affichée pour chaque cours de la base de données. Les cours
auxquels le formateur est affecté sont cochés. L’utilisateur peut cocher ou décocher les
cases pour changer les affectations de cours. Si le nombre de cours était bien plus
important, une autre interface utilisateur serait probablement plus pratique. Toutefois, la
méthode de gestion d’une relation plusieurs à plusieurs présentée ici resterait la même.
Pour créer ou supprimer des relations, vous devez manipuler une entité de jointure.

Créer une classe pour les données de cours affectées


Créez Models/SchoolViewModels/AssignedCourseData.cs avec le code suivant :

C#

namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}

La classe AssignedCourseData contient des données permettant de créer les cases à


cocher pour les cours affectés à un formateur.

Créer une classe de base pour le modèle de page du


formateur
Créez la classe de base Pages/Instructors/InstructorCoursesPageModel.cs :

C#

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Linq;

namespace ContosoUniversity.Pages.Instructors
{
public class InstructorCoursesPageModel : PageModel
{
public List<AssignedCourseData> AssignedCourseDataList;

public void PopulateAssignedCourseData(SchoolContext context,


Instructor instructor)
{
var allCourses = context.Courses;
var instructorCourses = new HashSet<int>(
instructor.Courses.Select(c => c.CourseID));
AssignedCourseDataList = new List<AssignedCourseData>();
foreach (var course in allCourses)
{
AssignedCourseDataList.Add(new AssignedCourseData
{
CourseID = course.CourseID,
Title = course.Title,
Assigned = instructorCourses.Contains(course.CourseID)
});
}
}
}
}
InstructorCoursesPageModel est la classe de base pour les modèles de page Edit et

Create. PopulateAssignedCourseData lit toutes les entités Course pour renseigner


AssignedCourseDataList . Pour chaque cours, le code définit le CourseID , le titre, et si le

formateur est affecté au cours. Un HashSet est utilisé pour permettre des recherches
efficaces.

Gérer l’emplacement du bureau


La page de modification doit également gérer la relation « un à zéro/zéro ou un » qui
existe entre l’entité de l’instructeur et l’entité OfficeAssignment . Le code de modification
du formateur doit gérer les scénarios suivants :

Si l’utilisateur efface l’attribution d'un bureau, supprimer l'entité OfficeAssignment .


Si l’utilisateur entre une attribution et qu'elle était vide, créer une nouvelle entité
OfficeAssignment .

Si l’utilisateur change l’attribution de bureau, mettre à jour l’entité


OfficeAssignment .

Mettre à jour le modèle de page de


modification du formateur
Mettez à jour Pages/Instructors/Edit.cshtml.cs à l’aide du code suivant :

C#

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
public class EditModel : InstructorCoursesPageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Instructor Instructor { get; set; }

public async Task<IActionResult> OnGetAsync(int? id)


{
if (id == null)
{
return NotFound();
}

Instructor = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

if (Instructor == null)
{
return NotFound();
}
PopulateAssignedCourseData(_context, Instructor);
return Page();
}

public async Task<IActionResult> OnPostAsync(int? id, string[]


selectedCourses)
{
if (id == null)
{
return NotFound();
}

var instructorToUpdate = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.FirstOrDefaultAsync(s => s.ID == id);

if (instructorToUpdate == null)
{
return NotFound();
}

if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"Instructor",
i => i.FirstMidName, i => i.LastName,
i => i.HireDate, i => i.OfficeAssignment))
{
if (String.IsNullOrWhiteSpace(
instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(selectedCourses,
instructorToUpdate);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
PopulateAssignedCourseData(_context, instructorToUpdate);
return Page();
}

public void UpdateInstructorCourses(string[] selectedCourses,


Instructor instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}

var selectedCoursesHS = new HashSet<string>(selectedCourses);


var instructorCourses = new HashSet<int>
(instructorToUpdate.Courses.Select(c => c.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}
else
{
if (instructorCourses.Contains(course.CourseID))
{
var courseToRemove =
instructorToUpdate.Courses.Single(
c => c.CourseID ==
course.CourseID);
instructorToUpdate.Courses.Remove(courseToRemove);
}
}
}
}
}
}

Le code précédent :

Obtient l’entité Instructor actuelle de la base de données à l’aide d’un


chargement hâtif des propriétés de navigation OfficeAssignment et Courses .
Met à jour l’entité Instructor récupérée avec les valeurs du classeur de modèles.
TryUpdateModelAsync empêche la survalidation.
Si l’emplacement du bureau est vide, définit Instructor.OfficeAssignment avec la
valeur null. Lorsque Instructor.OfficeAssignment est null, la ligne correspondante
dans la table OfficeAssignment est supprimée.
Appelle PopulateAssignedCourseData dans OnGetAsync afin de fournir des
informations pour les cases à cocher à l’aide de la classe de modèle de vue
AssignedCourseData .

Appelle UpdateInstructorCourses dans OnPostAsync pour appliquer les


informations des cases à cocher à l’entité de formateur en cours de modification.
Appelle PopulateAssignedCourseData et UpdateInstructorCourses dans
OnPostAsync si TryUpdateModelAsync échoue. Ces appels de méthode restaurent

les données de cours affectées qui ont été entrées dans la page lorsqu’elles sont
réaffichées avec un message d’erreur.

Comme la page Razor n’a pas de collection d’entités Course, le classeur de modèles ne
peut pas mettre à jour automatiquement la propriété de navigation Courses . Au lieu
d’utiliser le classeur de modèles pour mettre à jour la propriété de navigation Courses ,
cela est fait dans la nouvelle méthode UpdateInstructorCourses . Par conséquent, vous
devez exclure la propriété Courses de la liaison de modèle. Ceci ne nécessite aucune
modification du code qui appelle TryUpdateModelAsync, car vous utilisez la surcharge
avec des propriétés déclarées et Courses n’est pas dans la liste des éléments à inclure.

Si aucune case n’a été cochée, le code de UpdateInstructorCourses initialise la


instructorToUpdate.Courses avec une collection vide et retourne :

C#

if (selectedCourses == null)
{
instructorToUpdate.Courses = new List<Course>();
return;
}

Ensuite, le code exécute une boucle dans tous les cours de la base de données, et
compare les cours actuellement affectés au formateur à ceux qui sont sélectionnés dans
la vue. Pour faciliter des recherches efficaces, les deux dernières collections sont
stockées dans des objets HashSet .

Si la case pour un cours a été cochée mais que le cours n’est pas dans la propriété de
navigation Instructor.Courses , le cours est ajouté à la collection dans la propriété de
navigation.

C#
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.Courses.Add(course);
}
}

Si la case pour un cours n’est pas cochée mais que le cours est dans la propriété de
navigation Instructor.Courses , le cours est supprimé de la propriété de navigation.

C#

else
{
if (instructorCourses.Contains(course.CourseID))
{
var courseToRemove = instructorToUpdate.Courses.Single(
c => c.CourseID == course.CourseID);
instructorToUpdate.Courses.Remove(courseToRemove);
}
}

Mettre à jour la page Razor de modification de formateur


Mettez à jour Pages/Instructors/Edit.cshtml à l’aide du code suivant :

CSHTML

@page
@model ContosoUniversity.Pages.Instructors.EditModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Instructor</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<input type="hidden" asp-for="Instructor.ID" />
<div class="form-group">
<label asp-for="Instructor.LastName" class="control-label">
</label>
<input asp-for="Instructor.LastName" class="form-control" />
<span asp-validation-for="Instructor.LastName" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.FirstMidName" class="control-
label"></label>
<input asp-for="Instructor.FirstMidName" class="form-
control" />
<span asp-validation-for="Instructor.FirstMidName"
class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.HireDate" class="control-label">
</label>
<input asp-for="Instructor.HireDate" class="form-control" />
<span asp-validation-for="Instructor.HireDate" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.OfficeAssignment.Location"
class="control-label"></label>
<input asp-for="Instructor.OfficeAssignment.Location"
class="form-control" />
<span asp-validation-
for="Instructor.OfficeAssignment.Location" class="text-danger" />
</div>
<div class="form-group">
<div class="table">
<table>
<tr>
@{
int cnt = 0;

foreach (var course in


Model.AssignedCourseDataList)
{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ?
"checked=\"checked\"" : "")) />
@course.CourseID @:
@course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>

<div>
<a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Le code précédent crée une table HTML avec trois colonnes. Chaque colonne a une case
à cocher et une légende contenant le numéro et le titre du cours. Les cases à cocher ont
toutes le même nom (« selectedCourses »). L’utilisation du même nom signale au
classeur de modèles qu’il doit les traiter en tant que groupe. L’attribut de valeur de
chaque case à cocher est défini sur CourseID . Quand la page est publiée, le classeur de
modèles passe un tableau composé des valeurs CourseID correspondant uniquement
aux cases à cocher sélectionnées.

Lors de l’affichage initial des cases à cocher, les cours affectés au formateur sont cochés.

Remarque : L’approche adoptée ici pour modifier les données de cours des formateurs
fonctionne bien quand il y a un nombre limité de cours. Pour les collections qui sont
beaucoup plus grandes, une interface utilisateur différente et une autre méthode de
mise à jour seraient plus utiles et plus efficaces.

Exécutez l’application et testez la page de modification du formateur qui vient d’être


mise à jour. Changez certaines affectations de cours. Les modifications sont répercutées
dans la page Index.

Mettre à jour la page de création du formateur


Mettez à jour le modèle de la page de création de formateur avec du code similaire à
celui de la page de modification :

C#

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ContosoUniversity.Pages.Instructors
{
public class CreateModel : InstructorCoursesPageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
private readonly ILogger<InstructorCoursesPageModel> _logger;

public CreateModel(SchoolContext context,


ILogger<InstructorCoursesPageModel> logger)
{
_context = context;
_logger = logger;
}

public IActionResult OnGet()


{
var instructor = new Instructor();
instructor.Courses = new List<Course>();

// Provides an empty collection for the foreach loop


// foreach (var course in Model.AssignedCourseDataList)
// in the Create Razor page.
PopulateAssignedCourseData(_context, instructor);
return Page();
}

[BindProperty]
public Instructor Instructor { get; set; }

public async Task<IActionResult> OnPostAsync(string[]


selectedCourses)
{
var newInstructor = new Instructor();

if (selectedCourses.Length > 0)
{
newInstructor.Courses = new List<Course>();
// Load collection with one DB call.
_context.Courses.Load();
}

// Add selected Courses courses to the new instructor.


foreach (var course in selectedCourses)
{
var foundCourse = await
_context.Courses.FindAsync(int.Parse(course));
if (foundCourse != null)
{
newInstructor.Courses.Add(foundCourse);
}
else
{
_logger.LogWarning("Course {course} not found", course);
}
}

try
{
if (await TryUpdateModelAsync<Instructor>(
newInstructor,
"Instructor",
i => i.FirstMidName, i => i.LastName,
i => i.HireDate, i => i.OfficeAssignment))
{
_context.Instructors.Add(newInstructor);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return RedirectToPage("./Index");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}

PopulateAssignedCourseData(_context, newInstructor);
return Page();
}
}
}

Le code précédent :

Ajoute la journalisation pour les messages d’avertissement et d’erreur.

Appelle Load, qui récupère tous les cours d’un appel de base de données. Pour les
petites collections, il s’agit d’une optimisation lors de l’utilisation de FindAsync.
FindAsync retourne l’entité suivie sans demande à la base de données.

C#

public async Task<IActionResult> OnPostAsync(string[] selectedCourses)


{
var newInstructor = new Instructor();

if (selectedCourses.Length > 0)
{
newInstructor.Courses = new List<Course>();
// Load collection with one DB call.
_context.Courses.Load();
}

// Add selected Courses courses to the new instructor.


foreach (var course in selectedCourses)
{
var foundCourse = await
_context.Courses.FindAsync(int.Parse(course));
if (foundCourse != null)
{
newInstructor.Courses.Add(foundCourse);
}
else
{
_logger.LogWarning("Course {course} not found", course);
}
}

try
{
if (await TryUpdateModelAsync<Instructor>(
newInstructor,
"Instructor",
i => i.FirstMidName, i => i.LastName,
i => i.HireDate, i => i.OfficeAssignment))
{
_context.Instructors.Add(newInstructor);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return RedirectToPage("./Index");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}

PopulateAssignedCourseData(_context, newInstructor);
return Page();
}

_context.Instructors.Add(newInstructor) crée un Instructor à l’aide de relations

plusieurs-à-plusieurs sans mapper explicitement la table de jointure. Plusieurs-à-


plusieurs a été ajouté dans EF 5.0.

Testez la page de création de l'instructeur.

Mettez à jour le modèle de la page Razor de création de formateur avec du code


similaire à celui de la page de modification :

CSHTML

@page
@model ContosoUniversity.Pages.Instructors.CreateModel

@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>

<h4>Instructor</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Instructor.LastName" class="control-label">
</label>
<input asp-for="Instructor.LastName" class="form-control" />
<span asp-validation-for="Instructor.LastName" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.FirstMidName" class="control-
label"></label>
<input asp-for="Instructor.FirstMidName" class="form-
control" />
<span asp-validation-for="Instructor.FirstMidName"
class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.HireDate" class="control-label">
</label>
<input asp-for="Instructor.HireDate" class="form-control" />
<span asp-validation-for="Instructor.HireDate" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Instructor.OfficeAssignment.Location"
class="control-label"></label>
<input asp-for="Instructor.OfficeAssignment.Location"
class="form-control" />
<span asp-validation-
for="Instructor.OfficeAssignment.Location" class="text-danger" />
</div>
<div class="form-group">
<div class="table">
<table>
<tr>
@{
int cnt = 0;

foreach (var course in


Model.AssignedCourseDataList)
{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ?
"checked=\"checked\"" : "")) />
@course.CourseID @:
@course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary"
/>
</div>
</form>
</div>
</div>

<div>
<a asp-page="Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Mettre à jour la page de suppression du


formateur
Mettez à jour Pages/Instructors/Delete.cshtml.cs à l’aide du code suivant :

C#

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Instructors
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public DeleteModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}

[BindProperty]
public Instructor Instructor { get; set; }

public async Task<IActionResult> OnGetAsync(int? id)


{
if (id == null)
{
return NotFound();
}

Instructor = await _context.Instructors.FirstOrDefaultAsync(m =>


m.ID == id);

if (Instructor == null)
{
return NotFound();
}
return Page();
}

public async Task<IActionResult> OnPostAsync(int? id)


{
if (id == null)
{
return NotFound();
}

Instructor instructor = await _context.Instructors


.Include(i => i.Courses)
.SingleAsync(i => i.ID == id);

if (instructor == null)
{
return RedirectToPage("./Index");
}

var departments = await _context.Departments


.Where(d => d.InstructorID == id)
.ToListAsync();
departments.ForEach(d => d.InstructorID = null);

_context.Instructors.Remove(instructor);

await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
Le code précédent apporte les modifications suivantes :

Utilise un chargement hâtif pour la propriété de navigation Courses . Les Courses


doivent être inclus ou ils ne seront pas supprimés lorsque l'instructeur est
supprimé. Pour éviter d’avoir à les lire, configurez la suppression en cascade dans
la base de données.

Si le formateur à supprimer est attribué en tant qu’administrateur d’un


département, supprime l’attribution de l'instructeur de ces départements.

Exécutez l’application et testez la page de suppression.

Étapes suivantes
Tutoriel précédent Tutoriel suivant

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 8, Pages Razor avec EF Core dans
ASP.NET Core - Concurrence
Article • 30/11/2023

Tom Dykstra et Jon P Smith

L’application web Contoso University montre comment créer des applications web
Pages Razor avec EF Core et Visual Studio. Pour obtenir des informations sur la série de
didacticiels, consultez le premier didacticiel.

Si vous rencontrez des problèmes que vous ne pouvez pas résoudre, téléchargez
l’application finale et comparez ce code à celui que vous avez créé en suivant le
tutoriel.

Ce tutoriel montre comment gérer les conflits quand plusieurs utilisateurs mettent à jour
une entité en même temps.

Conflits d’accès concurrentiel


Un conflit d’accès concurrentiel se produit quand :

Un utilisateur accède à la page de modification d’une entité.


Un autre utilisateur met à jour la même entité avant que la modification du
premier utilisateur soit écrite dans la base de données.

Si la détection de l’accès concurrentiel n’est pas activée, quiconque qui met à jour la
base de données en dernier remplace les modifications de l’autre utilisateur. Si ce risque
est acceptable, le coût de programmation de l’accès concurrentiel peut l’emporter sur
l’avantage.

Accès concurrentiel pessimiste


Une façon d’éviter les conflits d’accès concurrentiel consiste à utiliser des verrous de
base de données. Ceci est appelé « accès concurrentiel pessimiste ». Avant de lire une
ligne de base de données qu’elle entend mettre à jour, une application demande un
verrou. Dès lors qu’une ligne est verrouillée pour l’accès aux mises à jour, aucun autre
utilisateur n’est autorisé à verrouiller la ligne tant que le premier verrou n’est pas libéré.

La gestion des verrous présente des inconvénients. Elle peut être difficile à programmer
et peut occasionner des problèmes de performances à mesure que le nombre
d’utilisateurs augmente. Entity Framework Core ne fournit aucune prise en charge
intégrée de la concurrence pessimiste.

Accès concurrentiel optimiste


L’accès concurrentiel optimiste autorise la survenance des conflits d’accès concurrentiel,
et réagit correctement quand ils surviennent. Par exemple, Jane consulte la page de
modification de département et change le montant de « Budget » pour le département
« English » en le faisant passer de 350 000,00 $ à 0,00 $.

Avant que Jane clique sur Save, John consulte la même page et change le champ Start
Date de 01/09/2007 en 01/09/2013.
Jane clique d’abord sur Save et voit sa modification prendre effet, puisque le navigateur
affiche la page d’index avec un montant de budget égal à zéro.

John clique sur Save dans une page Edit qui affiche toujours un budget de 350 000,00 $.
Ce qui se passe ensuite dépend de la façon dont vous gérez les conflits d’accès
concurrentiel :

Effectuez le suivi des propriétés modifiées par un utilisateur et mettez à jour


seulement les colonnes correspondantes dans la base de données.

Dans le scénario, aucune donnée ne serait perdue. Des propriétés différentes ont
été mises à jour par les deux utilisateurs. La prochaine fois que quelqu’un
consultera le département « English », il verra à la fois les modifications de Jane et
de John. Cette méthode de mise à jour peut réduire le nombre de conflits
susceptibles d’entraîner une perte de données. Cette approche présente quelques
inconvénients :
Elle ne peut pas éviter une perte de données si des modifications concurrentes
sont apportées à la même propriété.
Elle n’est généralement pas pratique dans une application web. Elle nécessite la
tenue à jour d’un état significatif afin d’effectuer le suivi de toutes les valeurs
récupérées et des nouvelles valeurs. La maintenance de grandes quantités d’état
peut affecter les performances de l’application.
Elle peut augmenter la complexité de l’application par rapport à la détection de
l’accès concurrentiel sur une entité.

Laissez les modifications de John remplacer les modifications de Jane.

La prochaine fois que quelqu’un consultera le département « English », il verra la


date 01/09/2013 et la valeur 350 000,00 $ récupérée. Cette approche est un
scénario Priorité au client ou Priorité au dernier. Toutes les valeurs du client sont
prioritaires par rapport au contenu du magasin de données. Le code généré
n’effectue aucune gestion de concurrence. Client Wins se produit
automatiquement.

Empêchez les modifications de John de faire l’objet d’une mise à jour dans la base
de données. En règle générale, l’application :
affiche un message d’erreur ;
indique l’état actuel des données ;
autorise l’utilisateur à réappliquer les modifications.

Il s’agit alors d’un scénario Priorité au magasin. Les valeurs du magasin de données
sont prioritaires par rapport à celles soumises par le client. Le scénario Store Wins
est utilisé dans ce tutoriel. Cette méthode garantit qu’aucune modification n’est
remplacée sans qu’un utilisateur soit averti.

Détection de conflit dans EF Core


Les propriétés configurées en tant que jetons d’accès concurrentiel sont utilisées pour
implémenter un contrôle d’accès concurrentiel optimiste. Lorsqu’une opération de mise
à jour ou de suppression est déclenchée par SaveChanges ou SaveChangesAsync, la
valeur du jeton d’accès concurrentiel dans la base de données est comparée à la valeur
d’origine lue par EF Core :

Si les valeurs correspondent, l’opération peut s’effectuer.


Si les valeurs ne correspondent pas, EF Core suppose qu’un autre utilisateur a
effectué une opération conflictuelle, abandonne la transaction en cours et lève un
DbUpdateConcurrencyException.
Un autre utilisateur ou processus effectuant une opération en conflit avec l’opération en
cours est appelé conflit d’accès concurrentiel.

Sur les bases de données relationnelles, EF Core vérifie la valeur du jeton d’accès
concurrentiel dans la clause WHERE des instructions UPDATE et DELETE pour détecter un
conflit d’accès concurrentiel.

Le modèle de données doit être configuré pour activer la détection des conflits en
incluant une colonne de suivi qui peut être utilisée pour déterminer quand une ligne a
été modifiée. EF fournit deux approches pour les jetons d’accès concurrentiel :

Application de [ConcurrencyCheck] ou IsConcurrencyToken à une propriété sur le


modèle. Cette approche n’est pas recommandée. Pour plus d'informations,
consultez Jetons d’accès concurrentiel dans EF Core.

Application de TimestampAttribute ou IsRowVersion à un jeton d’accès


concurrentiel dans le modèle. Il s’agit de l’approche utilisée dans ce tutoriel.

L’approche SQL Server et les détails de l’implémentation SQLite sont légèrement


différents. Un fichier de différences répertoriant les différences s’affiche plus loin dans le
tutoriel. L’onglet Visual Studio affiche l’approche SQL Server. L’onglet Visual Studio Code
montre l’approche pour les bases de données non SQL Server, telles que SQLite.

Visual Studio

Dans le modèle, incluez une colonne de suivi utilisée pour déterminer quand
une ligne a été modifiée.
Appliquez le TimestampAttribute à la propriété d’accès concurrentiel.

Mettez à jour le fichier Models/Department.cs avec le code en surbrillance suivant :

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Name { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }

public int? InstructorID { get; set; }

[Timestamp]
public byte[] ConcurrencyToken { get; set; }

public Instructor Administrator { get; set; }


public ICollection<Course> Courses { get; set; }
}
}

Le TimestampAttribute est ce qui identifie la colonne en tant que colonne de suivi


d’accès concurrentiel. L’API Fluent est un autre moyen de spécifier la propriété de
suivi :

C#

modelBuilder.Entity<Department>()
.Property<byte[]>("ConcurrencyToken")
.IsRowVersion();

L’attribut [Timestamp] sur une propriété d’entité génère le code suivant dans la
méthode ModelBuilder :

C#

b.Property<byte[]>("ConcurrencyToken")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("rowversion");

Le code précédent :

Définit le type de propriété ConcurrencyToken sur le groupe d’octets. byte[]


est le type requis pour SQL Server.
Appelle IsConcurrencyToken. IsConcurrencyToken configure la propriété en
tant que jeton d’accès concurrentiel. Lors des mises à jour, la valeur du jeton
d’accès concurrentiel dans la base de données est comparée à la valeur
d’origine pour s’assurer qu’elle n’a pas changé depuis que l’instance a été
récupérée à partir de la base de données. Si elle a changé, un
DbUpdateConcurrencyException est levé et les modifications ne sont pas
appliquées.
Appelle ValueGeneratedOnAddOrUpdate, qui configure la propriété
ConcurrencyToken pour qu’une valeur soit générée automatiquement lors de

l’ajout ou de la mise à jour d’une entité.


HasColumnType("rowversion") définit le type de colonne dans la base de

données SQL Server sur rowversion.

Le code suivant montre une partie du T-SQL généré par EF Core quand le nom
Department est mis à jour :

SQL

SET NOCOUNT ON;


UPDATE [Departments] SET [Name] = @p0
WHERE [DepartmentID] = @p1 AND [ConcurrencyToken] = @p2;
SELECT [ConcurrencyToken]
FROM [Departments]
WHERE @@ROWCOUNT = 1 AND [DepartmentID] = @p1;

Le code en surbrillance ci-dessus montre la clause WHERE contenant


ConcurrencyToken . Si la base de données ConcurrencyToken n’est pas égale au

paramètre ConcurrencyToken @p2 , aucune ligne n’est mise à jour.

Le code en surbrillance suivant montre le T-SQL qui vérifie qu’une seule ligne a été
mise à jour :

SQL

SET NOCOUNT ON;


UPDATE [Departments] SET [Name] = @p0
WHERE [DepartmentID] = @p1 AND [ConcurrencyToken] = @p2;
SELECT [ConcurrencyToken]
FROM [Departments]
WHERE @@ROWCOUNT = 1 AND [DepartmentID] = @p1;

@@ROWCOUNT retourne le nombre de lignes affectées par la dernière instruction.


Si aucune ligne n’est mise à jour, EF Core lève un DbUpdateConcurrencyException .

Ajouter une migration


L’ajout de la propriété ConcurrencyToken change le modèle de données, ce qui nécessite
une migration.

Créez le projet.

Visual Studio

Exécutez les commandes suivantes dans PMC :

PowerShell

Add-Migration RowVersion
Update-Database

Les commandes précédentes :

Crée le fichier de migration Migrations/{time stamp}_RowVersion.cs .


Met à jour le fichier Migrations/SchoolContextModelSnapshot.cs . La mise à jour
ajoute le code suivant à la méthode BuildModel :

C#

b.Property<byte[]>("ConcurrencyToken")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("rowversion");

Générer automatiquement des modèles de


pages Department
Visual Studio

Suivez les instructions dans Générer automatiquement des modèles de pages


Student avec les exceptions suivantes :

Créez un dossier Pages/Departments.


Utilisez Department pour la classe de modèle.
Utilisez la classe de contexte existante au lieu d’en créer une nouvelle.
Ajouter une classe utilitaire
Dans le dossier du projet, créez la classe Utility avec le code suivant :

Visual Studio

C#

namespace ContosoUniversity
{
public static class Utility
{
public static string GetLastChars(byte[] token)
{
return token[7].ToString();
}
}
}

La classe Utility fournit la méthode GetLastChars utilisée pour afficher les derniers
caractères du jeton d’accès concurrentiel. Le code suivant montre le code qui fonctionne
à la fois avec SQLite et SQL Server :

C#

#if SQLiteVersion
using System;

namespace ContosoUniversity
{
public static class Utility
{
public static string GetLastChars(Guid token)
{
return token.ToString().Substring(
token.ToString().Length - 3);
}
}
}
#else
namespace ContosoUniversity
{
public static class Utility
{
public static string GetLastChars(byte[] token)
{
return token[7].ToString();
}
}
}
#endif

La #if SQLiteVersion directive de préprocesseur isole les différences entre les versions
SQLite et SQL Server et aide :

L’auteur conserve une base de code pour les deux versions.


Les développeurs SQLite déploient l’application sur Azure et utilisent SQL Azure.

Créez le projet.

Mettre à jour la page Index


L’outil de génération de modèles automatique créé une colonne ConcurrencyToken pour
la page Index, mais ce champ ne s’affiche pas dans une application de production. Dans
ce tutoriel, la dernière partie du ConcurrencyToken est affichée pour montrer comment la
gestion de l’accès concurrentiel fonctionne. Il n’est pas garanti que la dernière partie soit
unique par elle-même.

Mettre à jour la page Pages\Departments\Index.cshtml :

Remplacez Index par Departments.


Modifiez le code contenant ConcurrencyToken pour afficher uniquement les
derniers caractères.
Remplacez FirstMidName par FullName .

Le code suivant affiche la page mise à jour :

CSHTML

@page
@model ContosoUniversity.Pages.Departments.IndexModel

@{
ViewData["Title"] = "Departments";
}

<h2>Departments</h2>

<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Department[0].Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Department[0].Budget)
</th>
<th>
@Html.DisplayNameFor(model => model.Department[0].StartDate)
</th>
<th>
@Html.DisplayNameFor(model =>
model.Department[0].Administrator)
</th>
<th>
Token
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Department)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Budget)
</td>
<td>
@Html.DisplayFor(modelItem => item.StartDate)
</td>
<td>
@Html.DisplayFor(modelItem =>
item.Administrator.FullName)
</td>
<td>
@Utility.GetLastChars(item.ConcurrencyToken)
</td>
<td>
<a asp-page="./Edit" asp-route-
id="@item.DepartmentID">Edit</a> |
<a asp-page="./Details" asp-route-
id="@item.DepartmentID">Details</a> |
<a asp-page="./Delete" asp-route-
id="@item.DepartmentID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Mettre à jour le modèle de page de
modification
Mettez à jour Pages/Departments/Edit.cshtml.cs à l’aide du code suivant :

Visual Studio

C#

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Departments
{
public class EditModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Department Department { get; set; }
// Replace ViewData["InstructorID"]
public SelectList InstructorNameSL { get; set; }

public async Task<IActionResult> OnGetAsync(int id)


{
Department = await _context.Departments
.Include(d => d.Administrator) // eager loading
.AsNoTracking() // tracking not required
.FirstOrDefaultAsync(m => m.DepartmentID == id);

if (Department == null)
{
return NotFound();
}

// Use strongly typed data rather than ViewData.


InstructorNameSL = new SelectList(_context.Instructors,
"ID", "FirstMidName");

return Page();
}

public async Task<IActionResult> OnPostAsync(int id)


{
if (!ModelState.IsValid)
{
return Page();
}

// Fetch current department from DB.


// ConcurrencyToken may have changed.
var departmentToUpdate = await _context.Departments
.Include(i => i.Administrator)
.FirstOrDefaultAsync(m => m.DepartmentID == id);

if (departmentToUpdate == null)
{
return HandleDeletedDepartment();
}

// Set ConcurrencyToken to value read in OnGetAsync


_context.Entry(departmentToUpdate).Property(
d => d.ConcurrencyToken).OriginalValue =
Department.ConcurrencyToken;

if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"Department",
s => s.Name, s => s.StartDate, s => s.Budget, s =>
s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues =
(Department)exceptionEntry.Entity;
var databaseEntry =
exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable
to save. " +
"The department was deleted by another
user.");
return Page();
}

var dbValues = (Department)databaseEntry.ToObject();


await SetDbErrorMessage(dbValues, clientValues,
_context);
// Save the current ConcurrencyToken so next
postback
// matches unless an new concurrency issue happens.
Department.ConcurrencyToken =
(byte[])dbValues.ConcurrencyToken;
// Clear the model error for the next postback.
ModelState.Remove($"{nameof(Department)}.
{nameof(Department.ConcurrencyToken)}");
}
}

InstructorNameSL = new SelectList(_context.Instructors,


"ID", "FullName", departmentToUpdate.InstructorID);

return Page();
}

private IActionResult HandleDeletedDepartment()


{
// ModelState contains the posted data because of the
deletion error
// and overides the Department instance values when
displaying Page().
ModelState.AddModelError(string.Empty,
"Unable to save. The department was deleted by another
user.");
InstructorNameSL = new SelectList(_context.Instructors,
"ID", "FullName", Department.InstructorID);
return Page();
}

private async Task SetDbErrorMessage(Department dbValues,


Department clientValues, SchoolContext context)
{

if (dbValues.Name != clientValues.Name)
{
ModelState.AddModelError("Department.Name",
$"Current value: {dbValues.Name}");
}
if (dbValues.Budget != clientValues.Budget)
{
ModelState.AddModelError("Department.Budget",
$"Current value: {dbValues.Budget:c}");
}
if (dbValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("Department.StartDate",
$"Current value: {dbValues.StartDate:d}");
}
if (dbValues.InstructorID != clientValues.InstructorID)
{
Instructor dbInstructor = await _context.Instructors
.FindAsync(dbValues.InstructorID);
ModelState.AddModelError("Department.InstructorID",
$"Current value: {dbInstructor?.FullName}");
}

ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in
the database "
+ "have been displayed. If you still want to edit this
record, click "
+ "the Save button again.");
}
}
}

Les mises à jour de l’accès concurrentiel


OriginalValue est mise à jour avec la valeur ConcurrencyToken de l’entité au moment où
elle a été récupérée dans la méthode OnGetAsync . EF Core génère une commande SQL
UPDATE avec une clause WHERE contenant la valeur ConcurrencyToken d’origine. Si aucune

ligne n’est affectée par la commande UPDATE , une exception


DbUpdateConcurrencyException est levée. Aucune ligne n’est affectée par la commande
UPDATE quand aucune ligne n’a la valeur ConcurrencyToken d’origine.

Visual Studio

C#

public async Task<IActionResult> OnPostAsync(int id)


{
if (!ModelState.IsValid)
{
return Page();
}

// Fetch current department from DB.


// ConcurrencyToken may have changed.
var departmentToUpdate = await _context.Departments
.Include(i => i.Administrator)
.FirstOrDefaultAsync(m => m.DepartmentID == id);

if (departmentToUpdate == null)
{
return HandleDeletedDepartment();
}
// Set ConcurrencyToken to value read in OnGetAsync
_context.Entry(departmentToUpdate).Property(
d => d.ConcurrencyToken).OriginalValue =
Department.ConcurrencyToken;

Dans le code en surbrillance précédent :

La valeur dans Department.ConcurrencyToken est la valeur lorsque l’entité a été


extraite dans la requête Get pour la page Edit . La valeur est fournie à la méthode
OnPost par un champ masqué de la page Razor qui affiche l’entité à modifier. La

valeur du champ masqué est copiée dans Department.ConcurrencyToken par le


classeur de modèles.
OriginalValue est ce que EF Core utilise dans la clause WHERE . Avant l’exécution de

la ligne de code mise en surbrillance :


OriginalValue a la valeur qui se trouvait dans la base de données quand

FirstOrDefaultAsync a été appelée dans cette méthode.

Cette valeur peut être différente de celle affichée dans la page Modifier.
Le code en surbrillance garantit qu’EF Core utilise la valeur ConcurrencyToken
d’origine de l’entité Department affichée dans la clause UPDATE de l’instruction SQL
WHERE .

Le code suivant montre le modèle Department . Department est initialisé dans la :

méthode OnGetAsync par la requête EF.


méthode OnPostAsync par le champ masqué dans la page Razor à l’aide de la
liaison de données :

Visual Studio

C#

public class EditModel : PageModel


{
private readonly ContosoUniversity.Data.SchoolContext _context;

public EditModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Department Department { get; set; }
// Replace ViewData["InstructorID"]
public SelectList InstructorNameSL { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Department = await _context.Departments
.Include(d => d.Administrator) // eager loading
.AsNoTracking() // tracking not required
.FirstOrDefaultAsync(m => m.DepartmentID == id);

if (Department == null)
{
return NotFound();
}

// Use strongly typed data rather than ViewData.


InstructorNameSL = new SelectList(_context.Instructors,
"ID", "FirstMidName");

return Page();
}

public async Task<IActionResult> OnPostAsync(int id)


{
if (!ModelState.IsValid)
{
return Page();
}

// Fetch current department from DB.


// ConcurrencyToken may have changed.
var departmentToUpdate = await _context.Departments
.Include(i => i.Administrator)
.FirstOrDefaultAsync(m => m.DepartmentID == id);

if (departmentToUpdate == null)
{
return HandleDeletedDepartment();
}

// Set ConcurrencyToken to value read in OnGetAsync


_context.Entry(departmentToUpdate).Property(
d => d.ConcurrencyToken).OriginalValue =
Department.ConcurrencyToken;

Le code précédent montre que la valeur ConcurrencyToken de l’entité Department de la


requête HTTP POST est définie sur la valeur ConcurrencyToken de la requête HTTP GET .

Quand une erreur d’accès concurrentiel se produit, le code en surbrillance suivant


obtient les valeurs du client (valeurs publiées dans cette méthode) et les valeurs de la
base de données.

C#
if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"Department",
s => s.Name, s => s.StartDate, s => s.Budget, s => s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The department was deleted by another user.");
return Page();
}

var dbValues = (Department)databaseEntry.ToObject();


await SetDbErrorMessage(dbValues, clientValues, _context);

// Save the current ConcurrencyToken so next postback


// matches unless an new concurrency issue happens.
Department.ConcurrencyToken = dbValues.ConcurrencyToken;
// Clear the model error for the next postback.
ModelState.Remove($"{nameof(Department)}.
{nameof(Department.ConcurrencyToken)}");
}

Le code suivant ajoute un message d’erreur personnalisé pour chaque colonne dont les
valeurs dans la base de données sont différentes de celles envoyées à OnPostAsync :

C#

private async Task SetDbErrorMessage(Department dbValues,


Department clientValues, SchoolContext context)
{

if (dbValues.Name != clientValues.Name)
{
ModelState.AddModelError("Department.Name",
$"Current value: {dbValues.Name}");
}
if (dbValues.Budget != clientValues.Budget)
{
ModelState.AddModelError("Department.Budget",
$"Current value: {dbValues.Budget:c}");
}
if (dbValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("Department.StartDate",
$"Current value: {dbValues.StartDate:d}");
}
if (dbValues.InstructorID != clientValues.InstructorID)
{
Instructor dbInstructor = await _context.Instructors
.FindAsync(dbValues.InstructorID);
ModelState.AddModelError("Department.InstructorID",
$"Current value: {dbInstructor?.FullName}");
}

ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in the database
"
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again.");
}

Le code en surbrillance suivant affecte à ConcurrencyToken la nouvelle valeur récupérée


à partir de la base de données. La prochaine fois que l’utilisateur cliquera sur Save,
seules les erreurs d’accès concurrentiel qui se sont produites depuis le dernier affichage
de la page Edit seront interceptées.

C#

if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"Department",
s => s.Name, s => s.StartDate, s => s.Budget, s => s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " +
"The department was deleted by another user.");
return Page();
}

var dbValues = (Department)databaseEntry.ToObject();


await SetDbErrorMessage(dbValues, clientValues, _context);

// Save the current ConcurrencyToken so next postback


// matches unless an new concurrency issue happens.
Department.ConcurrencyToken = dbValues.ConcurrencyToken;
// Clear the model error for the next postback.
ModelState.Remove($"{nameof(Department)}.
{nameof(Department.ConcurrencyToken)}");
}

L’instruction ModelState.Remove est nécessaire, car ModelState contient l’ancienne


valeur ConcurrencyToken . Dans la Page Razor, la valeur ModelState d’un champ est
prioritaire par rapport aux valeurs de propriétés du modèle quand les deux sont
présentes.

Différences de code entre SQL Server et SQLite


Voici les différences entre les versions SQL Server et SQLite :

diff

+ using System; // For GUID on SQLite

+ departmentToUpdate.ConcurrencyToken = Guid.NewGuid();

_context.Entry(departmentToUpdate)
.Property(d => d.ConcurrencyToken).OriginalValue =
Department.ConcurrencyToken;

- Department.ConcurrencyToken = (byte[])dbValues.ConcurrencyToken;
+ Department.ConcurrencyToken = dbValues.ConcurrencyToken;

Mettre à jour la page Edit Razor


Mettez à jour Pages/Departments/Edit.cshtml à l’aide du code suivant :

CSHTML

@page "{id:int}"
@model ContosoUniversity.Pages.Departments.EditModel
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>Department</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<input type="hidden" asp-for="Department.DepartmentID" />
<input type="hidden" asp-for="Department.ConcurrencyToken" />
<div class="form-group">
<label>Version</label>
@Utility.GetLastChars(Model.Department.ConcurrencyToken)
</div>
<div class="form-group">
<label asp-for="Department.Name" class="control-label">
</label>
<input asp-for="Department.Name" class="form-control" />
<span asp-validation-for="Department.Name" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Department.Budget" class="control-label">
</label>
<input asp-for="Department.Budget" class="form-control" />
<span asp-validation-for="Department.Budget" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Department.StartDate" class="control-label">
</label>
<input asp-for="Department.StartDate" class="form-control"
/>
<span asp-validation-for="Department.StartDate" class="text-
danger">
</span>
</div>
<div class="form-group">
<label class="control-label">Instructor</label>
<select asp-for="Department.InstructorID" class="form-
control"
asp-items="@Model.InstructorNameSL"></select>
<span asp-validation-for="Department.InstructorID"
class="text-danger">
</span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="./Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Le code précédent :

Il met à jour la directive page en remplaçant @page par @page "{id:int}" .


Ajoute une version de ligne masquée. ConcurrencyToken doit être ajouté afin que la
publication (postback) lie la valeur.
Affiche le dernier octet de ConcurrencyToken à des fins de débogage.
Remplace ViewData par le InstructorNameSL fortement typé.

Tester les conflits d’accès concurrentiel avec la page Edit


Ouvrez deux instances de navigateur de la page Edit sur le département English :

Exécutez l’application et sélectionnez Departments.


Cliquez avec le bouton droit sur le lien hypertexte Edit correspondant au
département English, puis sélectionnez Open in new tab.
Sous le premier onglet, cliquez sur le lien hypertexte Edit correspondant au
département English.

Les deux onglets de navigateur affichent les mêmes informations.

Changez le nom sous le premier onglet de navigateur, puis cliquez sur Save.
Le navigateur affiche la page Index avec la valeur modifiée et un indicateur
ConcurrencyToken mis à jour. Notez l’indicateur ConcurrencyToken mis à jour ; il est

affiché sur la deuxième publication (postback) dans l’autre onglet.

Changez un champ différent sous le deuxième onglet du navigateur.


Cliquez sur Enregistrer. Des messages d’erreur s’affichent pour tous les champs qui ne
correspondent pas aux valeurs de la base de données :
Cette fenêtre de navigateur n’avait pas l’intention de changer le champ Name. Copiez et
collez la valeur actuelle (Languages) dans le champ Name. Appuyez sur Tab. La
validation côté client supprime le message d’erreur.

Cliquez à nouveau sur Enregistrer. La valeur que vous avez entrée sous le deuxième
onglet du navigateur est enregistrée. Les valeurs enregistrées sont visibles dans la page
Index.

Mettre à jour le modèle de page Delete


Mettez à jour Pages/Departments/Delete.cshtml.cs à l’aide du code suivant :

C#

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Departments
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;

public DeleteModel(ContosoUniversity.Data.SchoolContext context)


{
_context = context;
}

[BindProperty]
public Department Department { get; set; }
public string ConcurrencyErrorMessage { get; set; }

public async Task<IActionResult> OnGetAsync(int id, bool?


concurrencyError)
{
Department = await _context.Departments
.Include(d => d.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.DepartmentID == id);

if (Department == null)
{
return NotFound();
}

if (concurrencyError.GetValueOrDefault())
{
ConcurrencyErrorMessage = "The record you attempted to
delete "
+ "was modified by another user after you selected delete.
"
+ "The delete operation was canceled and the current
values in the "
+ "database have been displayed. If you still want to
delete this "
+ "record, click the Delete button again.";
}
return Page();
}

public async Task<IActionResult> OnPostAsync(int id)


{
try
{
if (await _context.Departments.AnyAsync(
m => m.DepartmentID == id))
{
// Department.ConcurrencyToken value is from when the
entity
// was fetched. If it doesn't match the DB, a
// DbUpdateConcurrencyException exception is thrown.
_context.Departments.Remove(Department);
await _context.SaveChangesAsync();
}
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException)
{
return RedirectToPage("./Delete",
new { concurrencyError = true, id = id });
}
}
}
}

La page Delete détecte les conflits d’accès concurrentiel quand l’entité a changé après
avoir été récupérée. Department.ConcurrencyToken est la version de ligne quand l’entité a
été récupérée. Quand EF Core crée la commande SQL DELETE , il inclut une clause WHERE
avec ConcurrencyToken . Si après l’exécution de la commande SQL DELETE aucune ligne
n’est affectée :

La valeur de ConcurrencyToken dans la commande SQL DELETE ne correspond pas à


ConcurrencyToken dans la base de données.

Une exception DbUpdateConcurrencyException est levée.


OnGetAsync est appelée avec concurrencyError .

Mettre à jour la page Razor Delete


Mettez à jour Pages/Departments/Delete.cshtml à l’aide du code suivant :

CSHTML

@page "{id:int}"
@model ContosoUniversity.Pages.Departments.DeleteModel

@{
ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<p class="text-danger">@Model.ConcurrencyErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>


<div>
<h4>Department</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department.Name)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Department.Name)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department.Budget)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Department.Budget)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department.StartDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Department.StartDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department.ConcurrencyToken)
</dt>
<dd class="col-sm-10">
@Utility.GetLastChars(Model.Department.ConcurrencyToken)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department.Administrator)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model =>
model.Department.Administrator.FullName)
</dd>
</dl>

<form method="post">
<input type="hidden" asp-for="Department.DepartmentID" />
<input type="hidden" asp-for="Department.ConcurrencyToken" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>

Le code précédent apporte les modifications suivantes :

Il met à jour la directive page en remplaçant @page par @page "{id:int}" .


Il ajoute un message d’erreur.
Il remplace FirstMidName par FullName dans le champ Administrator.
Il change ConcurrencyToken pour afficher le dernier octet.
Ajoute une version de ligne masquée. ConcurrencyToken doit être ajouté afin que la
publication (postback) lie la valeur.
Tester les conflits d'accès concurrentiel
Créez un département test.

Ouvrez deux instances de navigateur de la page Delete sur le département test :

Exécutez l’application et sélectionnez Departments.


Cliquez avec le bouton droit sur le lien hypertexte Delete correspondant au
département test, puis sélectionnez Open in new tab.
Cliquez sur le lien hypertexte Edit correspondant au département test.

Les deux onglets de navigateur affichent les mêmes informations.

Changez le budget sous le premier onglet de navigateur, puis cliquez sur Save.

Le navigateur affiche la page Index avec la valeur modifiée et un indicateur


ConcurrencyToken mis à jour. Notez l’indicateur ConcurrencyToken mis à jour ; il est

affiché sur la deuxième publication (postback) dans l’autre onglet.

Supprimez le service de test du deuxième onglet. Une erreur d’accès concurrentiel


s’affiche avec les valeurs actuelles de la base de données. Un clic sur Supprimer
supprime l’entité, sauf si ConcurrencyToken a été mis à jour.

Ressources supplémentaires
Jetons d’accès concurrentiel dans EF Core
Gérer l’accès concurrentiel dans EF Core
Débogage d’une source ASP.NET Core 2.x

Étapes suivantes
Ce tutoriel est le dernier de la série. Des rubriques supplémentaires sont abordées dans
la version MVC de cette série de tutoriels.

Tutoriel précédent

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et  Ouvrir un problème de
examiner les problèmes et les documentation
demandes de tirage. Pour plus
d’informations, consultez notre
 Indiquer des commentaires sur
guide du contributeur.
le produit
MVC ASP.NET Core avec EF Core - Série
de tutoriels
Article • 30/11/2023

Ce didacticiel décrit ASP.NET Core MVC et Entity Framework Core avec des contrôleurs
et des vues. Razor Pages est un autre modèle de programmation. Pour le nouveau
développement, nous recommandons Razor Pages plutôt que MVC avec des contrôleurs
et des vues. Consultez la version Razor Pages de ce tutoriel. Chaque tutoriel couvre des
sujets que l’autre ne couvre pas :

Ce tutoriel MVC présente certains éléments que le tutoriel Razor Pages ne présente pas :

Implémenter l’héritage dans le modèle de données


Exécuter des requêtes SQL brutes
Utiliser du code dynamique LINQ pour simplifier le code

Voici certains éléments que le tutoriel Razor Pages présente, contrairement à celui-ci :

Utiliser la méthode Select pour charger les données associées


Bonnes pratiques pour EF.

1. Prise en main
2. Opérations de création, lecture, mise à jour et suppression
3. Tri, filtrage, pagination et regroupement
4. Migrations
5. Créer un modèle de données complexe
6. Lecture de données associées
7. Mise à jour de données associées
8. Gérer les conflits d’accès concurrentiel
9. Héritage
10. Rubriques avancées

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre  Indiquer des commentaires sur
guide du contributeur. le produit
Tutoriel : bien démarrer avec EF Core
dans une application web ASP.NET MVC
Article • 30/11/2023

Par Tom Dykstra et Rick Anderson

Ce didacticiel décrit ASP.NET Core MVC et Entity Framework Core avec des contrôleurs
et des vues. Razor Pages est un autre modèle de programmation. Pour le nouveau
développement, nous recommandons Razor Pages plutôt que MVC avec des contrôleurs
et des vues. Consultez la version Razor Pages de ce tutoriel. Chaque tutoriel couvre des
sujets que l’autre ne couvre pas :

Ce tutoriel MVC présente certains éléments que le tutoriel Razor Pages ne présente pas :

Implémenter l’héritage dans le modèle de données


Exécuter des requêtes SQL brutes
Utiliser du code dynamique LINQ pour simplifier le code

Voici certains éléments que le tutoriel Razor Pages présente, contrairement à celui-ci :

Utiliser la méthode Select pour charger les données associées


Bonnes pratiques pour EF.

L’exemple d’application web Contoso University montre comment créer une application
web ASP.NET Core MVC à l’aide d’Entity Framework (EF) Core et Visual Studio.

L’exemple d’application est un site web pour une université Contoso fictive. Il comprend
des fonctionnalités telles que l’admission des étudiants, la création des cours et les
affectations des formateurs. Ce tutoriel est le premier d’une série qui explique comment
générer l’exemple d’application Contoso University.

Prérequis
Si vous débutez avec ASP.NET Core MVC, consultez la série de tutoriels Bien
démarrer avec ASP.NET Core MVC avant de commencer celui-ci.

Visual Studio 2022 avec la charge de travail Développement web et ASP.NET.


SDK .NET 6.0

Ce tutoriel n’a pas été mis à jour pour ASP.NET Core 6 ou version ultérieure. Les
instructions de ce tutoriel ne fonctionneront pas correctement si vous créez un projet
qui cible ASP.NET Core 6 ou 7. Par exemple, les modèles web ASP.NET Core 6 et 7
utilisent le modèle d’hébergement minimal, qui rassemble Startup.cs et Program.cs en
un seul fichier Program.cs .

Une autre différence introduite dans .NET 6 est la fonctionnalité NRT (types de référence
null). Les modèles de projet activent cette fonctionnalité par défaut. Des problèmes
peuvent se produire. EF considère alors qu’une propriété est requise dans .NET 6, qui
peut accepter la valeur Null dans .NET 5. Par exemple, la page Créer un étudiant échoue
en mode silencieux, sauf si la propriété Enrollments est rendue comme pouvant
accepter la valeur Null ou si la balise d’assistance asp-validation-summary passe de
ModelOnly à All .

Nous vous recommandons d’installer et d’utiliser le Kit de développement logiciel (SDK)


.NET 5 dans le cadre de ce tutoriel. Tant que ce tutoriel n’est pas mis à jour, consultez
Razor Pages avec Entity Framework Core dans ASP.NET Core - Tutoriel 1 sur 8 sur
l’utilisation d’Entity Framework avec ASP.NET Core 6 ou version ultérieure.

Moteurs de base de données


Les instructions Visual Studio utilisent la Base de données locale SQL Server, version de
SQL Server Express qui s’exécute uniquement sur Windows.

Détecter et résoudre des problèmes


Si vous rencontrez un problème que vous ne pouvez pas résoudre, vous pouvez
généralement trouver la solution en comparant votre code au projet terminé . Pour
obtenir la liste des erreurs courantes et comment les résoudre, consultez la section
Dépannage du dernier didacticiel de la série. Si vous n’y trouvez pas ce dont vous avez
besoin, vous pouvez publier une question sur StackOverflow.com pour ASP.NET Core
ou EF Core .

 Conseil

Il s’agit d’une série de 10 didacticiels, dont chacun s’appuie sur les opérations
réalisées dans les précédents. Pensez à enregistrer une copie du projet à la fin de
chaque didacticiel réussi. Ainsi, si vous rencontrez des problèmes, vous pouvez
recommencer à la fin du didacticiel précédent au lieu de revenir au début de la
série entière.

Application web Contoso University


L’application générée dans ces didacticiels est le site web de base d’une université.

Les utilisateurs peuvent afficher et mettre à jour les informations relatives aux étudiants,
aux cours et aux formateurs. Voici quelques écrans dans l’application :
Créer une application web
1. Démarrez Visual Studio et sélectionnez Créer un projet.
2. Dans la boîte de dialogue Créer un nouveau projet, sélectionnez Application web
ASP.NET Core>Next.
3. Dans la boîte de dialogue Configurer votre nouveau projet,
entrez ContosoUniversity pour Nom du projet. Il est important d’utiliser ce nom
exact, en respectant l’utilisation des majuscules, de sorte que chaque namespace
corresponde au moment où le code est copié.
4. Cliquez sur Créer.
5. Dans la boîte de dialogue Créer une nouvelle application web ASP.NET Core,
sélectionnez :
a. .NET Core et ASP.NET Core 5.0 dans les listes déroulantes.
b. Application web ASP.NET Core (modèle-vue-contrôleur).
c. Créer

Configurer le style du site


Quelques changements basiques permettent de définir le menu, la disposition et la
page d’accueil du site.

Ouvrez Views/Shared/_Layout.cshtml et apportez les changements suivants :

Remplacez chaque occurrence de ContosoUniversity par Contoso University . Il y a


trois occurrences.
Ajoutez des entrées de menu pour À propos, Étudiants, Cours, Formateurs et
Departments, et supprimez l’entrée de menu Privacy.

Les modifications précédentes sont mises en évidence dans le code suivant :

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-
light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-controller="Home"
asp-action="Index">Contoso University</a>
<button class="navbar-toggler" type="button" data-
toggle="collapse" data-target=".navbar-collapse" aria-
controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle
navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex
justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Home" asp-action="About">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Students" asp-action="Index">Students</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Courses" asp-action="Index">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Instructors" asp-action="Index">Instructors</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Departments" asp-action="Index">Departments</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">


<div class="container">
&copy; 2020 - Contoso University - <a asp-area="" asp-
controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Dans Views/Home/Index.cshtml , remplacez le contenu du fichier par la balise suivante :

CSHTML

@{
ViewData["Title"] = "Home Page";
}

<div class="jumbotron">
<h1>Contoso University</h1>
</div>
<div class="row">
<div class="col-md-4">
<h2>Welcome to Contoso University</h2>
<p>
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core MVC web application.
</p>
</div>
<div class="col-md-4">
<h2>Build it from scratch</h2>
<p>You can build the application by following the steps in a series
of tutorials.</p>
<p><a class="btn btn-default"
href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the
tutorial &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Download it</h2>
<p>You can download the completed project from GitHub.</p>
<p><a class="btn btn-default"
href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef
-mvc/intro/samples/5cu-final">See project source code &raquo;</a></p>
</div>
</div>

Appuyez sur Ctrl+F5 pour exécuter le projet ou choisissez Déboguer > Exécuter sans
débogage dans le menu. La page d’accueil s’affiche avec des onglets pour les pages
créées dans ce tutoriel.
PackagesEF Core NuGet
Ce didacticiel utilise SQL Server et le package de fournisseur est
Microsoft.EntityFrameworkCore.SqlServer .

Le package EF SQL Server et ses dépendances, Microsoft.EntityFrameworkCore et


Microsoft.EntityFrameworkCore.Relational , fournissent la prise en charge du runtime

pour EF.

Ajoutez le package NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore .


Dans la console du Gestionnaire de package, saisissez les commandes suivantes pour
installer les packages NuGet :

PowerShell

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Le package NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore fournit
l’intergiciel ASP.NET Core pour les pages d’erreur EF Core. Cet intergiciel permet de
détecter et de diagnostiquer les erreurs liées aux migrations EF Core.

Pour obtenir des informations sur les autres fournisseurs de bases de données qui sont
disponibles pour EF Core, consultez Fournisseurs de bases de données.

Créer le modèle de données


Les classes d’entité suivantes sont créées pour cette application :

Les entités précédentes ont les relations suivantes :

Une relation un-à-plusieurs entre les entités Student et Enrollment . Un étudiant


peut être inscrit à autant de cours qu’il le souhaite.
Une relation un-à-plusieurs entre les entités Course et Enrollment . Un cours peut
avoir une quantité illimitée d’élèves inscrits.

Dans les sections suivantes, une classe est créée pour chacune de ces entités.

L’entité Student

Dans le dossier Modèles, créez la classe Student avec le code suivant :


C#

using System;
using System.Collections.Generic;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

La propriété ID devient la colonne de clé primaire (PK) de la table de base de données


qui correspond à cette classe. Par défaut, EF interprète une propriété nommée ID ou
classnameID comme clé primaire. Par exemple, la PK peut être nommée StudentID

plutôt que ID .

La propriété Enrollments est une propriété de navigation. Les propriétés de navigation


contiennent d’autres entités qui sont associées à cette entité. La propriété Enrollments
d’une entité Student :

Contient toutes les entités Enrollment associées à cette entité Student .


Si une ligne Student spécifique de la base de données a deux lignes Enrollment
associées :
La propriété de navigation de Enrollments cette entité Student contient ces
deux entités Enrollment .

Les lignes Enrollment contiennent la valeur PK d’un étudiant dans la colonne de clé
étrangère StudentID (FK).

Si une propriété de navigation peut contenir plusieurs entités :

Le type doit être une liste, par exemple ICollection<T> , List<T> ou HashSet<T> .
Il est possible d’ajouter, de supprimer et de mettre à jour des entités.

Les relations de navigation plusieurs-à-plusieurs et un-à-plusieurs peuvent contenir


plusieurs entités. Quand vous utilisez ICollection<T> , EF crée une collection HashSet<T>
par défaut.
L’entité Enrollment

Dans le dossier Modèles, créez la classe Enrollment avec le code suivant :

C#

namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}

public class Enrollment


{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }

public Course Course { get; set; }


public Student Student { get; set; }
}
}

La propriété EnrollmentID est la clé primaire. Cette entité utilise le modèle classnameID
au lieu de ID seul. L’entité Student a utilisé le modèle ID . Certains développeurs
préfèrent utiliser un modèle dans tout le modèle de données. Dans ce tutoriel, la
variante montre qu’il est possible d’utiliser l’un ou l’autre des modèles. Un prochain
tutoriel montre comment utiliser ID sans nom de classe simplifie l’implémentation de
l’héritage dans le modèle de données.

La propriété Grade est un enum . Le ? après la déclaration de type Grade indique que la
propriété Grade accepte la valeur Null. Une note qui est null est différente d’une note
zéro. null signifie qu’une note n’est pas connue ou n’a pas encore été affectée.
La propriété StudentID est une clé étrangère (FK), et la propriété de navigation
correspondante est Student . Une entité Enrollment est associée à une entité Student .
Par conséquent, la propriété peut seulement détenir une seule entité Student . Cela
diffère de la propriété de navigation Student.Enrollments , qui peut détenir plusieurs
entités Enrollment .

La propriété CourseID est une clé étrangère (FK), et la propriété de navigation


correspondante est Course . Une entité Enrollment est associée à une entité Course .

Entity Framework interprète une propriété comme une propriété FK si elle est nommée
< nom de la propriété de navigation >< nom de la propriété de clé primaire > . Par

exemple, StudentID pour la propriété de navigation Student , puisque la clé primaire


(PK) de l’entité Student est ID . Les propriétés FK peuvent également être nommées
< nom de propriété de clé primaire > . Par exemple, CourseID parce que la PK de l’entité

Course est CourseID .

L’entité Course

Dans le dossier Modèles, créez la classe Course avec le code suivant :

C#

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

La propriété Enrollments est une propriété de navigation. Une entité Course peut être
associée à un nombre quelconque d’entités Enrollment .

L’attribut DatabaseGenerated est expliqué dans un tutoriel ultérieur. Cet attribut permet
d’entrer la PK pour le cours plutôt que de le générer par la base de données.

Créer le contexte de base de données


La classe principale qui coordonne les fonctionnalités d’EF pour un modèle de données
déterminé est la classe du contexte de base de données DbContext. Cette classe est
créée en dérivant de la classe Microsoft.EntityFrameworkCore.DbContext . La classe
dérivée DbContext spécifie les entités qui sont incluses dans le modèle de données. Il est
possible de personnaliser certains comportements EF. Dans ce projet, la classe est
nommée SchoolContext .

Dans le dossier du projet, créez un dossier nommé Data .

Dans le dossier Données, créez une classe SchoolContext avec le code suivant :

C#

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
}
}

Le code précédent crée une propriété DbSet pour chaque jeu d’entités. Dans la
terminologie EF :

Un jeu d’entités correspond généralement à une table de base de données.


Une entité correspond à une ligne dans la table.

Les instructions DbSet<Enrollment> et DbSet<Course> pourraient être omises, cela


fonctionnerait de la même façon. EF les inclurait implicitement pour les raisons
suivantes :

L’entité Student fait référence à l’entité Enrollment .


L’entité Enrollment fait référence à l’entité Course .

Quand la base de données est créée, EF crée des tables dont les noms sont identiques
aux noms de propriété DbSet . Les noms des propriétés pour les collections sont
généralement au pluriel. Par exemple, Students plutôt que Student . Les développeurs
ne sont pas tous d’accord sur la nécessité d’utiliser des noms de table au pluriel. Pour
ces tutoriels, le comportement par défaut est remplacé en spécifiant des noms de tables
au singulier dans le DbContext . Pour ce faire, ajoutez le code en surbrillance suivant
après la dernière propriété DbSet.

C#

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}

Inscrivez SchoolContext
ASP.NET Core comprend l’injection de dépendances. Des services, tels que le contexte
de base de données EF, sont inscrits avec l’injection de dépendance au démarrage de
l’application. Ces services sont affectés aux composants qui les nécessitent, tels que les
contrôleurs MVC, par le biais de paramètres de constructeur. Le code de constructeur de
contrôleur qui obtient une instance de contexte est montré plus loin dans ce tutoriel.

Pour inscrire SchoolContext en tant que service, ouvrez Startup.cs et ajoutez les lignes
en surbrillance à la méthode ConfigureServices .

C#

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ContosoUniversity
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<SchoolContext>(options =>

options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
);

services.AddControllersWithViews();
}

Le nom de la chaîne de connexion est transmis au contexte en appelant une méthode


sur un objet DbContextOptionsBuilder . Pour le développement local, le système de
configuration ASP.NET Core lit la chaîne de connexion à partir du fichier
appsettings.json .

Ouvrez le fichier appsettings.json et ajoutez une chaîne de connexion comme indiqué


dans la balise suivante :
JSON

{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;
MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

Ajouter le filtre d’exception de la base de données


Ajoutez AddDatabaseDeveloperPageExceptionFilter à ConfigureServices comme
indiqué dans le code suivant :

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddDbContext<SchoolContext>(options =>

options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
);

services.AddDatabaseDeveloperPageExceptionFilter();

services.AddControllersWithViews();
}

Le AddDatabaseDeveloperPageExceptionFilter fournit des informations utiles sur les


erreurs dans l’environnement de développement.

Base de données locale SQL Server Express


La chaîne de connexion spécifie SQL Server LocalDB. LocalDB est une version allégée du
moteur de base de données SQL Server Express. Elle est destinée au développement
d’applications, et non à une utilisation en production. LocalDB démarre à la demande et
s’exécute en mode utilisateur, ce qui n’implique aucune configuration complexe. Par
défaut, LocalDB crée des fichiers de base de données .mdf dans le répertoire
C:/Users/<user> .

Initialiser la base de données avec des données


de test
EF crée une base de données vide. Dans cette section, une méthode est ajoutée. Elle est
appelée après la création de la base de données pour la remplir avec des données de
test.

La méthode EnsureCreated est utilisée pour créer automatiquement la base de données.


Dans un tutoriel ultérieur, vous verrez comment traiter les modifications des modèles à
l’aide des migrations Code First pour modifier le schéma de base de données au lieu de
supprimer et de recréer la base de données.

Dans le dossier Données, créez une nouvelle classe nommée DbInitializer avec le code
suivant :

C#

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();

// Look for any students.


if (context.Students.Any())
{
return; // DB has been seeded
}

var students = new Student[]


{
new
Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.P
arse("2005-09-01")},
new
Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Pa
rse("2002-09-01")},
new
Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse
("2003-09-01")},
new
Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Pa
rse("2002-09-01")},
new
Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002
-09-01")},
new
Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Pars
e("2001-09-01")},
new
Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse
("2003-09-01")},
new
Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Pars
e("2005-09-01")}
};
foreach (Student s in students)
{
context.Students.Add(s);
}
context.SaveChanges();

var courses = new Course[]


{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges();

var enrollments = new Enrollment[]


{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
foreach (Enrollment e in enrollments)
{
context.Enrollments.Add(e);
}
context.SaveChanges();
}
}
}

Le code précédent vérifie si la base de données existe :

Si la base de données n’est pas trouvée ;


Elle est créée et chargée avec des données de test. Il charge les données de test
dans des tableaux plutôt que des collections List<T> afin d’optimiser les
performances.
Si la base de données est trouvée, aucune action n’est nécessaire.

Mettez à jour Program.cs à l’aide du code suivant :

C#

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();

CreateDbIfNotExists(host);

host.Run();
}
private static void CreateDbIfNotExists(IHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>
();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger =
services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the
DB.");
}
}
}

public static IHostBuilder CreateHostBuilder(string[] args) =>


Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

Program.cs effectue les opérations suivantes au démarrage de l’application :

Obtenir une instance de contexte de base de données à partir du conteneur


d’injection de dépendance.
Appelez la méthode DbInitializer.Initialize .
Supprimez le contexte lorsque la méthode Initialize se termine, comme indiqué
dans le code suivant :

C#

public static void Main(string[] args)


{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred while seeding the
database.");
}
}

host.Run();
}

Lors de la première exécution de l’application, la base de données est créée et chargée


avec des données de test. Dès que le modèle de données change :

Supprimez la base de données.


Mettez à jour la méthode implantée et démarrez à nouveau avec une nouvelle
base de données.

Dans les tutoriels suivants, la base de données est modifiée quand le modèle de
données change, sans supprimer et recréer la base de données. Aucune donnée n’est
perdue lorsque le modèle de données change.

Créer un contrôleur et des vues


Utilisez le moteur de génération de modèles automatique dans Visual Studio pour
ajouter un contrôleur MVC et les vues qu’utilisera EF pour exécuter des requêtes de
données et enregistrer les données.

La création automatique de vues et de méthodes d’action CRUD porte le nom de


génération de modèles automatique.

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le dossier


Controllers , puis choisissez Ajouter > Nouvel élément généré automatiquement.

Dans la boîte de dialogue Ajouter un modèle automatique :


Sélectionnez Contrôleur MVC avec vues, utilisant Entity Framework.
Cliquez sur Add. La boîte de dialogue Ajouter un contrôleur MVC avec vues,
utilisant Entity Framework s’affiche :
Dans Classe de modèle, sélectionnez Étudiant.
Dans Classe du contexte de données, sélectionnez SchoolContext.
Acceptez la valeur par défaut StudentsController comme nom.
Cliquez sur Add.

Le moteur de génération de modèles automatique de Visual Studio crée un fichier


StudentsController.cs et un ensemble de vues (fichiers *.cshtml ) qui fonctionnent

avec le contrôleur.

Notez que le contrôleur accepte un SchoolContext comme paramètre de constructeur.

C#

namespace ContosoUniversity.Controllers
{
public class StudentsController : Controller
{
private readonly SchoolContext _context;

public StudentsController(SchoolContext context)


{
_context = context;
}

L’injection de dépendance ASP.NET Core s’occupe de la transmission d’une instance de


SchoolContext dans le contrôleur. Vous l’avez configuré dans la classe Startup .

Le contrôleur contient une méthode d’action Index , qui affiche tous les étudiants dans
la base de données. La méthode obtient la liste des étudiants du jeu d’entités Students
en lisant la propriété Students de l’instance de contexte de base de données :
C#

public async Task<IActionResult> Index()


{
return View(await _context.Students.ToListAsync());
}

Les éléments de programmation asynchrones dans ce code sont expliqués plus loin
dans ce tutoriel.

La vue Views/Students/Index.cshtml affiche cette liste dans un tableau :

CSHTML

@model IEnumerable<ContosoUniversity.Models.Student>

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
@Html.DisplayNameFor(model => model.EnrollmentDate)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a>
|
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Appuyez sur Ctrl+F5 pour exécuter le projet ou choisissez Déboguer > Exécuter sans
débogage dans le menu.

Cliquez sur l’onglet Students pour afficher les données de test que la méthode
DbInitializer.Initialize a insérées. Selon l’étroitesse de votre fenêtre de navigateur,

vous verrez le lien de l’onglet Students en haut de la page ou vous devrez cliquer sur
l’icône de navigation dans le coin supérieur droit pour afficher le lien.
Afficher la base de données
Quand l’application est lancée, la méthode DbInitializer.Initialize appelle
EnsureCreated . EF a constaté qu’il n’y avait pas de base de données :

Il a donc créé une base de données.


Le code de méthode Initialize a rempli la base de données avec des données.

Utilisez l’Explorateur d’objets SQL Server (SSOX) pour afficher la base de données dans
Visual Studio :

Sélectionnez l’Explorateur d’objets SQL Server (SSOX) à partir du menu Affichage


de Visual Studio.
Dans SSOX, sélectionnez (localdb)\MSSQLLocalDB > Bases de données.
Sélectionnez ContosoUniversity1 , l’entrée du nom de la base de données qui se
trouve dans la chaîne de connexion dans le fichier appsettings.json .
Développez le nœud Tables pour afficher les tables de la base de données.
Cliquez avec le bouton droit sur la table Étudiant, puis cliquez sur Afficher les données
pour afficher les données de la table.

Les fichiers des base de données *.mdf et *.ldf se trouvent dans le dossier C:\Users\
<username> .

Étant donné que EnsureCreated est appelé dans la méthode d’initialiseur qui s’exécute
au démarrage de l’application, vous pouvez :

Apporter une modification à la classe Student .


Supprimez la base de données.
Arrêtez, puis démarrez l’application. La base de données est automatiquement
recréée pour correspondre à la modification.

Par exemple, si une propriété EmailAddress est ajoutée à la classe Student , une nouvelle
colonne EmailAddress se trouve dans la table recréée. La vue n’affiche pas la nouvelle
propriété EmailAddress .

Conventions
La quantité de code écrite pour que l’EF crée une base de données complète est minime
en raison de l’utilisation des conventions qu’EF emploie :

Les noms des propriétés DbSet sont utilisés comme noms de tables. Pour les
entités non référencées par une propriété DbSet , les noms des classes d’entités
sont utilisés comme noms de tables.
Les noms des propriétés d’entités sont utilisés comme noms de colonnes.
Les propriétés d’entité nommées ID ou classnameID sont reconnues comme
propriétés PK.
Une propriété est interprétée comme une propriété FK si elle est nommée < nom
de propriété de navigation >< nom de propriété PK > . Par exemple, StudentID pour
la propriété de navigation Student , puisque la clé primaire (PK) de l’entité Student
est ID . Les propriétés FK peuvent également être nommées < nom de propriété de
clé primaire > . Par exemple, EnrollmentID puisque la clé primaire (PK) de l’entité
Enrollment est EnrollmentID .

Le comportement conventionnel peut être remplacé. Par exemple, les noms des tables
peuvent être spécifiés explicitement, comme indiqué plus haut dans ce tutoriel. Les
noms de colonnes et n’importe quelle propriété peuvent être définis en tant que PK ou
FK (clé primaire ou clé étrangère).

Code asynchrone
La programmation asynchrone est le mode par défaut pour ASP.NET Core et EF Core.

Un serveur web a un nombre limité de threads disponibles et, dans les situations de
forte charge, tous les threads disponibles peuvent être utilisés. Quand cela se produit, le
serveur ne peut pas traiter de nouvelle requête tant que les threads ne sont pas libérés.
Avec le code synchrone, plusieurs threads peuvent être bloqués alors qu’ils n’effectuent
en fait aucun travail, car ils attendent que des E/S se terminent. Avec le code
asynchrone, quand un processus attend que des E/S se terminent, son thread est libéré
afin d’être utilisé par le serveur pour traiter d’autres demandes. Il permet ainsi d’utiliser
les ressources serveur plus efficacement, et le serveur peut gérer plus de trafic sans
retard.

Le code asynchrone introduit néanmoins une petite surcharge au moment de


l’exécution, mais dans les situations de faible trafic, la baisse de performances est
négligeable, alors qu’en cas de trafic élevé, l’amélioration potentielle des performances
est importante.
Dans le code suivant, async , Task<T> , await et ToListAsync font que le code s’exécute
de manière asynchrone.

C#

public async Task<IActionResult> Index()


{
return View(await _context.Students.ToListAsync());
}

Le mot clé async indique au compilateur de générer des rappels pour les parties
du corps de la méthode et pour créer automatiquement l’objet
Task<IActionResult> qui est renvoyé.

Le type de retour Task<IActionResult> représente le travail en cours avec un


résultat de type IActionResult .
Le mot clé await indique au compilateur de fractionner la méthode en deux
parties. La première partie se termine par l’opération qui est démarrée de façon
asynchrone. La seconde partie est placée dans une méthode de rappel qui est
appelée quand l’opération se termine.
ToListAsync est la version asynchrone de la méthode d’extension ToList .

Voici quelques éléments à connaître lors de l’écriture de code asynchrone qui utilise EF :

Seules les instructions qui provoquent l’envoi de requêtes ou de commandes vers


la base de données sont exécutées de façon asynchrone. Cela inclut, par exemple,
ToListAsync , SingleOrDefaultAsync et SaveChangesAsync , mais pas les instructions

qui ne font, par exemple, que changer IQueryable , telles que var students =
context.Students.Where(s => s.LastName == "Davolio") .

Un contexte EF n’est pas thread-safe : n’essayez pas d’effectuer plusieurs


opérations en parallèle. Lorsque vous appelez une méthode EF asynchrone
quelconque, utilisez toujours le mot clé await .
Pour tirer profit des meilleures performances du code asynchrone, assurez-vous
que tous les packages de bibliothèque utilisés utilisent également du code
asynchrone s’ils appellent des méthodes EF qui provoquent l’envoi des requêtes à
la base de données.

Pour plus d’informations sur la programmation asynchrone dans .NET, consultez Vue
d’ensemble du code asynchrone.

Limiter les entités extraites


Pour plus d’informations sur la limitation du nombre d’entités renvoyées à partir d’une
requête, consultez Considérations relatives aux performances.

Journalisation SQL d’Entity Framework Core


La configuration de la journalisation est généralement fournie par la section Logging
des fichiers appsettings.{Environment}.json . Pour journaliser les instructions SQL,
ajoutez "Microsoft.EntityFrameworkCore.Database.Command": "Information" au
fichier appsettings.Development.json :

JSON

{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDB-
2;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
,"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
},
"AllowedHosts": "*"
}

Avec la valeur JSON précédente, les instructions SQL s’affichent sur la ligne de
commande et dans la fenêtre de sortie de Visual Studio.

Pour plus d’informations, consultez Journalisation dans .NET Core et ASP.NET Core et ce
problème GitHub .

Passez au tutoriel suivant pour découvrir comment effectuer des opérations CRUD de
base (créer, lire, mettre à jour, supprimer).

Implémenter la fonctionnalité CRUD de base

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et  Ouvrir un problème de
examiner les problèmes et les
documentation
demandes de tirage. Pour plus
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : Implémenter la fonctionnalité
CRUD - ASP.NET MVC avec EF Core
Article • 30/11/2023

Dans le didacticiel précédent, vous avez créé une application MVC qui stocke et affiche
les données en utilisant Entity Framework et SQL Server LocalDB. Dans ce didacticiel,
vous allez examiner et personnaliser le code CRUD (créer, lire, mettre à jour, supprimer)
que la génération de modèles automatique MVC a créé automatiquement pour vous
dans des contrôleurs et des vues.

7 Notes

Il est courant d’implémenter le modèle de référentiel pour créer une couche


d’abstraction entre votre contrôleur et la couche d’accès aux données. Pour
conserver ces didacticiels simples et orientés vers l’apprentissage de l’utilisation
d’Entity Framework proprement dit, ils n’utilisent pas de référentiels. Pour plus
d’informations sur les référentiels avec EF, consultez le dernier didacticiel de cette
série.

Dans ce tutoriel, vous allez :

" Personnaliser la page Details


" Mettre à jour la page Create
" Mettre à jour la page Edit
" Mettre à jour la page Delete
" Fermer les connexions de base de données

Prérequis
Bien démarrer avec EF Core et ASP.NET Core MVC

Personnaliser la page Details


Le code du modèle généré automatiquement pour la page Index des étudiants exclut la
propriété Enrollments , car elle contient une collection. Dans la page Details, vous
affichez le contenu de la collection dans un tableau HTML.
Dans Controllers/StudentsController.cs , la méthode d’action pour la vue Détails utilise
la méthode FirstOrDefaultAsync pour récupérer une seule entité Student . Ajoutez du
code qui appelle Include . Les méthodes ThenInclude et AsNoTracking , comme indiqué
dans le code en surbrillance suivant.

C#

public async Task<IActionResult> Details(int? id)


{
if (id == null)
{
return NotFound();
}

var student = await _context.Students


.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);

if (student == null)
{
return NotFound();
}

return View(student);
}

Les méthodes Include et ThenInclude font que le contexte charge la propriété de


navigation Student.Enrollments et dans chaque inscription, la propriété de navigation
Enrollment.Course . Vous découvrirez plus d’informations sur ces méthodes dans le

tutoriel sur la lecture des données associées.

La méthode AsNoTracking améliore les performances dans les scénarios où les entités
retournées ne sont pas mises à jour pendant la durée de vie du contexte actif. Vous
pouvez découvrir plus d’informations sur AsNoTracking à la fin de ce didacticiel.

Données de routage
La valeur de clé qui est passée à la méthode Details provient des données de route. Les
données de route sont des données que le classeur de modèles a trouvées dans un
segment de l’URL. Par exemple, la route par défaut spécifie les segments contrôleur,
action et ID :

C#
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});

Dans l’URL suivante, la route par défaut mappe Instructor en tant que contrôleur, Index
en tant qu’action et 1 en tant qu’ID ; il s’agit des valeurs des données de route.

http://localhost:1230/Instructor/Index/1?courseID=2021

La dernière partie de l’URL (« ?courseID=2021 ») est une valeur de chaîne de requête. Le


classeur de modèles passe aussi la valeur d’ID au paramètre id de la méthode Index si
vous le passez en tant que valeur de chaîne de requête :

http://localhost:1230/Instructor/Index?id=1&CourseID=2021

Dans la page Index, les URL des liens hypertexte sont créées par des instructions Tag
Helper dans la vue Razor. Dans le code Razor suivant, le paramètre id correspond à
l’itinéraire par défaut : id est donc ajouté aux données d’itinéraire.

HTML

<a asp-action="Edit" asp-route-id="@item.ID">Edit</a>

Ceci génère le code HTML suivant quand item.ID vaut 6 :

HTML

<a href="/Students/Edit/6">Edit</a>

Dans le code Razor suivant, studentID ne correspond pas à un paramètre dans


l’itinéraire par défaut : il est donc ajouté en tant que chaîne de requête.

HTML

<a asp-action="Edit" asp-route-studentID="@item.ID">Edit</a>


Ceci génère le code HTML suivant quand item.ID vaut 6 :

HTML

<a href="/Students/Edit?studentID=6">Edit</a>

Pour plus d’informations sur les Tag Helpers, consultez Tag Helpers dans ASP.NET Core.

Ajouter des inscriptions à la vue Details


Ouvrez Views/Students/Details.cshtml . Chaque champ est affiché avec les helpers
DisplayNameFor et DisplayFor , comme montré dans l’exemple suivant :

CSHTML

<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.LastName)
</dd>

Après le dernier champ et immédiatement avant la balise de fermeture </dl> , ajoutez le


code suivant pour afficher une liste d’inscriptions :

CSHTML

<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Enrollments)
</dt>
<dd class="col-sm-10">
<table class="table">
<tr>
<th>Course Title</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Enrollments)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Course.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
</dd>

Si l’indentation du code est incorrecte une fois le code collé, appuyez sur Ctrl-K-D pour
la corriger.

Ce code parcourt en boucle les entités dans la propriété de navigation Enrollments .


Pour chaque inscription, il affiche le titre du cours et le niveau. Le titre du cours est
récupéré à partir de l’entité de cours qui est stockée dans la propriété de navigation
Course de l’entité Enrollments.

Exécutez l’application, sélectionnez l’onglet Students, puis cliquez sur le lien Details
pour un étudiant. Vous voyez la liste des cours et des notes de l’étudiant sélectionné :

Mettre à jour la page Create


Dans StudentsController.cs , modifiez la méthode HttpPost Create en ajoutant un bloc
try-catch et en supprimant l’ID de l’attribut Bind .

C#

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists " +
"see your system administrator.");
}
return View(student);
}

Ce code ajoute l’entité Student créée par le classeur de modèles ASP.NET Core MVC au
jeu d’entités Students, puis enregistre les modifications dans la base de données. (Le
classeur de modèles référence la fonctionnalité d’ASP.NET Core MVC qui facilite
l’utilisation des données envoyées par un formulaire ; un classeur de modèles convertit
les valeurs de formulaire envoyées en types CLR et les transfère à la méthode d’action
dans des paramètres. Dans ce cas, le classeur de modèles instancie une entité Student
pour vous à l’aide de valeurs de propriété de la collection Form.)

Vous avez supprimé ID de l’attribut Bind , car ID est la valeur de clé primaire définie
automatiquement par SQL Server lors de l’insertion de la ligne. L’entrée de l’utilisateur
ne définit pas la valeur de l’ID.

À part l’attribut Bind , le bloc try-catch est la seule modification que vous avez apportée
au code du modèle généré automatiquement. Si une exception qui dérive de
DbUpdateException est interceptée lors de l’enregistrement des modifications, un

message d’erreur générique est affiché. Les exceptions DbUpdateException sont parfois
dues à quelque chose d’externe à l’application et non pas à une erreur de
programmation : il est donc conseillé à l’utilisateur de réessayer. Bien que ceci ne soit
pas implémenté dans cet exemple, une application destinée à la production doit
consigner l’exception. Pour plus d’informations, consultez la section Journal pour
obtenir un aperçu de Surveillance et télémétrie (génération d’applications Cloud du
monde réel avec Azure).

L’attribut ValidateAntiForgeryToken aide à éviter les attaques par falsification de


requête intersites (CSRF, Cross-Site Request Forgery). Le jeton est automatiquement
injecté dans la vue par le FormTagHelper et est inclus quand le formulaire est envoyé
par l’utilisateur. Le jeton est validé par l’attribut ValidateAntiForgeryToken . Pour plus
d’informations, consultez Prévenir les attaques par falsification de requête intersites
(XSRF/CSRF) dans ASP.NET Core.

Remarque sur la sécurité concernant la survalidation


L’attribut Bind inclus dans le code du modèle généré automatiquement sur la méthode
Create est un moyen de protéger contre la survalidation dans les scénarios de création.

Par exemple, supposons que l’entité Student comprend une propriété Secret et que
vous ne voulez pas que cette page web définisse sa valeur.

C#

public class Student


{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public string Secret { get; set; }
}

Même si vous n’avez pas de champ Secret dans la page web, un hacker pourrait utiliser
un outil comme Fiddler, ou écrire du JavaScript, pour envoyer une valeur de formulaire
pour Secret . Sans l’attribut Bind limitant les champs utilisés par le classeur de modèles
quand il crée une instance de Student, le classeur de modèles choisit la valeur de
formulaire pour Secret et l’utilise pour créer l’instance de l’entité Student. Ensuite, la
valeur spécifiée par le hacker pour le champ de formulaire Secret , quelle qu’elle soit, est
mise à jour dans la base de données. L’illustration suivante montre l’outil Fiddler en train
d’ajouter le champ Secret (avec la valeur « OverPost ») aux valeurs du formulaire
envoyé.
La valeur « OverPost » serait correctement ajoutée à la propriété Secret de la ligne
insérée, même si vous n’aviez jamais prévu que la page web puisse définir cette
propriété.

Vous pouvez empêcher la survalidation dans les scénarios de modification en lisant


d’abord l’entité à partir de la base de données, puis en appelant TryUpdateModel , en
passant une liste des propriétés explicitement autorisées. Il s’agit de la méthode utilisée
dans ces didacticiels.

Une autre façon d’empêcher la survalidation qui est préférée par de nombreux
développeurs consiste à utiliser les afficher des modèles de vues au lieu de classes
d’entités avec la liaison de modèle. Incluez seulement les propriétés que vous voulez
mettre à jour dans le modèle de vue. Une fois le classeur de modèles MVC a terminé,
copiez les propriétés du modèle de vue vers l’instance de l’entité, en utilisant si vous le
souhaitez un outil comme AutoMapper. Utilisez _context.Entry sur l’instance de l’entité
pour définir son état sur Unchanged , puis définissez
Property("PropertyName").IsModified sur true sur chaque propriété d’entité qui est

incluse dans le modèle de vue. Cette méthode fonctionne à la fois dans les scénarios de
modification et de création.

Tester la page Create


Le code dans Views/Students/Create.cshtml utilise les Tag Helpers label , input et span
(pour les messages de validation) pour chaque champ.
Exécutez l’application, sélectionnez l’onglet Students, puis cliquez sur Create New.

Entrez des noms et une date. Si votre navigateur vous le permet, essayez d’entrer une
date non valide. (Certains navigateurs vous obligent à utiliser un sélecteur de dates.)
Cliquez ensuite sur Créer pour afficher le message d’erreur.

Il s’agit de la validation côté serveur que vous obtenez par défaut ; dans un didacticiel
suivant, vous verrez comment ajouter des attributs qui génèrent du code également
pour la validation côté client. Le code en surbrillance suivant montre la vérification de
validation du modèle dans la méthode Create .

C#

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(
[Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
{
try
{
if (ModelState.IsValid)
{
_context.Add(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists " +
"see your system administrator.");
}
return View(student);
}

Changez la date en une valeur valide, puis cliquez sur Create pour voir apparaître le
nouvel étudiant dans la page Index.

Mettre à jour la page Edit


Dans StudentController.cs , la méthode HttpGet Edit (celle sans l’attribut HttpPost )
utilise la méthode FirstOrDefaultAsync pour récupérer l’entité Student sélectionnée,
comme vous l’avez vu dans la méthode Details . Vous n’avez pas besoin de modifier
cette méthode.

Code HttpPost Edit recommandé : lire et mettre à jour


Remplacez la méthode d’action HttpPost Edit par le code suivant.

C#

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
var studentToUpdate = await _context.Students.FirstOrDefaultAsync(s =>
s.ID == id);
if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
}
return View(studentToUpdate);
}

Ces modifications implémentent une bonne pratique de sécurité pour empêcher la


survalidation. Le générateur de modèles automatique a généré un attribut Bind et a
ajouté l’entité créée par le classeur de modèles au jeu d’entités avec un indicateur
Modified . Ce code n’est pas recommandé dans de nombreux scénarios, car l’attribut

Bind efface toutes les données préexistantes dans les champs non répertoriés dans le

paramètre Include .

Le nouveau code lit l’entité existante et appelle TryUpdateModel pour mettre à jour les
champs dans l’entité récupérée en fonction de l’entrée d’utilisateur dans les données du
formulaire envoyé. Le suivi automatique des modifications d’Entity Framework définit
l’indicateur Modified sur les champs qui sont modifiés via une entrée dans le formulaire.
Quand la méthode SaveChanges est appelée, Entity Framework crée des instructions SQL
pour mettre à jour la ligne de la base de données. Les conflits d’accès concurrentiel sont
ignorés, et seules les colonnes de table qui ont été mises à jour par l’utilisateur sont
mises à jour dans la base de données. (Un didacticiel suivant montre comment gérer les
conflits d’accès concurrentiel.)

Au titre de bonne pratique pour empêcher la survalidation, les champs dont vous voulez
qu’ils puissent être mis à jour par la page de modification sont déclarés dans les
paramètres de TryUpdateModel . (La chaîne vide précédant la liste des champs de la liste
de paramètres est un préfixe à utiliser avec les noms des champs de formulaire.)
Actuellement, vous ne protégez aucun champ supplémentaire, mais le fait de répertorier
les champs que vous voulez que le classeur de modèles lie garantit que si vous ajoutez
ultérieurement des champs au modèle de données, ils seront automatiquement
protégés jusqu’à ce que vous les ajoutiez explicitement ici.

À la suite de ces modifications, la signature de méthode de la méthode HttpPost Edit


est la même que celle de la méthode HttpGet Edit ; par conséquent, vous avez
renommé la méthode EditPost .

Autre possibilité pour le code HttpPost Edit : Créer et


attacher
Le code de HttpPost Edit recommandé garantit que seules les colonnes modifiées sont
mises à jour et conserve les données dans les propriétés que vous ne voulez pas inclure
pour la liaison de modèle. Cependant, l’approche « lecture en premier » nécessite une
lecture supplémentaire de la base de données et peut aboutir à un code plus complexe
pour la gestion des conflits d’accès concurrentiel. Une alternative consiste à attacher
une entité créée par le classeur de modèles au contexte EF et à la marquer comme étant
modifiée. (Ne mettez pas à jour votre projet avec ce code, il figure ici seulement pour
illustrer une approche facultative.)

C#

public async Task<IActionResult> Edit(int id,


[Bind("ID,EnrollmentDate,FirstMidName,LastName")] Student student)
{
if (id != student.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
}
return View(student);
}

Vous pouvez utiliser cette approche quand l’interface utilisateur de la page web inclut
tous les champs de l’entité et peut les mettre à jour.

Le code du modèle généré automatiquement utilise l’approche « créer et attacher »,


mais il intercepte seulement les exceptions DbUpdateConcurrencyException et retourne
des codes d’erreur 404. L’exemple suivant intercepte toutes les exceptions de mise à
jour de la base de données et affiche un message d’erreur.

États des entités


Le contexte de base de données effectue le suivi de la synchronisation ou non des
entités en mémoire avec leurs lignes correspondantes dans la base de données, et ces
informations déterminent ce qui se passe quand vous appelez la méthode SaveChanges .
Par exemple, quand vous passez une nouvelle entité à la méthode Add , l’état de cette
entité est défini sur Added . Ensuite, quand vous appelez la méthode SaveChanges , le
contexte de base de données émet une commande SQL INSERT.

L’état d’une entité peut être l’un des suivants :

Added . L’entité n’existe pas encore dans la base de données. La méthode


SaveChanges émet une instruction INSERT.

Unchanged . La méthode SaveChanges ne doit rien faire avec cette entité. Quand

vous lisez une entité dans la base de données, l’entité a d’abord cet état.

Modified . Tout ou partie des valeurs de propriété de l’entité ont été modifiées. La

méthode SaveChanges émet une instruction UPDATE.

Deleted . L’entité a été marquée pour suppression. La méthode SaveChanges émet

une instruction DELETE.

Detached . L’entité n’est pas suivie par le contexte de base de données.

Dans une application de poste de travail, les changements d’état sont généralement
définis automatiquement. Vous lisez une entité et vous apportez des modifications à
certaines de ses valeurs de propriété. Son état passe alors automatiquement à Modified .
Quand vous appelez SaveChanges , Entity Framework génère une instruction SQL UPDATE
qui met à jour seulement les propriétés que vous avez modifiées.

Dans une application web, le DbContext qui lit initialement une entité et affiche ses
données pour permettre leur modification est supprimé après le rendu d’une page.
Quand la méthode d’action HttpPost Edit est appelée, une nouvelle requête web est
effectuée et vous disposez d’une nouvelle instance de DbContext . Si vous relisez l’entité
dans ce nouveau contexte, vous simulez le traitement du poste de travail.

Mais si vous ne voulez pas effectuer l’opération de lecture supplémentaire, vous devez
utiliser l’objet entité créé par le classeur de modèles. Le moyen le plus simple consiste à
définir l’état de l’entité en Modified, comme cela est fait dans l’alternative pour le code
HttpPost Edit illustrée précédemment. Ensuite, quand vous appelez SaveChanges , Entity
Framework met à jour toutes les colonnes de la ligne de la base de données, car le
contexte n’a aucun moyen de savoir quelles propriétés vous avez modifiées.

Si vous voulez éviter l’approche « lecture en premier », mais que vous voulez aussi que
l’instruction SQL UPDATE mette à jour seulement les champs que l’utilisateur a
réellement changés, le code est plus complexe. Vous devez enregistrer les valeurs
d’origine d’une façon ou d’une autre (par exemple en utilisant des champs masqués)
afin qu’ils soient disponibles quand la méthode HttpPost Edit est appelée. Vous pouvez
ensuite créer une entité Student en utilisant les valeurs d’origine, appeler la méthode
Attach avec cette version d’origine de l’entité, mettre à jour les valeurs de l’entité avec

les nouvelles valeurs, puis appeler SaveChanges .

Tester la page Edit


Exécutez l’application, sélectionnez l’onglet Students, puis cliquez sur un lien hypertexte
Edit.
Changez quelques données et cliquez sur Save. La page Index s’ouvre et affiche les
données modifiées.

Mettre à jour la page Delete


Dans StudentController.cs , le modèle de code pour la méthode HttpGet Delete utilise
la méthode FirstOrDefaultAsync pour récupérer l’entité Student sélectionnée, comme
vous l’avez vu dans les méthodes Details et Edit. Cependant, pour implémenter un
message d’erreur personnalisé quand l’appel à SaveChanges échoue, vous devez ajouter
des fonctionnalités à cette méthode et à sa vue correspondante.

Comme vous l’avez vu pour les opérations de mise à jour et de création, les opérations
de suppression nécessitent deux méthodes d’action. La méthode qui est appelée en
réponse à une demande GET affiche une vue qui permet à l’utilisateur d’approuver ou
d’annuler l’opération de suppression. Si l’utilisateur l’approuve, une demande POST est
créée. Quand cela se produit, la méthode HttpPost Delete est appelée, puis cette
méthode effectue ensuite l’opération de suppression.

Vous allez ajouter un bloc try-catch à la méthode HttpPost Delete pour gérer les erreurs
qui peuvent se produire quand la base de données est mise à jour. Si une erreur se
produit, la méthode HttpPost Delete appelle la méthode HttpGet Delete, en lui passant
un paramètre qui indique qu’une erreur s’est produite. La méthode HttpGet Delete
réaffiche ensuite la page de confirmation, ainsi que le message d’erreur, donnant à
l’utilisateur la possibilité d’annuler ou de recommencer.

Remplacez la méthode d’action HttpGet Delete par le code suivant, qui gère le
signalement des erreurs.

C#

public async Task<IActionResult> Delete(int? id, bool? saveChangesError =


false)
{
if (id == null)
{
return NotFound();
}

var student = await _context.Students


.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (student == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ViewData["ErrorMessage"] =
"Delete failed. Try again, and if the problem persists " +
"see your system administrator.";
}

return View(student);
}

Ce code accepte un paramètre facultatif qui indique si la méthode a été appelée après
une erreur d’enregistrement des modifications. Ce paramètre a la valeur false quand la
méthode HttpGet Delete est appelée sans une erreur antérieure. Quand elle est appelée
par la méthode HttpPost Delete en réponse à une erreur de mise à jour de la base de
données, le paramètre a la valeur true et un message d’erreur est passé à la vue.

L’approche « lecture en premier » pour HttpPost Delete


Remplacez la méthode d’action HttpPost Delete (nommée DeleteConfirmed ) par le code
suivant, qui effectue l’opération de suppression réelle et intercepte les erreurs de mise à
jour de la base de données.

C#

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var student = await _context.Students.FindAsync(id);
if (student == null)
{
return RedirectToAction(nameof(Index));
}

try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { id = id,
saveChangesError = true });
}
}
Ce code récupère l’entité sélectionnée, puis appelle la méthode Remove pour définir
l’état de l’entité sur Deleted . Lorsque SaveChanges est appelée, une commande SQL
DELETE est générée.

L’approche « créer et attacher » pour HttpPost Delete


Si l’amélioration des performances dans une application traitant des volumes importants
est une priorité, vous pouvez éviter une requête SQL inutile en instanciant une entité de
Student en utilisant seulement la valeur de la clé primaire, puis en définissant l’état de
l’entité sur Deleted . C’est tout ce dont a besoin Entity Framework pour pouvoir
supprimer l’entité. (Ne placez pas de ce code dans votre projet ; il figure ici seulement
pour illustrer une solution alternative.)

C#

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
try
{
Student studentToDelete = new Student() { ID = id };
_context.Entry(studentToDelete).State = EntityState.Deleted;
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { id = id,
saveChangesError = true });
}
}

Si l’entité a des données connexes qui doivent également être supprimées, vérifiez que
la suppression en cascade est configurée dans la base de données. Avec cette approche
pour la suppression de l’entité, EF peut ne pas réaliser que des entités connexes doivent
être supprimées.

Mettre à jour la vue Delete


Dans Views/Student/Delete.cshtml , ajoutez un message d’erreur entre le titre h2 et le
titre h3, comme indiqué dans l’exemple suivant :

CSHTML
<h2>Delete</h2>
<p class="text-danger">@ViewData["ErrorMessage"]</p>
<h3>Are you sure you want to delete this?</h3>

Exécutez l’application, sélectionnez l’onglet Students, puis cliquez sur un lien hypertexte
Delete :

Cliquez sur Supprimer. La page Index s’affiche sans l’étudiant supprimé. (Vous verrez un
exemple du code de gestion des erreurs en action dans le didacticiel sur l’accès
concurrentiel.)

Fermer les connexions de base de données


Pour libérer les ressources détenues par une connexion de base de données, l’instance
du contexte doit être supprimée dès que possible quand vous en avez terminé avec
celle-ci. L’injection de dépendances intégrée d’ASP.NET Core prend en charge cette
tâche pour vous.
Dans Startup.cs , vous appelez la méthode d’extension AddDbContext pour
provisionner la classe DbContext dans le conteneur d’injection de dépendances
d’ASP.NET Core. Cette méthode définit par défaut la durée de vie du service sur Scoped .
Scoped signifie que la durée de vie de l’objet de contexte coïncide avec la durée de vie

de la demande web, et que la méthode Dispose sera appelée automatiquement à la fin


de la requête web.

Gérer les transactions


Par défaut, Entity Framework implémente implicitement les transactions. Dans les
scénarios où vous apportez des modifications à plusieurs lignes ou plusieurs tables, puis
que appelez SaveChanges , Entity Framework garantit automatiquement que soit toutes
vos modifications réussissent soit elles échouent toutes. Si certaines modifications sont
effectuées en premier puis qu’une erreur se produit, ces modifications sont
automatiquement annulées. Pour les scénarios où vous avez besoin de plus de contrôle,
par exemple si vous voulez inclure des opérations effectuées en dehors d’Entity
Framework dans une transaction, consultez Transactions.

Pas de suivi des requêtes


Quand un contexte de base de données récupère des lignes de table et crée des objets
entité qui les représentent, par défaut, il effectue le suivi du fait que les entités en
mémoire sont ou non synchronisées avec ce qui se trouve dans la base de données. Les
données en mémoire agissent comme un cache et sont utilisées quand vous mettez à
jour une entité. Cette mise en cache est souvent inutile dans une application web, car les
instances de contexte ont généralement une durée de vie courte (une instance est créée
puis supprimée pour chaque requête) et le contexte qui lit une entité est généralement
supprimé avant que cette entité soit réutilisée.

Vous pouvez désactiver le suivi des objets entité en mémoire en appelant la méthode
AsNoTracking . Voici des scénarios classiques où vous voulez procéder ainsi :

Pendant la durée de vie du contexte, vous n’avez besoin de mettre à jour aucune
entité et il n’est pas nécessaire qu’EF charge automatiquement les propriétés de
navigation avec les entités récupérées par des requêtes distinctes. Ces conditions
sont souvent rencontrées dans les méthodes d’action HttpGet d’un contrôleur.

Vous exécutez une requête qui récupère un gros volume de données, et seule une
petite partie des données retournées sont mises à jour. Il peut être plus efficace de
désactiver le suivi pour la requête retournant un gros volume de données et
d’exécuter une requête plus tard pour les quelques entités qui doivent être mises à
jour.

Vous voulez attacher une entité pour pouvoir la mettre à jour, mais vous avez
auparavant récupéré la même entité à d’autre fins. Comme l’entité est déjà suivie
par le contexte de base de données, vous ne pouvez pas attacher l’entité que vous
voulez modifier. Une façon de gérer cette situation est d’appeler AsNoTracking sur
la requête précédente.

Pour plus d’informations, consultez Suivi et non-suivi.

Obtenir le code
Télécharger ou afficher l’application complète.

Étapes suivantes
Dans ce tutoriel, vous allez :

" Personnaliser la page Details


" Mettre à jour la page Create
" Mettre à jour la page Edit
" Mettre à jour la page Delete
" Connexions de base de données fermées

Passez au tutoriel suivant pour découvrir comment développer les fonctionnalités de la


page Index en ajoutant le tri, le filtrage et la pagination.

Prochain : Tri, filtrage et pagination


6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : Ajouter le tri, le filtrage et la
pagination - ASP.NET MVC avec EF Core
Article • 30/11/2023

Dans le didacticiel précédent, vous avez implémenté un ensemble de pages web pour
les opérations CRUD de base pour les entités Student. Dans ce didacticiel, vous allez
ajouter les fonctionnalités de tri, de filtrage et de changement de page à la page d’index
des étudiants. Vous allez également créer une page qui effectue un regroupement
simple.

L’illustration suivante montre à quoi ressemblera la page quand vous aurez terminé. Les
en-têtes des colonnes sont des liens sur lesquels l’utilisateur peut cliquer pour trier
selon les colonnes. Cliquer de façon répétée sur un en-tête de colonne permet de
changer l’ordre de tri (croissant ou décroissant).

Dans ce tutoriel, vous allez :

" Ajouter des liens de tri de colonne


" Ajouter une zone Rechercher
" Ajouter la pagination à l'index des étudiants
" Ajouter la pagination à la méthode Index
" Ajouter des liens de pagination
" Créer une page À propos

Prérequis
Implémenter la fonctionnalité CRUD

Ajouter des liens de tri de colonne


Pour ajouter le tri à la page d’index des étudiants, vous allez modifier la méthode Index
du contrôleur Students et ajouter du code à la vue de l’index des étudiants.

Ajouter la fonctionnalité de tri à la méthode Index


Dans StudentsController.cs , remplacez la méthode Index par le code ci-dessous :

C#

public async Task<IActionResult> Index(string sortOrder)


{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc"
: "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
var students = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}
Ce code reçoit un paramètre sortOrder à partir de la chaîne de requête dans l’URL. La
valeur de chaîne de requête est fournie par ASP.NET Core MVC en tant que paramètre à
la méthode d’action. Le paramètre sera la chaîne « Name » ou « Date », éventuellement
suivie d’un trait de soulignement et de la chaîne « desc » pour spécifier l’ordre
décroissant. L’ordre de tri par défaut est croissant.

La première fois que la page d’index est demandée, il n’y a pas de chaîne de requête.
Les étudiants sont affichés dans l’ordre croissant par leur nom, ce qui correspond au
paramétrage par défaut de l’instruction switch . Quand l’utilisateur clique sur un lien
hypertexte d’en-tête de colonne, la valeur sortOrder appropriée est fournie dans la
chaîne de requête.

Les deux éléments ViewData (NameSortParm et DateSortParm) sont utilisés par la vue
pour configurer les liens hypertexte d’en-tête de colonne avec les valeurs de chaîne de
requête appropriées.

C#

public async Task<IActionResult> Index(string sortOrder)


{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc"
: "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
var students = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}

Il s’agit d’instructions ternaires. La première spécifie que si le paramètre sortOrder est


null ou vide, NameSortParm doit être défini sur « name_desc » ; sinon, il doit être défini
sur une chaîne vide. Ces deux instructions permettent à la vue de définir les liens
hypertexte d’en-tête de colonne comme suit :
Ordre de tri actuel Lien hypertexte Nom de famille Lien hypertexte Date

Nom de famille croissant descending ascending

Nom de famille décroissant ascending ascending

Date croissante ascending descending

Date décroissante ascending ascending

La méthode utilise LINQ to Entities pour spécifier la colonne d’après laquelle effectuer le
tri. Le code crée une variable IQueryable avant l’instruction switch, la modifie dans
l’instruction switch et appelle la méthode ToListAsync après l’instruction switch .
Lorsque vous créez et modifiez des variables IQueryable , aucune requête n’est envoyée
à la base de données. La requête n’est pas exécutée tant que vous ne convertissez pas
l’objet IQueryable en collection en appelant une méthode telle que ToListAsync . Par
conséquent, ce code génère une requête unique qui n’est pas exécutée avant
l’instruction return View .

Ce code peut devenir très détaillé avec un grand nombre de colonnes. Le dernier
didacticiel de cette série montre comment écrire du code qui vous permet de
transmettre le nom de la colonne OrderBy dans une variable chaîne.

Ajouter des liens hypertexte d’en-tête de colonne dans la


vue de l’index des étudiants
Remplacez le code dans Views/Students/Index.cshtml par le code suivant pour ajouter
des liens hypertexte d’en-tête de colonne. Les lignes modifiées apparaissent en
surbrillance.

CSHTML

@model IEnumerable<ContosoUniversity.Models.Student>

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
<a asp-action="Index" asp-route-
sortOrder="@ViewData["NameSortParm"]">@Html.DisplayNameFor(model =>
model.LastName)</a>
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
<a asp-action="Index" asp-route-
sortOrder="@ViewData["DateSortParm"]">@Html.DisplayNameFor(model =>
model.EnrollmentDate)</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a>
|
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Ce code utilise les informations figurant dans les propriétés ViewData pour configurer
des liens hypertexte avec les valeurs de chaîne de requête appropriées.

Exécutez l’application, sélectionnez l’onglet Students, puis cliquez sur les en-têtes des
colonnes Last Name et Enrollment Date pour vérifier que le tri fonctionne.
Ajouter une zone Rechercher
Pour ajouter le filtrage à la page d’index des étudiants, vous allez ajouter une zone de
texte et un bouton d’envoi à la vue et apporter les modifications correspondantes dans
la méthode Index . La zone de texte vous permet d’entrer une chaîne à rechercher dans
les champs de prénom et de nom.

Ajouter la fonctionnalité de filtrage à la méthode Index


Dans StudentsController.cs , remplacez la méthode Index par le code suivant (les
modifications apparaissent en surbrillance).

C#

public async Task<IActionResult> Index(string sortOrder, string


searchString)
{
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc"
: "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
ViewData["CurrentFilter"] = searchString;

var students = from s in _context.Students


select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}
return View(await students.AsNoTracking().ToListAsync());
}

Vous avez ajouté un paramètre searchString à la méthode Index . La valeur de chaîne


de recherche est reçue à partir d’une zone de texte que vous ajouterez à la vue Index.
Vous avez également ajouté à l’instruction LINQ une clause where qui sélectionne
uniquement les étudiants dont le prénom ou le nom contient la chaîne de recherche.
L’instruction qui ajoute la clause where est exécutée uniquement s’il existe une valeur à
rechercher.

7 Notes

Ici, vous appelez la méthode Where sur un objet IQueryable , et le filtre sera traité
sur le serveur. Dans certains scénarios, vous pouvez appeler la méthode Where en
tant que méthode d’extension sur une collection en mémoire. (Par exemple,
supposons que vous modifiez la référence en _context.Students pour qu’au lieu
d’un EF DbSet , elle référence une méthode de dépôt qui retourne une collection
IEnumerable .) Le résultat est normalement le même, mais dans certains cas il peut

être différent.

Par exemple, l’implémentation par le .NET Framework de la méthode Contains


effectue une comparaison respectant la casse par défaut, mais dans SQL Server,
cela est déterminé par le paramètre de classement de l’instance SQL Server. Ce
paramètre définit par défaut le non-respect de la casse. Vous pouvez appeler la
méthode ToUpper pour rendre explicitement le test sensible à la casse : Where(s =>
s.LastName.ToUpper().Contains(searchString.ToUpper()). Cela garantit que les
résultats resteront les mêmes si vous modifiez le code ultérieurement pour utiliser
un référentiel qui renverra une collection IEnumerable à la place d’un objet
IQueryable . (Lorsque vous appelez la méthode Contains sur une collection
IEnumerable , vous obtenez l’implémentation du .NET Framework ; lorsque vous

l’appelez sur un objet IQueryable , vous obtenez l’implémentation du fournisseur de


base de données.) Toutefois, cette solution engendre une pénalité de
performances. Le code ToUpper place une fonction dans la clause WHERE de
l’instruction TSQL SELECT. Elle empêche l’optimiseur d’utiliser un index. Étant donné
que SQL est généralement installé comme non sensible à la casse, il est préférable
d’éviter le code ToUpper jusqu’à ce que vous ayez migré vers un magasin de
données qui respecte la casse.

Ajouter une zone de recherche à la vue de l’index des


étudiants
Dans Views/Student/Index.cshtml , ajoutez le code en surbrillance immédiatement avant
la balise d’ouverture de table afin de créer une légende, une zone de texte et un bouton
de recherche.

CSHTML

<p>
<a asp-action="Create">Create New</a>
</p>

<form asp-action="Index" method="get">


<div class="form-actions no-color">
<p>
Find by name: <input type="text" name="SearchString"
value="@ViewData["CurrentFilter"]" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>

<table class="table">

Ce code utilise le Tag Helper <form> pour ajouter le bouton et la zone de texte de
recherche. Par défaut, le Tag Helper <form> envoie les données de formulaire avec un
POST, ce qui signifie que les paramètres sont transmis dans le corps du message HTTP
et non pas dans l’URL sous forme de chaînes de requête. Lorsque vous spécifiez HTTP
GET, les données de formulaire sont transmises dans l’URL sous forme de chaînes de
requête, ce qui permet aux utilisateurs d’ajouter l’URL aux favoris. Les recommandations
du W3C stipulent que vous devez utiliser GET quand l’action ne produit pas de mise à
jour.

Exécutez l’application, sélectionnez l’onglet Students, entrez une chaîne de recherche,


puis cliquez sur Rechercher pour vérifier que le filtrage fonctionne.

Notez que l’URL contient la chaîne de recherche.

HTML

http://localhost:5813/Students?SearchString=an

Si vous marquez cette page d’un signet, vous obtenez la liste filtrée lorsque vous utilisez
le signet. L’ajout de method="get" dans la balise form est ce qui a provoqué la
génération de la chaîne de requête.

À ce stade, si vous cliquez sur un lien de tri d’en-tête de colonne, vous perdez la valeur
de filtre que vous avez entrée dans la zone Rechercher. Vous corrigerez cela dans la
section suivante.
Ajouter la pagination à l'index des étudiants
Pour ajouter le changement de page à la page d’index des étudiants, vous allez créer
une classe PaginatedList qui utilise les instructions Skip et Take pour filtrer les
données sur le serveur au lieu de toujours récupérer toutes les lignes de la table.
Ensuite, vous apporterez des modifications supplémentaires dans la méthode Index et
ajouterez des boutons de changement de page dans la vue Index . L’illustration suivante
montre les boutons de pagination.

Dans le dossier du projet, créez PaginatedList.cs , puis remplacez le code du modèle


par le code suivant.

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }

public PaginatedList(List<T> items, int count, int pageIndex, int


pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);

this.AddRange(items);
}

public bool HasPreviousPage => PageIndex > 1;

public bool HasNextPage => PageIndex < TotalPages;

public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T>


source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageIndex - 1) *
pageSize).Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}

La méthode CreateAsync de ce code accepte la taille de page et le numéro de page, et


applique les instructions Skip et Take appropriées à IQueryable . Quand la méthode
ToListAsync est appelée sur IQueryable , elle renvoie une liste contenant uniquement la

page demandée. Les propriétés HasPreviousPage et HasNextPage peuvent être utilisées


pour activer ou désactiver les boutons de changement de page Précédent et Suivant.

Une méthode CreateAsync est utilisée à la place d’un constructeur pour créer l’objet
PaginatedList<T> , car les constructeurs ne peuvent pas exécuter de code asynchrone.

Ajouter la pagination à la méthode Index


Dans StudentsController.cs , remplacez la méthode Index par le code ci-dessous.

C#

public async Task<IActionResult> Index(


string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc"
: "";
ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";

if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}

ViewData["CurrentFilter"] = searchString;

var students = from s in _context.Students


select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
students = students.OrderByDescending(s => s.LastName);
break;
case "Date":
students = students.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
students = students.OrderByDescending(s => s.EnrollmentDate);
break;
default:
students = students.OrderBy(s => s.LastName);
break;
}

int pageSize = 3;
return View(await
PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1,
pageSize));
}

Ce code ajoute un paramètre de numéro de page, un paramètre d’ordre de tri actuel et


un paramètre de filtre actuel à la signature de la méthode.

C#

public async Task<IActionResult> Index(


string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)

La première fois que la page s’affiche, ou si l’utilisateur n’a pas cliqué sur un lien de
changement de page ni de tri, tous les paramètres sont Null. Si l’utilisateur clique sur un
lien de changement de page, la variable de page contient le numéro de page à afficher.

L’élément ViewData nommé CurrentSort fournit à l’affichage l’ordre de tri actuel, car il
doit être inclus dans les liens de changement de page pour que l’ordre de tri soit
conservé lors du changement de page.

L’élément ViewData nommé CurrentFilter fournit à la vue la chaîne de filtre actuelle.


Cette valeur doit être incluse dans les liens de changement de page pour que les
paramètres de filtre soient conservés lors du changement de page, et elle doit être
restaurée dans la zone de texte lorsque la page est réaffichée.

Si la chaîne de recherche est modifiée au cours du changement de page, la page doit


être réinitialisée à 1, car le nouveau filtre peut entraîner l’affichage de données
différentes. La chaîne de recherche est modifiée quand une valeur est entrée dans la
zone de texte et que le bouton d’envoi est enfoncé. Dans ce cas, le paramètre
searchString n’est pas Null.

C#

if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}

À la fin de la méthode Index , la méthode PaginatedList.CreateAsync convertit la


requête d’étudiant en une page individuelle d’étudiants dans un type de collection qui
prend en charge le changement de page. Cette page individuelle d’étudiants est alors
transmise à la vue.

C#

return View(await
PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1,
pageSize));
La méthode PaginatedList.CreateAsync accepte un numéro de page. Les deux points
d’interrogation représentent l’opérateur de fusion Null. L’opérateur de fusion Null
définit une valeur par défaut pour un type nullable ; l’expression (pageNumber ?? 1)
indique de renvoyer la valeur de pageNumber si elle a une valeur, ou de renvoyer 1 si
pageNumber a la valeur Null.

Ajouter des liens de pagination


Dans Views/Students/Index.cshtml , remplacez le code existant par le code suivant. Les
modifications sont mises en surbrillance.

CSHTML

@model PaginatedList<ContosoUniversity.Models.Student>

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
<a asp-action="Create">Create New</a>
</p>

<form asp-action="Index" method="get">


<div class="form-actions no-color">
<p>
Find by name: <input type="text" name="SearchString"
value="@ViewData["CurrentFilter"]" />
<input type="submit" value="Search" class="btn btn-default" /> |
<a asp-action="Index">Back to Full List</a>
</p>
</div>
</form>

<table class="table">
<thead>
<tr>
<th>
<a asp-action="Index" asp-route-
sortOrder="@ViewData["NameSortParm"]" asp-route-
currentFilter="@ViewData["CurrentFilter"]">Last Name</a>
</th>
<th>
First Name
</th>
<th>
<a asp-action="Index" asp-route-
sortOrder="@ViewData["DateSortParm"]" asp-route-
currentFilter="@ViewData["CurrentFilter"]">Enrollment Date</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-
id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-
id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

@{
var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.HasNextPage ? "disabled" : "";
}

<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-pageNumber="@(Model.PageIndex - 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @prevDisabled">
Previous
</a>
<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-pageNumber="@(Model.PageIndex + 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @nextDisabled">
Next
</a>
L’instruction @model en haut de la page spécifie que la vue obtient désormais un objet
PaginatedList<T> à la place d’un objet List<T> .

Les liens d’en-tête de colonne utilisent la chaîne de requête pour transmettre la chaîne
de recherche actuelle au contrôleur afin que l’utilisateur puisse trier les résultats de
filtrage :

HTML

<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-


route-currentFilter ="@ViewData["CurrentFilter"]">Enrollment Date</a>

Les boutons de changement de page sont affichés par des Tag Helpers :

HTML

<a asp-action="Index"
asp-route-sortOrder="@ViewData["CurrentSort"]"
asp-route-pageNumber="@(Model.PageIndex - 1)"
asp-route-currentFilter="@ViewData["CurrentFilter"]"
class="btn btn-default @prevDisabled">
Previous
</a>

Exécutez l’application et accédez à la page des étudiants.


Cliquez sur les liens de changement de page dans différents ordres de tri pour vérifier
que le changement de page fonctionne. Ensuite, entrez une chaîne de recherche et
essayez de changer de page à nouveau pour vérifier que le changement de page
fonctionne correctement avec le tri et le filtrage.

Créer une page À propos


Pour la page About du site web de Contoso University, vous afficherez le nombre
d’étudiants inscrits pour chaque date d’inscription. Cela nécessite un regroupement et
des calculs simples sur les groupes. Pour ce faire, vous devez effectuer les opérations
suivantes :

Créez une classe de modèle de vue pour les données que vous devez transmettre
à la vue.
Créez la méthode About dans le contrôleur Home.
Créer la vue About.

Créer le modèle d’affichage


Créez un dossier SchoolViewModels dans le dossier Models.
Dans le nouveau dossier, ajoutez un fichier de classe EnrollmentDateGroup.cs et
remplacez le code du modèle par le code suivant :

C#

using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }

public int StudentCount { get; set; }


}
}

Modifier le contrôleur Home


Dans HomeController.cs , ajoutez les instructions suivantes en haut du fichier :

C#

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Data;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.Extensions.Logging;

Ajoutez une variable de classe pour le contexte de base de données immédiatement


après l’accolade ouvrante de la classe et obtenez une instance du contexte à partir
d’ASP.NET Core DI :

C#

public class HomeController : Controller


{
private readonly ILogger<HomeController> _logger;
private readonly SchoolContext _context;

public HomeController(ILogger<HomeController> logger, SchoolContext


context)
{
_logger = logger;
_context = context;
}
Ajoutez une méthode About avec le code suivant :

C#

public async Task<ActionResult> About()


{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};
return View(await data.AsNoTracking().ToListAsync());
}

L’instruction LINQ regroupe les entités Student par date d’inscription, calcule le nombre
d’entités dans chaque groupe et stocke les résultats dans une collection d’objets de
modèle de vue EnrollmentDateGroup .

Créer la vue About


Ajoutez un fichier Views/Home/About.cshtml avec le code suivant :

CSHTML

@model
IEnumerable<ContosoUniversity.Models.SchoolViewModels.EnrollmentDateGroup>

@{
ViewData["Title"] = "Student Body Statistics";
}

<h2>Student Body Statistics</h2>

<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>

@foreach (var item in Model)


{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>

Exécutez l’application et accédez à la page About. Le nombre d’étudiants pour chaque


date d’inscription s’affiche dans une table.

Obtenir le code
Télécharger ou afficher l’application complète.

Étapes suivantes
Dans ce tutoriel, vous allez :

" Ajouter des liens de tri de colonne


" Ajouter une zone Rechercher
" Ajouter la pagination à l'index des étudiants
" Ajouter la pagination à la méthode Index
" Ajouter des liens de pagination
" Page À propos créée

Passez au tutoriel suivant pour découvrir comment gérer les modifications du modèle
de données à l’aide de migrations.

Prochain : Gérer les modifications du modèle de données

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.
 Indiquer des commentaires sur
le produit
Tutoriel : Partie 5, appliquer des
migrations à l’exemple Contoso
University
Article • 30/11/2023

Dans ce tutoriel, vous utilisez la fonctionnalité de migrations EF Core pour gérer les
modifications du modèle de données. Dans les didacticiels suivants, vous allez ajouter
d’autres migrations au fil de la modification du modèle de données.

Dans ce didacticiel, vous avez effectué les actions suivantes :

" En savoir plus sur les migrations


" Créer une migration initiale
" Examiner les méthodes Up et Down
" En savoir plus sur la capture instantanée du modèle de données
" Appliquer la migration

Prérequis
Tri, filtrage et pagination

À propos des migrations


Quand vous développez une nouvelle application, votre modèle de données change
fréquemment et, chaque fois que le modèle change, il n’est plus en synchronisation avec
la base de données. Vous avez démarré ce didacticiel en configurant Entity Framework
pour créer la base de données si elle n’existait pas. Ensuite, chaque fois que vous
modifiez le modèle de données, en ajoutant, supprimant ou changeant des classes
d’entité ou votre classe DbContext, vous pouvez supprimer la base de données : EF en
crée alors une nouvelle qui correspond au modèle et l’alimente avec des données de
test.

Cette méthode pour conserver la base de données en synchronisation avec le modèle


de données fonctionne bien jusqu’au déploiement de l’application en production.
Quand l’application s’exécute en production, elle stocke généralement les données que
vous voulez conserver, et vous ne voulez pas tout perdre chaque fois que vous apportez
une modification, comme ajouter une nouvelle colonne. La fonctionnalité de migrations
EF Core résout ce problème en permettant à EF de mettre à jour le schéma de base de
données au lieu de créer une nouvelle base de données.
Pour effectuer des migrations, vous pouvez utiliser la console du gestionnaire de
package (PMC) ou l’interface de ligne de commande (CLI). Ces didacticiels montrent
comment utiliser des commandes CLI. Vous trouverez des informations sur la console du
Gestionnaire de package à la fin de ce didacticiel.

Supprimer la base de données


Installez les outils EF Core en tant qu’outil global et supprimez la base de données :

CLI .NET

dotnet tool install --global dotnet-ef


dotnet ef database drop

7 Notes

Par défaut, l’architecture des fichiers binaires .NET à installer représente


l’architecture du système d’exploitation en cours d’exécution. Pour spécifier une
architecture de système d’exploitation différente, consultez dotnet tool install, --
arch option. Pour plus d'informations, consultez le problème GitHub
dotnet/AspNetCore.Docs n° 29262 .

La section suivante explique comment exécuter des commandes CLI.

Créer une migration initiale


Enregistrez vos modifications et générez le projet. Ouvrez ensuite une fenêtre
Commande et accédez au dossier du projet. Voici un moyen rapide pour le faire :

Dans l’Explorateur de solutions, cliquez sur le projet et choisissez Ouvrir le dossier


dans l’Explorateur de fichiers dans le menu contextuel.
Entrez « cmd » dans la barre d’adresses et appuyez sur Entrée.

Entrez la commande suivante dans la fenêtre Commande :

CLI .NET

dotnet ef migrations add InitialCreate

Dans les commandes précédentes, une sortie similaire à ce qui suit s’affiche :

Console

info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core initialized 'SchoolContext' using provider
'Microsoft.EntityFrameworkCore.SqlServer' with options: None
Done. To undo this action, use 'ef migrations remove'
Si vous voyez un message d’erreur « Impossible d’accéder au fichier...
ContosoUniversity.dll, car il est utilisé par un autre processus. », recherchez l’icône IIS
Express dans la barre d’état système de Windows, cliquez avec le bouton droit, puis
cliquez sur ContosoUniversity Arrêter le site>.

Examiner les méthodes Up et Down


Quand vous avez exécuté la commande migrations add , EF a généré le code qui crée la
base de données à partir de zéro. Ce code se trouve dans le dossier Migrations, dans le
fichier nommé <timestamp>_InitialCreate.cs . La méthode Up de la classe
InitialCreate crée les tables de base de données qui correspondent aux jeux d’entités

du modèle de données, et la méthode Down les supprime, comme indiqué dans


l’exemple suivant.

C#

public partial class InitialCreate : Migration


{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Course",
columns: table => new
{
CourseID = table.Column<int>(nullable: false),
Credits = table.Column<int>(nullable: false),
Title = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Course", x => x.CourseID);
});

// Additional code not shown


}

protected override void Down(MigrationBuilder migrationBuilder)


{
migrationBuilder.DropTable(
name: "Enrollment");
// Additional code not shown
}
}

La fonctionnalité Migrations appelle la méthode Up pour implémenter les modifications


du modèle de données pour une migration. Quand vous entrez une commande pour
annuler la mise à jour, Migrations appelle la méthode Down .
Ce code est celui de la migration initiale qui a été créé quand vous avez entré la
commande migrations add InitialCreate . Le paramètre de nom de la migration («
InitialCreate » dans l’exemple) est utilisé comme nom de fichier ; vous pouvez le choisir
librement. Nous vous conseillons néanmoins de choisir un mot ou une expression qui
résume ce qui est effectué dans la migration. Par exemple, vous pouvez nommer une
migration ultérieure « AjouterTableDépartement ».

Si vous avez créé la migration initiale alors que la base de données existait déjà, le code
de création de la base de données est généré, mais il n’est pas nécessaire de l’exécuter,
car la base de données correspond déjà au modèle de données. Quand vous déployez
l’application sur un autre environnement où la base de données n’existe pas encore, ce
code est exécuté pour créer votre base de données : il est donc judicieux de le tester au
préalable. C’est la raison pour laquelle vous avez précédemment annulé la base de
données : les migrations doivent pouvoir créer une base de données à partir de zéro.

Capture instantanée du modèle de données


Migrations crée une capture instantanée du schéma de base de données actuel dans
Migrations/SchoolContextModelSnapshot.cs . Quand vous ajoutez une migration, EF

détermine ce qui a changé en comparant le modèle de données au fichier de capture


instantanée.

Utilisez la commande dotnet ef migrations remove pour supprimer une migration.


dotnet ef migrations remove supprime la migration et garantit que la capture

instantanée est correctement réinitialisée. En cas d’échec de dotnet ef migrations


remove , utilisez dotnet ef migrations remove -v pour obtenir plus d’informations sur

l’échec.

Pour plus d’informations sur l’utilisation du fichier de capture instantanée, consultez


Migrations EF Core dans les environnements d’équipe.

Appliquer la migration
Dans la fenêtre Commande, entrez la commande suivante pour créer la base de
données et ses tables.

CLI .NET

dotnet ef database update


La sortie de la commande est similaire à la commande migrations add , à ceci près que
vous voyez des journaux pour les commandes SQL qui configurent la base de données.
La plupart des journaux sont omis dans l’exemple de sortie suivant. Si vous préférez ne
pas voir ce niveau de détail dans les messages des journaux, vous pouvez changer le
niveau de journalisation dans le fichier appsettings.Development.json . Pour plus
d’informations, consultez Journalisation dans .NET Core et ASP.NET Core.

text

info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core initialized 'SchoolContext' using provider
'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (274ms) [Parameters=[], CommandType='Text',
CommandTimeout='60']
CREATE DATABASE [ContosoUniversity2];
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (60ms) [Parameters=[], CommandType='Text',
CommandTimeout='60']
IF SERVERPROPERTY('EngineEdition') <> 5
BEGIN
ALTER DATABASE [ContosoUniversity2] SET READ_COMMITTED_SNAPSHOT
ON;
END;
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (15ms) [Parameters=[], CommandType='Text',
CommandTimeout='30']
CREATE TABLE [__EFMigrationsHistory] (
[MigrationId] nvarchar(150) NOT NULL,
[ProductVersion] nvarchar(32) NOT NULL,
CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId])
);

<logs omitted for brevity>

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (3ms) [Parameters=[], CommandType='Text',
CommandTimeout='30']
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20190327172701_InitialCreate', N'5.0-rtm');
Done.

Utilisez l’Explorateur d’objets SQL Server pour inspecter la base de données, comme
vous l’avez fait dans le premier didacticiel. Vous pouvez noter l’ajout d’une table
__EFMigrationsHistory, qui fait le suivi des migrations qui ont été appliquées à la base de
données. Visualisez les données de cette table : vous y voyez une ligne pour la première
migration. (Le dernier journal dans l’exemple de sortie CLI précédent montre
l’instruction INSERT qui crée cette ligne.)
Exécutez l’application pour vérifier que tout fonctionne toujours comme avant.

Comparer l’interface CLI et PMC


Les outils EF pour la gestion des migrations sont disponibles à partir de commandes CLI
.NET Core ou d’applets de commande PowerShell dans la fenêtre Console du
Gestionnaire de package de Visual Studio. Ce didacticiel montre comment utiliser
l’interface CLI, mais vous pouvez utiliser la console du Gestionnaire de package si vous
préférez.

Les commandes EF pour la console du Gestionnaire de package se trouvent dans le


package Microsoft.EntityFrameworkCore.Tools . Ce package étant inclus dans le
métapackage Microsoft.AspNetCore.App, vous n’avez pas besoin d’ajouter une
référence de package si votre application en comporte une pour
Microsoft.AspNetCore.App .

Important : il ne s’agit pas du même package que celui que vous installez pour
l’interface CLI en modifiant le fichier .csproj . Le nom de celui-ci se termine par Tools ,
contrairement au nom du package CLI qui se termine par Tools.DotNet .
Pour plus d’informations sur les commandes CLI, consultez Interface CLI .NET Core.

Pour plus d’informations sur les commandes de la console du Gestionnaire de package,


consultez Console du Gestionnaire de package (Visual Studio).

Obtenir le code
Télécharger ou afficher l’application complète.

Étape suivante
Passez au tutoriel suivant pour aborder des sujets plus avancés sur le développement du
modèle de données. Au cours de ce processus, vous allez créer et appliquer d’autres
migrations.

Créer et appliquer d’autres migrations

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : Créer un modèle de données
complexe - ASP.NET MVC avec EF Core
Article • 30/11/2023

Dans les didacticiels précédents, vous avez travaillé avec un modèle de données simple
composé de trois entités. Dans ce didacticiel, vous allez ajouter des entités et des
relations, et vous personnaliserez le modèle de données en spécifiant des règles de mise
en forme, de validation et de mappage de base de données.

Lorsque vous aurez terminé, les classes d’entité composeront le modèle de données
complet indiqué dans l’illustration suivante :
Dans ce tutoriel, vous allez :

" Personnaliser le modèle de données


" Apporter des modifications à l’entité Student
" Créer une entité Instructor
" Créer une entité OfficeAssignment
" Modifier l’entité Course
" Créer l’entité Department
" Modifier l’entité Enrollment
" Mettre à jour le contexte de base de données
" Remplir la base de données avec des données de test
" Ajouter une migration
" Changer la chaîne de connexion
" Mettre à jour la base de données

Prérequis
Utilisation des migrations EF Core

Personnaliser le modèle de données


Dans cette section, vous allez apprendre à personnaliser le modèle de données en
utilisant des attributs qui spécifient des règles de mise en forme, de validation et de
mappage de base de données. Ensuite, dans plusieurs des sections suivantes, vous allez
créer le modèle de données School complet en ajoutant des attributs aux classes que
vous avez déjà créées et en créant de nouvelles classes pour les autres types d’entités
dans le modèle.

Attribut DataType
Pour les dates d’inscription des étudiants, toutes les pages web affichent l’heure avec la
date, alors que seule la date vous intéresse dans ce champ. Vous pouvez avoir recours
aux attributs d’annotation de données pour apporter une modification au code,
permettant de corriger le format d’affichage dans chaque vue qui affiche ces données.
Pour voir un exemple de la procédure à suivre, vous allez ajouter un attribut à la
propriété EnrollmentDate dans la classe Student .

Dans Models/Student.cs , ajoutez une instruction using pour l’espace de noms


System.ComponentModel.DataAnnotations , puis ajoutez les attributs DataType et

DisplayFormat à la propriété EnrollmentDate , comme le montre l’exemple suivant :

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

L’attribut DataType sert à spécifier un type de données qui est plus spécifique que le
type intrinsèque de la base de données. Dans le cas présent, nous voulons uniquement
effectuer le suivi de la date, pas de la date et de l’heure. L’énumération DataType fournit
de nombreux types de données, tels que Date, Time, PhoneNumber, Currency ou
EmailAddress. L’attribut DataType peut également permettre à l’application de fournir
automatiquement des fonctionnalités propres au type. Par exemple, vous pouvez créer
un lien mailto: pour DataType.EmailAddress , et vous pouvez fournir un sélecteur de
date pour DataType.Date dans les navigateurs qui prennent en charge HTML5. L’attribut
DataType émet des attributs HTML 5 data- compréhensibles par les navigateurs HTML

5. Les attributs DataType ne fournissent aucune validation.

DataType.Date ne spécifie pas le format de la date qui s’affiche. Par défaut, le champ de

données est affiché conformément aux formats par défaut basés sur l’objet CultureInfo
du serveur.

L’attribut DisplayFormat est utilisé pour spécifier explicitement le format de date :

C#

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode =


true)]

Le paramètre ApplyFormatInEditMode indique que la mise en forme doit également être


appliquée quand la valeur est affichée dans une zone de texte à des fins de
modification. (Ceci peut ne pas être souhaitable pour certains champs ; par exemple,
pour les valeurs monétaires, vous ne souhaiterez peut-être pas que le symbole
monétaire figure dans la zone de texte.)

Vous pouvez utiliser l’attribut DisplayFormat seul, mais il est généralement judicieux
d’utiliser également l’attribut DataType . L’attribut DataType donne la sémantique des
données au lieu d’expliquer comment les afficher à l’écran. Il présente, par ailleurs, les
avantages suivants, dont vous ne bénéficiez pas avec DisplayFormat :
Le navigateur peut activer des fonctionnalités HTML5 (par exemple pour afficher
un contrôle de calendrier, le symbole monétaire correspondant aux paramètres
régionaux, des liens de messagerie, une certaine validation des entrées côté client,
etc.).

Par défaut, le navigateur affiche les données à l’aide du format correspondant à


vos paramètres régionaux.

Pour plus d’informations, consultez la documentation relative au Tag Helper <input>.

Exécutez l’application, accédez à la page d’index des étudiants et notez que les heures
ne sont plus affichées pour les dates d’inscription. La même chose est vraie pour toute
vue qui utilise le modèle Student.

Attribut StringLength
Vous pouvez également spécifier les règles de validation de données et les messages
d’erreur de validation à l’aide d’attributs. L’attribut StringLength définit la longueur
maximale dans la base de données, et fournit une validation côté client et côté serveur
pour ASP.NET Core MVC. Vous pouvez également spécifier la longueur de chaîne
minimale dans cet attribut, mais la valeur minimale n’a aucun impact sur le schéma de
base de données.

Supposons que vous voulez garantir que les utilisateurs n’entrent pas plus de 50
caractères pour un nom. Pour ajouter cette limitation, ajoutez des attributs
StringLength aux propriétés LastName et FirstMidName , comme indiqué dans l’exemple

suivant :

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

L’attribut StringLength n’empêche pas un utilisateur d’entrer un espace blanc comme


nom. Vous pouvez utiliser l’attribut RegularExpression pour appliquer des restrictions à
l’entrée. Par exemple, le code suivant exige que le premier caractère soit en majuscule et
que les autres caractères soient alphabétiques :

C#

[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]

L’attribut MaxLength fournit des fonctionnalités similaires à l’attribut StringLength , mais


n’assure pas la validation côté client.

Le modèle de base de données a maintenant changé d’une manière qui nécessite la


modification du schéma de base de données. Vous allez utiliser des migrations pour
mettre à jour le schéma sans perdre les données que vous avez éventuellement ajoutées
à la base de données via l’interface utilisateur de l’application.

Enregistrez vos modifications et générez le projet. Ensuite, ouvrez la fenêtre de


commande dans le dossier du projet et entrez les commandes suivantes :

CLI .NET

dotnet ef migrations add MaxLengthOnNames

CLI .NET

dotnet ef database update

La commande migrations add vous avertit qu’une perte de données peut se produire,
car la modification raccourcit la longueur maximale de deux colonnes. Les migrations
permettent de créer un fichier nommé <timeStamp>_MaxLengthOnNames.cs . Ce fichier
contient du code dans la méthode Up qui met à jour la base de données pour qu’elle
corresponde au modèle de données actuel. La commande database update a exécuté ce
code.

L’horodatage utilisé comme préfixe du nom de fichier migrations est utilisé par Entity
Framework pour ordonner les migrations. Vous pouvez créer plusieurs migrations avant
d’exécuter la commande de mise à jour de base de données, puis toutes les migrations
sont appliquées dans l’ordre où elles ont été créées.

Exécutez l’application, sélectionnez l’onglet Students, cliquez sur Create New et essayez
d’entrer un nom de plus de 50 caractères. L’application doit empêcher cette opération.

Attribut Column
Vous pouvez également utiliser des attributs pour contrôler la façon dont les classes et
les propriétés sont mappées à la base de données. Supposons que vous aviez utilisé le
nom FirstMidName pour le champ de prénom, car le champ peut également contenir un
deuxième prénom. Mais vous souhaitez que la colonne de base de données soit
nommée FirstName , car les utilisateurs qui écriront des requêtes ad-hoc par rapport à la
base de données sont habitués à ce nom. Pour effectuer ce mappage, vous pouvez
utiliser l’attribut Column .

L’attribut Column spécifie que lorsque la base de données sera créée, la colonne de la
table Student qui est mappée sur la propriété FirstMidName sera nommée FirstName .
En d’autres termes, lorsque votre code fait référence à Student.FirstMidName , les
données proviennent de la colonne FirstName de la table Student ou y sont mises à
jour. Si vous ne nommez pas les colonnes, elles obtiennent le nom de la propriété.

Dans le fichier Student.cs , ajoutez une instruction using pour


System.ComponentModel.DataAnnotations.Schema , puis ajoutez l’attribut de nom de

colonne à la propriété FirstMidName , comme le montre le code suivant mis en évidence :

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50)]
[Column("FirstName")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

L’ajout de l’attribut Column change le modèle sur lequel repose SchoolContext , donc il
ne correspond pas à la base de données.

Enregistrez vos modifications et générez le projet. Ensuite, ouvrez la fenêtre de


commande dans le dossier du projet et entrez les commandes suivantes pour créer une
autre migration :

CLI .NET

dotnet ef migrations add ColumnFirstName

CLI .NET

dotnet ef database update


Dans l’Explorateur d’objets SQL Server, ouvrez le concepteur de tables Student en
double-cliquant sur la table Student.

Avant d’appliquer les deux premières migrations, les colonnes de nom étaient de type
nvarchar(MAX). Elles sont maintenant de type nvarchar(50) et le nom de colonne
FirstMidName a été remplacé par FirstName.

7 Notes

Si vous essayez de compiler avant d’avoir fini de créer toutes les classes d’entité
dans les sections suivantes, vous pouvez obtenir des erreurs de compilation.

Apporter des modifications à l’entité Student


Dans Models/Student.cs , remplacez le code que vous avez ajouté par le code suivant.
Les modifications sont mises en surbrillance.

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50)]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}

public ICollection<Enrollment> Enrollments { get; set; }


}
}

Attribut Required
L’attribut Required fait des propriétés de nom des champs obligatoires. L’attribut
Required n’est pas requis pour les types non nullables tels que les types valeur

(DateTime, int, double, float, etc.). Les types qui n’acceptent pas les valeurs Null sont
traités automatiquement comme des champs obligatoires.

L'attribut Required doit être utilisé avec MinimumLength pour appliquer MinimumLength .
C#

[Display(Name = "Last Name")]


[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }

Attribut Display
L’attribut Display spécifie que la légende pour les zones de texte doit être « First Name
», « Last Name », « Full Name » et « Enrollment Date », au lieu du nom de propriété
dans chaque instance (qui n’a pas d’espace pour séparer les mots).

Propriété calculée FullName


FullName est une propriété calculée qui retourne une valeur créée par concaténation de

deux autres propriétés. Par conséquent, elle a uniquement un accesseur get et aucune
colonne FullName n’est générée dans la base de données.

Créer une entité Instructor

Créez Models/Instructor.cs , en remplaçant le code du modèle par le code suivant :

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }

[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }

[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }

[Display(Name = "Full Name")]


public string FullName
{
get { return LastName + ", " + FirstMidName; }
}

public ICollection<CourseAssignment> CourseAssignments { get; set; }


public OfficeAssignment OfficeAssignment { get; set; }
}
}

Notez que plusieurs propriétés sont identiques dans les entités Student et Instructor.
Dans le didacticiel Implémentation de l’héritage plus loin dans cette série, vous allez
refactoriser ce code pour éliminer la redondance.

Vous pouvez placer plusieurs attributs sur une seule ligne et écrire les attributs HireDate
comme suit :

C#

[DataType(DataType.Date),Display(Name = "Hire
Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]

Propriétés de navigation CourseAssignments et


OfficeAssignment
Les propriétés CourseAssignments et OfficeAssignment sont des propriétés de
navigation.
Un formateur pouvant animer un nombre quelconque de cours, CourseAssignments est
défini comme une collection.

C#

public ICollection<CourseAssignment> CourseAssignments { get; set; }

Si une propriété de navigation peut contenir plusieurs entités, son type doit être une
liste dans laquelle les entrées peuvent être ajoutées, supprimées et mises à jour. Vous
pouvez spécifier ICollection<T> ou un type tel que List<T> ou HashSet<T> . Si vous
spécifiez ICollection<T> , EF crée une collection HashSet<T> par défaut.

La raison pour laquelle ce sont des entités CourseAssignment est expliquée ci-dessous
dans la section sur les relations plusieurs-à-plusieurs.

Les règles d’entreprise de Contoso University stipulent qu’un formateur peut avoir au
plus un bureau, de sorte que la propriété OfficeAssignment contient une seule entité
OfficeAssignment (qui peut être null si aucun bureau n’est affecté).

C#

public OfficeAssignment OfficeAssignment { get; set; }

Créer une entité OfficeAssignment

Créez Models/OfficeAssignment.cs avec le code suivant :

C#

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }

public Instructor Instructor { get; set; }


}
}

Attribut Key
Il existe une relation un-à-zéro-ou-un entre les entités Instructor et OfficeAssignment .
Une affectation de bureau existe uniquement par rapport à l’instructeur auquel elle est
affectée. Ainsi, sa clé primaire est également sa clé étrangère pour l’entité Instructor .
Toutefois, Entity Framework ne peut pas reconnaître automatiquement InstructorID en
tant que clé primaire de cette entité, car son nom ne suit pas la convention de
nommage pour ID ou classnameID . Par conséquent, l’attribut Key est utilisé pour
l’identifier comme clé :

C#

[Key]
public int InstructorID { get; set; }

Vous pouvez également utiliser l’attribut Key si l’entité a sa propre clé primaire, mais
que vous souhaitez nommer la propriété autrement que classnameID ou ID.

Par défaut, EF traite la clé comme n’étant pas générée par la base de données, car la
colonne est utilisée pour une relation d’identification.

Propriété de navigation Instructor


L’entité Instructor a une propriété de navigation OfficeAssignment nullable (parce qu’un
formateur n’a peut-être pas d’affectation de bureau) et l’entité OfficeAssignment a une
propriété de navigation Instructor non nullable (comme une affectation de bureau ne
peut pas exister sans formateur, InstructorID est non nullable). Lorsqu’une entité
Instructor a une entité OfficeAssignment associée, chaque entité a une référence à
l’autre dans sa propriété de navigation.

Vous pouvez placer un attribut [Required] sur la propriété de navigation du formateur


pour spécifier qu’il doit y avoir un formateur associé, mais vous n’êtes pas obligé de le
faire, car la clé étrangère InstructorID (qui est également la clé pour cette table) est
non nullable.

Modifier l’entité Course

Dans Models/Course.cs , remplacez le code que vous avez ajouté par le code suivant. Les
modifications sont mises en surbrillance.

C#

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Title { get; set; }

[Range(0, 5)]
public int Credits { get; set; }

public int DepartmentID { get; set; }

public Department Department { get; set; }


public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<CourseAssignment> CourseAssignments { get; set; }
}
}
L’entité de cours a une propriété de clé étrangère DepartmentID qui pointe sur l’entité
Department associée et elle a une propriété de navigation Department .

Entity Framework ne vous demande pas d’ajouter une propriété de clé étrangère à votre
modèle de données lorsque vous avez une propriété de navigation pour une entité
associée. EF crée automatiquement des clés étrangères dans la base de données partout
où elles sont nécessaires et crée des propriétés fantôme pour elles. Mais le fait d’avoir la
clé étrangère dans le modèle de données peut rendre les mises à jour plus simples et
plus efficaces. Par exemple, quand vous récupérez une entité Course à modifier, l’entité
Department a une valeur nulle si vous ne la chargez pas. Ainsi, quand vous mettez à jour

l’entité Course , vous devez d’abord récupérer l’entité Department . Quand la propriété de
clé étrangère DepartmentID est incluse dans le modèle de données, vous n’avez pas
besoin de récupérer l’entité Department avant d’effectuer la mise à jour.

Attribut DatabaseGenerated
L’attribut DatabaseGenerated avec le paramètre None sur la propriété CourseID spécifie
que les valeurs de clé primaire sont fournies par l’utilisateur au lieu d’être générées par
la base de données.

C#

[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

Par défaut, Entity Framework suppose que les valeurs de clé primaire sont générées par
la base de données. C’est ce que vous souhaitez dans la plupart des scénarios. Toutefois,
pour les entités Course , vous allez utiliser un numéro de cours spécifié par l’utilisateur,
par exemple la série 1 000 pour un service, la série 2 000 pour un autre service, etc.

L’attribut DatabaseGenerated peut également être utilisé pour générer des valeurs par
défaut, comme dans le cas des colonnes de base de données utilisées pour enregistrer
la date à laquelle une ligne a été créée ou mise à jour. Pour plus d’informations,
consultez Propriétés générées.

Propriétés de clé étrangère et de navigation


Les propriétés de clé étrangère et les propriétés de navigation dans l’entité Course
reflètent les relations suivantes :
Un cours est affecté à un seul département, donc il existe une clé étrangère
DepartmentID et une propriété de navigation Department pour les raisons mentionnées

ci-dessus.

C#

public int DepartmentID { get; set; }


public Department Department { get; set; }

Un cours pouvant avoir un nombre quelconque d’étudiants inscrits, la propriété de


navigation Enrollments est une collection :

C#

public ICollection<Enrollment> Enrollments { get; set; }

Un cours peut être animé par plusieurs formateurs, si bien que la propriété de
navigation CourseAssignments est une collection (le type CourseAssignment est expliqué
ultérieurement) :

C#

public ICollection<CourseAssignment> CourseAssignments { get; set; }

Créer l’entité Department

Créez Models/Department.cs avec le code suivant :

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Name { get; set; }

[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }

public int? InstructorID { get; set; }

public Instructor Administrator { get; set; }


public ICollection<Course> Courses { get; set; }
}
}

Attribut Column
Précédemment, vous avez utilisé l’attribut Column pour changer le mappage de noms de
colonne. Dans le code de l’entité Department , l’attribut Column est utilisé pour changer le
mappage de type de données SQL afin que la colonne soit définie à l’aide du type
SQL Server money dans la base de données :

C#

[Column(TypeName="money")]
public decimal Budget { get; set; }

Le mappage de colonnes n’est généralement pas nécessaire, car Entity Framework


choisit le type de données SQL Server approprié en fonction du type CLR que vous
définissez pour la propriété. Le type CLR decimal est mappé à un type SQL Server
decimal . Toutefois, dans ce cas, vous savez que la colonne contiendra des montants en

devise et que le type de données monétaire est plus approprié pour cela.
Propriétés de clé étrangère et de navigation
Les propriétés de clé étrangère et de navigation reflètent les relations suivantes :

Un département peut ou non avoir un administrateur, et un administrateur est toujours


un formateur. Par conséquent, la propriété InstructorID est incluse en tant que clé
étrangère à l’entité Instructor, et un point d’interrogation est ajouté après la désignation
du type int pour marquer la propriété comme nullable. La propriété de navigation est
nommée Administrator , mais elle contient une entité Instructor :

C#

public int? InstructorID { get; set; }


public Instructor Administrator { get; set; }

Un département pouvant avoir de nombreux cours, il existe une propriété de navigation


Courses :

C#

public ICollection<Course> Courses { get; set; }

7 Notes

Par convention, Entity Framework permet la suppression en cascade pour les clés
étrangères non nullables et pour les relations plusieurs à plusieurs. Cela peut
entraîner des règles de suppression en cascade circulaires, qui provoqueront une
exception lorsque vous essaierez d’ajouter une migration. Par exemple, si vous
n’avez pas défini la propriété Department.InstructorID en tant que propriété
Nullable, EF configure une règle de suppression en cascade pour supprimer le
service quand vous supprimez l’instructeur, ce qui n’est pas ce que vous souhaitez
voir se produire. Si vos règles d’entreprise exigent que la propriété InstructorID
soit non nullable, vous devez utiliser l’instruction d’API Fluent suivante pour
désactiver la suppression en cascade sur la relation :

C#

modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Modifier l’entité Enrollment

Dans Models/Enrollment.cs , remplacez le code que vous avez ajouté par le code
suivant :

C#

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}

public class Enrollment


{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }

public Course Course { get; set; }


public Student Student { get; set; }
}
}

Propriétés de clé étrangère et de navigation


Les propriétés de clé étrangère et de navigation reflètent les relations suivantes :

Un enregistrement d’inscription est utilisé pour un cours unique, si bien qu’il existe une
propriété de clé étrangère CourseID et une propriété de navigation Course :
C#

public int CourseID { get; set; }


public Course Course { get; set; }

Un enregistrement d’inscription est utilisé pour un étudiant unique, si bien qu’il existe
une propriété de clé étrangère StudentID et une propriété de navigation Student :

C#

public int StudentID { get; set; }


public Student Student { get; set; }

Relations plusieurs-à-plusieurs
Il existe une relation plusieurs-à-plusieurs entre les entités Student et Course . L’entité
Enrollment fonctionne comme une table de jointure plusieurs-à-plusieurs avec une

charge utile dans la base de données. « Avec une charge utile » signifie que la table
Enrollment contient des données supplémentaires en plus des clés étrangères pour les

tables jointes (dans le cas présent, une clé primaire et une propriété Grade ).

L’illustration suivante montre à quoi ressemblent ces relations dans un diagramme


d’entité. (Ce diagramme a été généré à l’aide d’Entity Framework Power Tools pour EF
6.x ; la création du diagramme ne fait pas partie de ce didacticiel, elle est uniquement
utilisée ici à titre d’illustration.)
Chaque ligne de relation comporte un 1 à une extrémité et un astérisque (*) à l’autre, ce
qui indique une relation un-à-plusieurs.

Si la table Enrollment n’inclut pas d’informations relatives aux notes, elle doit
uniquement contenir les deux clés étrangères CourseID et StudentID . Dans ce cas, ce
serait une table de jointure plusieurs-à-plusieurs sans charge utile (ou une table de
jointure pure) dans la base de données. Les entités Instructor et Course ont ce genre
de relation plusieurs-à-plusieurs. L’étape suivante consiste à créer une classe d’entité qui
fonctionne en tant que table de jointure sans charge utile.

EF Core prend en charge les tables de jointure implicites pour les relations plusieurs-à-
plusieurs. Toutefois, ce tutoriel n’a pas été mis à jour pour utiliser une table de jointure
implicite. Consultez Relations plusieurs-à-plusieurs, la version Razor Pages de ce tutoriel,
qui a été mise à jour.

Entité CourseAssignment
Créez Models/CourseAssignment.cs avec le code suivant :

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class CourseAssignment
{
public int InstructorID { get; set; }
public int CourseID { get; set; }
public Instructor Instructor { get; set; }
public Course Course { get; set; }
}
}

Noms des entités de jointure


Une table de jointure est requise dans la base de données pour la relation plusieurs-à-
plusieurs entre formateurs et cours, et elle doit être représentée par un jeu d’entités. Il
est courant de nommer une entité de jointure EntityName1EntityName2 , ce qui donnerait
dans ce cas CourseInstructor . Toutefois, nous vous recommandons de choisir un nom
qui décrit la relation. Les modèles de données sont simples au départ, puis croissent,
avec des jointures sans charge utile qui obtiennent souvent des charges utiles plus tard.
Si vous commencez avec un nom d’entité descriptif, vous n’aurez pas à le modifier par la
suite. Dans l’idéal, l’entité de jointure aura son propre nom (éventuellement un mot
unique) naturel dans le domaine d’entreprise. Par exemple, les livres et les clients
pourraient être liés par le biais d’évaluations. Pour cette relation, CourseAssignment est
un meilleur choix que CourseInstructor .

Clé composite
Étant donné que les clés étrangères ne sont pas nullables et qu’elles identifient
ensemble de façon unique chaque ligne de la table, une clé primaire distincte n’est pas
requise. Les propriétés InstructorID et CourseID doivent servir de clé primaire
composite. La seule façon d’identifier des clés primaires composites pour EF consiste à
utiliser l’API Fluent (ce n’est pas possible à l’aide d’attributs). Vous allez voir comment
configurer la clé primaire composite dans la section suivante.

La clé composite garantit qu’en ayant plusieurs lignes pour un cours et plusieurs lignes
pour un formateur, vous ne puissiez pas avoir plusieurs lignes pour les mêmes
formateur et cours. L’entité de jointure Enrollment définit sa propre clé primaire, si bien
que les doublons de ce type sont possibles. Pour éviter ces doublons, vous pourriez
ajouter un index unique sur les champs de clé étrangère ou configurer Enrollment avec
une clé composite primaire similaire à CourseAssignment . Pour plus d’informations,
consultez Index.

Mettre à jour le contexte de base de données


Ajoutez le code mis en évidence ci-dessous au fichier Data/SchoolContext.cs :

C#

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>
().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>
().ToTable("CourseAssignment");

modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}

Ce code ajoute les nouvelles entités et configure la clé primaire composite de l’entité
CourseAssignment.

À propos de l’alternative d’API Fluent


Le code dans la méthode OnModelCreating de la classe DbContext utilise l’API Fluent
pour configurer le comportement EF. L’API est appelée « Fluent », car elle consiste
souvent à enchaîner une série d’appels de méthode sous forme d’une seule instruction,
comme dans cet exemple tiré de la documentation EF Core :

C#

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}

Dans ce didacticiel, vous utilisez l’API Fluent uniquement pour le mappage de base de
données que vous ne pouvez pas faire avec des attributs. Toutefois, vous pouvez
également utiliser l’API Fluent pour spécifier la majorité des règles de mise en forme, de
validation et de mappage que vous pouvez spécifier à l’aide d’attributs. Certains
attributs, tels que MinimumLength , ne peuvent pas être appliqués avec l’API Fluent.
Comme mentionné précédemment, MinimumLength ne change pas le schéma, il applique
uniquement une règle de validation côté client et côté serveur.

Certains développeurs préfèrent utiliser exclusivement l’API Fluent pour pouvoir garder
leurs classes d’entité « propres ». Vous pouvez mélanger des attributs et une API Fluent
si vous le souhaitez. Il existe quelques personnalisations qui ne peuvent être effectuées
qu’à l’aide de l’API Fluent. Toutefois, en règle générale, il est recommandé de choisir
l’une de ces deux approches, et de l’utiliser de manière cohérente dans la mesure du
possible. Si vous utilisez ces deux approches, notez que partout où il existe un conflit,
l’API Fluent a priorité sur les attributs.

Pour plus d’informations sur les attributs et l’API Fluent, consultez Méthodes de
configuration.

Diagramme des entités montrant les relations


L’illustration suivante montre le diagramme que les outils Entity Framework Power Tools
créent pour le modèle School complet.
En plus des lignes de relation un-à-plusieurs (1 à *), vous pouvez voir ici la ligne de
relation un-à-zéro-ou-un (1 à 0..1) entre les entités Instructor et OfficeAssignment
ainsi que la ligne de relation zéro-ou-un-à-plusieurs (0..1 à *) entre les entités Instructor
et Department.

Remplir la base de données avec des données


de test
Remplacez le code du fichier Data/DbInitializer.cs par le code suivant pour fournir les
données d’amorçage des nouvelles entités que vous avez créées.

C#

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
//context.Database.EnsureCreated();

// Look for any students.


if (context.Students.Any())
{
return; // DB has been seeded
}

var students = new Student[]


{
new Student { FirstMidName = "Carson", LastName =
"Alexander",
EnrollmentDate = DateTime.Parse("2010-09-01") },
new Student { FirstMidName = "Meredith", LastName =
"Alonso",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Gytis", LastName =
"Barzdukas",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Peggy", LastName =
"Justice",
EnrollmentDate = DateTime.Parse("2011-09-01") },
new Student { FirstMidName = "Laura", LastName =
"Norman",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Nino", LastName =
"Olivetto",
EnrollmentDate = DateTime.Parse("2005-09-01") }
};

foreach (Student s in students)


{
context.Students.Add(s);
}
context.SaveChanges();

var instructors = new Instructor[]


{
new Instructor { FirstMidName = "Kim", LastName =
"Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName =
"Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName =
"Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName =
"Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName =
"Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};

foreach (Instructor i in instructors)


{
context.Instructors.Add(i);
}
context.SaveChanges();

var departments = new Department[]


{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName ==
"Abercrombie").ID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName ==
"Fakhouri").ID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName ==
"Harui").ID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName ==
"Kapoor").ID }
};

foreach (Department d in departments)


{
context.Departments.Add(d);
}
context.SaveChanges();

var courses = new Course[]


{
new Course {CourseID = 1050, Title = "Chemistry",
Credits = 3,
DepartmentID = departments.Single( s => s.Name ==
"Engineering").DepartmentID
},
new Course {CourseID = 4022, Title = "Microeconomics",
Credits = 3,
DepartmentID = departments.Single( s => s.Name ==
"Economics").DepartmentID
},
new Course {CourseID = 4041, Title = "Macroeconomics",
Credits = 3,
DepartmentID = departments.Single( s => s.Name ==
"Economics").DepartmentID
},
new Course {CourseID = 1045, Title = "Calculus",
Credits = 4,
DepartmentID = departments.Single( s => s.Name ==
"Mathematics").DepartmentID
},
new Course {CourseID = 3141, Title = "Trigonometry",
Credits = 4,
DepartmentID = departments.Single( s => s.Name ==
"Mathematics").DepartmentID
},
new Course {CourseID = 2021, Title = "Composition",
Credits = 3,
DepartmentID = departments.Single( s => s.Name ==
"English").DepartmentID
},
new Course {CourseID = 2042, Title = "Literature",
Credits = 4,
DepartmentID = departments.Single( s => s.Name ==
"English").DepartmentID
},
};

foreach (Course c in courses)


{
context.Courses.Add(c);
}
context.SaveChanges();
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName ==
"Fakhouri").ID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName ==
"Harui").ID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName ==
"Kapoor").ID,
Location = "Thompson 304" },
};

foreach (OfficeAssignment o in officeAssignments)


{
context.OfficeAssignments.Add(o);
}
context.SaveChanges();

var courseInstructors = new CourseAssignment[]


{
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry"
).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Kapoor").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry"
).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title ==
"Microeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title ==
"Macroeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Calculus"
).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Fakhouri").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Trigonometry"
).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Composition"
).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Abercrombie").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Literature"
).CourseID,
InstructorID = instructors.Single(i => i.LastName ==
"Abercrombie").ID
},
};

foreach (CourseAssignment ci in courseInstructors)


{
context.CourseAssignments.Add(ci);
}
context.SaveChanges();

var enrollments = new Enrollment[]


{
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry"
).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Alexander").ID,
CourseID = courses.Single(c => c.Title ==
"Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Alexander").ID,
CourseID = courses.Single(c => c.Title ==
"Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Alonso").ID,
CourseID = courses.Single(c => c.Title == "Calculus"
).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Alonso").ID,
CourseID = courses.Single(c => c.Title == "Trigonometry"
).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Alonso").ID,
CourseID = courses.Single(c => c.Title == "Composition"
).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Anand").ID,
CourseID = courses.Single(c => c.Title == "Chemistry"
).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Anand").ID,
CourseID = courses.Single(c => c.Title ==
"Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Barzdukas").ID,
CourseID = courses.Single(c => c.Title ==
"Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").ID,
CourseID = courses.Single(c => c.Title ==
"Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName ==
"Justice").ID,
CourseID = courses.Single(c => c.Title ==
"Literature").CourseID,
Grade = Grade.B
}
};

foreach (Enrollment e in enrollments)


{
var enrollmentInDataBase = context.Enrollments.Where(
s =>
s.Student.ID == e.StudentID &&
s.Course.CourseID ==
e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollments.Add(e);
}
}
context.SaveChanges();
}
}
}

Comme vous l’avez vu dans le premier didacticiel, la majeure partie de ce code crée
simplement de nouveaux objets d’entité et charge des exemples de données dans les
propriétés requises pour les tests. Notez la façon dont les relations plusieurs à plusieurs
sont gérées : le code crée des relations en créant des entités dans les jeux d’entités de
jointure Enrollments et CourseAssignment .

Ajouter une migration


Enregistrez vos modifications et générez le projet. Ensuite, ouvrez la fenêtre de
commande dans le dossier du projet et entrez la commande migrations add (n’exécutez
pas encore la commande de mise à jour de base de données) :

CLI .NET

dotnet ef migrations add ComplexDataModel

Vous obtenez un avertissement concernant une perte possible de données.

text

An operation was scaffolded that may result in the loss of data. Please
review the migration for accuracy.
Done. To undo this action, use 'ef migrations remove'

Si vous tentiez d’exécuter la commande database update à ce stade (ne le faites pas
encore), vous obtiendriez l’erreur suivante :

L’instruction ALTER TABLE est en conflit avec la contrainte FOREIGN KEY «


FK_dbo.Course_dbo.Department_DepartmentID ». Le conflit s’est produit dans la
base de données « ContosoUniversity », table « dbo.Department », colonne «
DepartmentID ».
Parfois, lorsque vous exécutez des migrations avec des données existantes, vous devez
insérer des données stub dans la base de données pour répondre aux contraintes de clé
étrangère. Le code généré dans la méthode Up ajoute une clé étrangère DepartmentID
non Nullable à la table Course . S’il existe déjà des lignes dans la table Course lorsque le
code s’exécute, l’opération AddColumn échoue car SQL Server ne sait pas quelle valeur
placer dans la colonne qui ne peut pas être null. Pour ce didacticiel, vous allez exécuter
la migration sur une nouvelle base de données. Toutefois, dans une application de
production, vous devriez faire en sorte que la migration traite les données existantes, si
bien que les instructions suivantes montrent un exemple de la procédure à suivre pour
ce faire.

Pour faire en sorte que cette migration fonctionne avec les données existantes, vous
devez modifier le code pour attribuer à la nouvelle colonne une valeur par défaut et
créer un département stub nommé « Temp » qui agira en tant que département par
défaut. Par conséquent, les lignes Course existantes seront toutes associées au
département « Temp » après l’exécution de la méthode Up .

Ouvrez le fichier {timestamp}_ComplexDataModel.cs .

Commentez la ligne de code qui ajoute la colonne DepartmentID à la table Course.

C#

migrationBuilder.AlterColumn<string>(
name: "Title",
table: "Course",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldNullable: true);

//migrationBuilder.AddColumn<int>(
// name: "DepartmentID",
// table: "Course",
// nullable: false,
// defaultValue: 0);

Ajoutez le code en surbrillance suivant après le code qui crée la table Department :

C#

migrationBuilder.CreateTable(
name: "Department",
columns: table => new
{
DepartmentID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
Budget = table.Column<decimal>(type: "money", nullable: false),
InstructorID = table.Column<int>(nullable: true),
Name = table.Column<string>(maxLength: 50, nullable: true),
StartDate = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Department", x => x.DepartmentID);
table.ForeignKey(
name: "FK_Department_Instructor_InstructorID",
column: x => x.InstructorID,
principalTable: "Instructor",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
});

migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget,


StartDate) VALUES ('Temp', 0.00, GETDATE())");
// Default value for FK points to department created above, with
// defaultValue changed to 1 in following AddColumn statement.

migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
nullable: false,
defaultValue: 1);

Dans une application de production, vous devez écrire un code ou des scripts pour
ajouter des lignes Department et associer des lignes Course aux nouvelles lignes
Department. Vous n’avez alors plus besoin du service « Temp » ni de la valeur par défaut
de la colonne Course.DepartmentID .

Enregistrez vos modifications et générez le projet.

Changer la chaîne de connexion


Vous avez maintenant un nouveau code dans la classe DbInitializer qui ajoute des
données initiales pour les nouvelles entités à une base de données vide. Pour qu’EF crée
une base de données vide, remplacez le nom de la base de données dans la chaîne de
connexion de appsettings.json par ContosoUniversity3 ou par un autre nom que vous
n’avez pas utilisé sur l’ordinateur dont vous vous servez.

JSON

{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;
MultipleActiveResultSets=true"
},

Enregistrez le changement apporté dans appsettings.json .

7 Notes

Comme alternative au changement de nom de la base de données, vous pouvez


supprimer la base de données. Utilisez SQL Server Object Explorer (SSOX) ou la
commande CLI database drop :

CLI .NET

dotnet ef database drop

Mettre à jour la base de données


Une fois que vous avez modifié le nom de la base de données ou supprimé la base de
données, exécutez la commande database update dans la fenêtre de commande pour
exécuter les migrations.

CLI .NET

dotnet ef database update

Exécutez l’application pour que la méthode DbInitializer.Initialize exécute la


nouvelle base de données et la remplisse.

Ouvrez la base de données dans SSOX comme vous l’avez fait précédemment, puis
développez le nœud Tables pour voir que toutes les tables ont été créées. (Si SSOX est
resté ouvert, cliquez sur le bouton Actualiser.)
Exécutez l’application pour déclencher le code d’initialiseur qui remplit la base de
données.

Cliquez avec le bouton droit sur la table CourseAssignment et sélectionnez Afficher les
données pour vérifier qu’elle comporte des données.

Obtenir le code
Télécharger ou afficher l’application complète.

Étapes suivantes
Dans ce tutoriel, vous allez :

" Personnaliser le modèle de données


" Apporter des modifications à l’entité Student
" Créer une entité Instructor
" Créer une entité OfficeAssignment
" Modifier l’entité Course
" Créer l’entité Department
" Modifier l’entité Enrollment
" Mettre à jour le contexte de base de données
" Remplir la base de données avec des données de test
" Ajouter une migration
" Changer la chaîne de connexion
" Base de données mise à jour

Passez au tutoriel suivant pour en savoir plus sur l’accès aux données associées.

Suivant : Accéder aux données associées

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : Lire les données associées -
ASP.NET MVC avec EF Core
Article • 30/11/2023

Dans le didacticiel précédent, vous a élaboré le modèle de données School. Dans ce


didacticiel, vous allez lire et afficher les données associées, à savoir les données qu’Entity
Framework charge dans les propriétés de navigation.

Les illustrations suivantes montrent les pages que vous allez utiliser.
Dans ce tutoriel, vous allez :

" Découvrir comment charger les données associées


" Créer une page Courses
" Créer une page Instructors
" En savoir plus sur le chargement explicite
Prérequis
Créer un modèle de données complexe

Découvrir comment charger les données


associées
Il existe plusieurs façons de permettre à un logiciel de mappage relationnel objet (ORM)
comme Entity Framework de charger les données associées dans les propriétés de
navigation d’une entité :

Chargement hâtif : Quand l’entité est lue, ses données associées sont également
récupérées. Cela génère en général une requête de jointure unique qui récupère
toutes les données nécessaires. Vous spécifiez un chargement hâtif dans Entity
Framework Core à l’aide des méthodes Include et ThenInclude .

Vous pouvez récupérer une partie des données dans des requêtes distinctes et EF
« corrige » les propriétés de navigation. Autrement dit, EF ajoute automatiquement
les entités récupérées séparément là où elles doivent figurer dans les propriétés de
navigation des entités précédemment récupérées. Pour la requête qui récupère les
données associées, vous pouvez utiliser la méthode Load à la place d’une méthode
renvoyant une liste ou un objet, telle que ToList ou Single .

Chargement explicite : Quand l’entité est lue pour la première fois, les données
associées ne sont pas récupérées. Vous écrivez un code qui récupère les données
associées si elles sont nécessaires. Comme dans le cas du chargement hâtif avec
des requêtes distinctes, le chargement explicite génère plusieurs requêtes
envoyées à la base de données. La différence tient au fait qu’avec le chargement
explicite, le code spécifie les propriétés de navigation à charger. Dans Entity
Framework Core 1.1, vous pouvez utiliser la méthode Load pour effectuer le
chargement explicite. Par exemple :

Chargement différé : Quand l’entité est lue pour la première fois, les données
associées ne sont pas récupérées. Toutefois, la première fois que vous essayez
d’accéder à une propriété de navigation, les données requises pour cette propriété
de navigation sont récupérées automatiquement. Une requête est envoyée à la
base de données chaque fois que vous essayez d’obtenir des données à partir
d’une propriété de navigation pour la première fois. Entity Framework Core 1.0 ne
prend pas en charge le chargement différé.

Considérations relatives aux performances


Si vous savez que vous avez besoin des données associées pour toutes les entités
récupérées, le chargement hâtif souvent offre des performances optimales, car une
seule requête envoyée à la base de données est généralement plus efficace que les
requêtes distinctes pour chaque entité récupérée. Par exemple, supposons que chaque
département a dix cours associés. Le chargement hâtif de toutes les données associées
générerait une seule requête (de jointure) et un seul aller-retour à la base de données.
Une requête distincte pour les cours pour chaque département entraînerait onze allers-
retours à la base de données. Les allers-retours supplémentaires à la base de données
sont particulièrement nuisibles pour les performances lorsque la latence est élevée.

En revanche, dans certains scénarios, les requêtes distinctes s’avèrent plus efficaces. Le
chargement hâtif de toutes les données associées dans une seule requête peut entraîner
une jointure très complexe à générer, que SQL Server ne peut pas traiter efficacement.
Ou, si vous avez besoin d’accéder aux propriétés de navigation d’entité uniquement
pour un sous-ensemble des entités que vous traitez, des requêtes distinctes peuvent
être plus performantes, car le chargement hâtif de tous les éléments en amont
entraînerait la récupération de plus de données qu’il vous faut. Si les performances sont
essentielles, il est préférable de tester les performances des deux façons afin d’effectuer
le meilleur choix.

Créer une page Courses


L’entité Course inclut une propriété de navigation qui contient l’entité Department du
service auquel le cours est affecté. Pour afficher le nom du service affecté dans une liste
de cours, vous devez obtenir la propriété Name de l’entité Department qui figure dans la
propriété de navigation Course.Department .

Créez un contrôleur nommé CoursesController pour le type d’entité Course , en utilisant


les mêmes options pour le générateur de modèles automatique Contrôleur MVC avec
vues, utilisant Entity Framework que vous avez utilisées précédemment pour le
StudentsController , comme indiqué dans l’illustration suivante :

Ouvrez CoursesController.cs et examinez la méthode Index . La génération de modèles


automatique a spécifié un chargement hâtif pour la propriété de navigation Department
à l’aide de la méthode Include .

Remplacez la méthode Index par le code suivant qui utilise un nom plus approprié pour
IQueryable qui renvoie les entités Course ( courses à la place de schoolContext ) :

C#

public async Task<IActionResult> Index()


{
var courses = _context.Courses
.Include(c => c.Department)
.AsNoTracking();
return View(await courses.ToListAsync());
}
Ouvrez Views/Courses/Index.cshtml et remplacez le code du modèle par le code
suivant. Les modifications apparaissent en surbrillance :

CSHTML

@model IEnumerable<ContosoUniversity.Models.Course>

@{
ViewData["Title"] = "Courses";
}

<h2>Courses</h2>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.CourseID)
</th>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Credits)
</th>
<th>
@Html.DisplayNameFor(model => model.Department)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-action="Edit" asp-route-
id="@item.CourseID">Edit</a> |
<a asp-action="Details" asp-route-
id="@item.CourseID">Details</a> |
<a asp-action="Delete" asp-route-
id="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Vous avez apporté les modifications suivantes au code généré automatiquement :

Changement de l’en-tête : Index a été remplacé par Courses.

Ajout d’une colonne Number qui affiche la valeur de la propriété CourseID . Par
défaut, les clés primaires ne sont pas générées automatiquement, car elles ne sont
normalement pas significatives pour les utilisateurs finaux. Toutefois, dans le cas
présent, la clé primaire est significative et vous voulez l’afficher.

Modification de la colonne Department afin d’afficher le nom du département. Le


code affiche la propriété Name de l’entité Department qui est chargée dans la
propriété de navigation Department :

HTML

@Html.DisplayFor(modelItem => item.Department.Name)

Exécutez l’application et sélectionnez l’onglet Courses pour afficher la liste avec les
noms des départements.
Créer une page Instructors
Dans cette section, vous allez créer un contrôleur et une vue pour l’entité Instructor
afin d’afficher la page Instructors :
Cette page lit et affiche les données associées comme suit :

La liste des formateurs affiche les données associées de l’entité OfficeAssignment .


Il existe une relation un-à-zéro-ou-un entre les entités Instructor et
OfficeAssignment . Vous allez utiliser un chargement hâtif pour les entités

OfficeAssignment . Comme expliqué précédemment, le chargement hâtif est

généralement plus efficace lorsque vous avez besoin des données associées pour
toutes les lignes extraites de la table primaire. Dans ce cas, vous souhaitez afficher
les affectations de bureaux pour tous les formateurs affichés.

Quand l’utilisateur sélectionne un formateur, les entités Course associées sont


affichées. Il existe une relation plusieurs-à-plusieurs entre les entités Instructor et
Course . Vous utilisez le chargement hâtif pour les entités Course et leurs entités

Department associées. Dans ce cas, des requêtes distinctes peuvent être plus

efficaces, car vous avez besoin de cours uniquement pour le formateur sélectionné.
Toutefois, cet exemple montre comment utiliser le chargement hâtif pour des
propriétés de navigation dans des entités qui se trouvent elles-mêmes dans des
propriétés de navigation.

Quand l’utilisateur sélectionne un cours, les données associées du jeu d’entités


Enrollments s’affichent. Il existe une relation un-à-plusieurs entre les entités

Course et Enrollment . Vous allez utiliser des requêtes distinctes pour les entités
Enrollment et les entités Student qui leur sont associées.

Créer un modèle de vue pour la vue d’index des


formateurs
La page Instructors affiche des données de trois tables différentes. Par conséquent, vous
allez créer un modèle de vue qui comprend trois propriétés, chacune contenant les
données d’une des tables.

Dans le dossier SchoolViewModels, créez InstructorIndexData.cs et remplacez le code


existant par le code suivant :

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
Créer les vues et le contrôleur de formateurs
Créez un contrôleur de formateurs avec des actions de lecture/écriture EF comme
indiqué dans l’illustration suivante :

Ouvrez InstructorsController.cs et ajoutez une instruction using pour l’espace de


noms ViewModel :

C#

using ContosoUniversity.Models.SchoolViewModels;

Remplacez la méthode Index par le code suivant pour effectuer un chargement hâtif des
données associées et le placer dans le modèle de vue.

C#

public async Task<IActionResult> Index(int? id, int? courseID)


{
var viewModel = new InstructorIndexData();
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

if (id != null)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s =>
s.Course);
}

if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}

return View(viewModel);
}

La méthode accepte des données de route facultatives ( id ) et un paramètre de chaîne


de requête ( courseID ) qui fournissent les valeurs d’ID du formateur sélectionné et du
cours sélectionné. Ces paramètres sont fournis par les liens hypertexte Select dans la
page.

Le code commence par créer une instance du modèle de vue et la placer dans la liste
des formateurs. Le code spécifie un chargement hâtif pour les propriétés de navigation
Instructor.OfficeAssignment et Instructor.CourseAssignments . Dans la propriété
CourseAssignments , la propriété Course est chargée et, dans ce cadre, les propriétés

Enrollments et Department sont chargées, et dans chaque entité Enrollment , la

propriété Student est chargée.

C#

viewModel.Instructors = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Étant donné que la vue nécessite toujours l’entité OfficeAssignment , il est plus efficace
de l’extraire dans la même requête. Les entités Course sont requises lorsqu’un formateur
est sélectionné dans la page web, de sorte qu’une requête individuelle est meilleure que
plusieurs requêtes seulement si la page s’affiche plus souvent avec un cours sélectionné
que sans.

Le code répète CourseAssignments et Course , car vous avez besoin de deux propriétés
de Course . La première chaîne d’appels ThenInclude obtient CourseAssignment.Course ,
Course.Enrollments et Enrollment.Student .

Vous pouvez en apprendre plus sur l’inclusion de plusieurs niveaux de données


connexes ici.

C#

viewModel.Instructors = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();

À ce stade dans le code, un autre ThenInclude serait pour les propriétés de navigation
de Student , dont vous n’avez pas besoin. Toutefois, l’appel de Include recommence
avec les propriétés Instructor , donc vous devez parcourir la chaîne à nouveau, cette
fois en spécifiant Course.Department à la place de Course.Enrollments .

C#

viewModel.Instructors = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Enrollments)
.ThenInclude(i => i.Student)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.AsNoTracking()
.OrderBy(i => i.LastName)
.ToListAsync();
Le code suivant s’exécute quand un formateur a été sélectionné. Le formateur
sélectionné est récupéré à partir de la liste des formateurs dans le modèle d’affichage.
La propriété Courses du modèle d’affichage est ensuite chargée avec les entités Course
de la propriété de navigation CourseAssignments de ce formateur.

C#

if (id != null)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s => s.Course);
}

La méthode Where renvoie une collection, mais dans ce cas, les critères transmis à cette
méthode entraînent le renvoi d’une seule entité Instructor. La méthode Single convertit
la collection en une seule entité Instructor , ce qui vous permet d’accéder à la propriété
CourseAssignments de cette entité. La propriété CourseAssignments contient des entités
CourseAssignment , à partir desquelles vous souhaitez uniquement les entités Course

associées.

Vous utilisez la méthode Single sur une collection lorsque vous savez que la collection
aura un seul élément. La méthode Single lève une exception si la collection transmise
est vide ou s’il y a plusieurs éléments. Une alternative est SingleOrDefault , qui renvoie
une valeur par défaut (Null dans ce cas) si la collection est vide. Toutefois, dans ce cas,
cela entraînerait encore une exception (en tentant de trouver une propriété Courses sur
une référence null) et le message d’exception indiquerait moins clairement la cause du
problème. Lorsque vous appelez la méthode Single , vous pouvez également
transmettre la condition Where au lieu d’appeler séparément la méthode Where :

C#

.Single(i => i.ID == id.Value)

À la place de :

C#

.Where(i => i.ID == id.Value).Single()


Ensuite, si un cours a été sélectionné, le cours sélectionné est récupéré à partir de la liste
des cours dans le modèle de vue. Ensuite, la propriété Enrollments du modèle de vue
est chargée avec les entités Enrollment à partir de la propriété de navigation
Enrollments de ce cours.

C#

if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
viewModel.Enrollments = viewModel.Courses.Where(
x => x.CourseID == courseID).Single().Enrollments;
}

Suivi et non-suivi
Les requêtes sans suivi sont utiles lorsque les résultats sont utilisés dans un scénario en
lecture seule. Leur exécution est généralement plus rapide, car il n’est pas nécessaire de
configurer les informations de suivi des modifications. Si les entités récupérées à partir
de la base de données n’ont pas besoin d’être mises à jour, une requête sans suivi est
susceptible de fonctionner mieux qu’une requête avec suivi.

Dans certains cas, une requête avec suivi est plus efficace qu’une requête sans suivi.
Pour plus d’informations, consultez Requêtes avec suivi et non-suivi.

Modifier la vue d’index des formateurs


Dans Views/Instructors/Index.cshtml , remplacez le code du modèle par le code
suivant. Les modifications sont mises en surbrillance.

CSHTML

@model ContosoUniversity.Models.SchoolViewModels.InstructorIndexData

@{
ViewData["Title"] = "Instructors";
}

<h2>Instructors</h2>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Instructors)
{
string selectedRow = "";
if (item.ID == (int?)ViewData["InstructorID"])
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@foreach (var course in item.CourseAssignments)
{
@course.Course.CourseID @course.Course.Title <br />
}
</td>
<td>
<a asp-action="Index" asp-route-id="@item.ID">Select</a>
|
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-
id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-
id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Vous avez apporté les modifications suivantes au code existant :


Vous avez changé la classe de modèle en InstructorIndexData .

Vous avez changé le titre de la page en remplaçant Index par Instructors.

Il ajoute une colonne Office qui affiche item.OfficeAssignment.Location


uniquement si item.OfficeAssignment n’est pas Null. (Comme il s’agit d’une
relation un-à-zéro-ou-un, il se peut qu’il n’y ait pas d’entité OfficeAssignment
associée.)

CSHTML

@if (item.OfficeAssignment != null)


{
@item.OfficeAssignment.Location
}

Vous avez ajouté une colonne Courses qui affiche les cours animés par chaque
formateur. Pour plus d’informations, consultez la section Conversion de ligne
explicite de l’article relatif à la syntaxe Razor.

Ajout de code qui ajoute conditionnellement une classe CSS Bootstrap à l’élément
tr de l’instructeur sélectionné. Cette classe définit une couleur d’arrière-plan pour
la ligne sélectionnée.

Vous avez ajouté un nouveau lien hypertexte étiqueté Select immédiatement avant
les autres liens dans chaque ligne, ce qui entraîne l’envoi de l’ID du formateur
sélectionné à la méthode Index .

CSHTML

<a asp-action="Index" asp-route-id="@item.ID">Select</a> |

Exécutez l’application et sélectionnez le lien Instructors. La page affiche la propriété


Location des entités OfficeAssignment associées et une cellule de table vide lorsqu’il
n’existe aucune entité OfficeAssignment associée.
Dans le fichier Views/Instructors/Index.cshtml , après l’élément de fermeture de table (à
la fin du fichier), ajoutez le code suivant. Ce code affiche la liste des cours associés à un
formateur quand un formateur est sélectionné.

CSHTML

@if (Model.Courses != null)


{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>

@foreach (var item in Model.Courses)


{
string selectedRow = "";
if (item.CourseID == (int?)ViewData["CourseID"])
{
selectedRow = "success";
}
<tr class="@selectedRow">
<td>
@Html.ActionLink("Select", "Index", new { courseID =
item.CourseID })
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}

</table>
}

Ce code lit la propriété Courses du modèle de vue pour afficher la liste des cours. Il
fournit également un lien hypertexte Select qui envoie l’ID du cours sélectionné à la
méthode d’action Index .

Actualisez la page et sélectionnez un formateur. Vous voyez à présent une grille qui
affiche les cours affectés au formateur sélectionné et, pour chaque cours, vous voyez le
nom du département affecté.
Après le bloc de code que vous venez d’ajouter, ajoutez le code suivant. Ceci affiche la
liste des étudiants qui sont inscrits à un cours quand ce cours est sélectionné.

CSHTML

@if (Model.Enrollments != null)


{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}

Ce code lit la propriété Enrollments du modèle de vue pour afficher la liste des
étudiants inscrits dans ce cours.

Actualisez la page à nouveau et sélectionnez un formateur. Ensuite, sélectionnez un


cours pour afficher la liste des étudiants inscrits et leurs notes.
À propos du chargement explicite
Lorsque vous avez récupéré la liste des formateurs dans InstructorsController.cs , vous
avez spécifié un chargement hâtif pour la propriété de navigation CourseAssignments .

Supposons que vous vous attendiez à ce que les utilisateurs ne souhaitent que rarement
voir les inscriptions pour un formateur et un cours sélectionnés. Dans ce cas, vous
pouvez charger les données d’inscription uniquement si elles sont demandées. Pour voir
un exemple illustrant comment effectuer un chargement explicite, remplacez la
méthode Index par le code suivant, qui supprime le chargement hâtif pour Enrollments
et charge explicitement cette propriété. Les modifications du code apparaissent en
surbrillance.

C#

public async Task<IActionResult> Index(int? id, int? courseID)


{
var viewModel = new InstructorIndexData();
viewModel.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.ThenInclude(i => i.Department)
.OrderBy(i => i.LastName)
.ToListAsync();

if (id != null)
{
ViewData["InstructorID"] = id.Value;
Instructor instructor = viewModel.Instructors.Where(
i => i.ID == id.Value).Single();
viewModel.Courses = instructor.CourseAssignments.Select(s =>
s.Course);
}

if (courseID != null)
{
ViewData["CourseID"] = courseID.Value;
var selectedCourse = viewModel.Courses.Where(x => x.CourseID ==
courseID).Single();
await _context.Entry(selectedCourse).Collection(x =>
x.Enrollments).LoadAsync();
foreach (Enrollment enrollment in selectedCourse.Enrollments)
{
await _context.Entry(enrollment).Reference(x =>
x.Student).LoadAsync();
}
viewModel.Enrollments = selectedCourse.Enrollments;
}

return View(viewModel);
}

Le nouveau code supprime les appels de la méthode ThenInclude pour les données
d’inscription à partir du code qui extrait les entités de formateur. Il dépose également
AsNoTracking . Si un formateur et un cours sont sélectionnés, le code en évidence
récupère les entités Enrollment pour le cours sélectionné et les entités Student pour
chaque entité Enrollment .

Exécutez l’application, accédez à la page d’index des formateurs et vous ne verrez


aucune différence pour ce qui est affiché dans la page, bien que vous ayez modifié la
façon dont les données sont récupérées.

Obtenir le code
Télécharger ou afficher l’application complète.

Étapes suivantes
Dans ce tutoriel, vous allez :

" Chargement des données associées découvert


" Page Courses créée
" Page Instructors créée
" Chargement explicite découvert

Passez au tutoriel suivant pour découvrir comment mettre à jour les données associées.

Mettre à jour les données associées

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : Mettre à jour les données
associées - ASP.NET MVC avec EF Core
Article • 30/11/2023

Dans le didacticiel précédent, vous avez affiché des données associées ; dans ce
didacticiel, vous mettez à jour des données associées en mettant à jour des champs de
clé étrangère et des propriétés de navigation.

Les illustrations suivantes montrent quelques-unes des pages que vous allez utiliser.
Dans ce didacticiel, vous avez effectué les actions suivantes :

" Personnaliser les pages de cours


" Ajouter une page de modification de formateur
" Ajouter des cours à la page de modification
" Mettre à jour la page Delete
" Ajouter des emplacements de bureau et des cours à la page Create

Prérequis
Lire les données associées
Personnaliser les pages de cours
Quand une entité Course est créée, elle doit avoir une relation avec un département
existant. Pour faciliter cela, le code du modèle généré automatiquement inclut des
méthodes de contrôleur, et des vues Create et Edit qui incluent une liste déroulante
pour sélectionner le département. La liste déroulante définit la propriété de clé
étrangère Course.DepartmentID , qui est tout ce dont Entity Framework a besoin pour
charger la propriété de navigation Department avec l’entité Department appropriée. Vous
utilisez le code du modèle généré automatiquement, mais que vous modifiez un peu
pour ajouter la gestion des erreurs et trier la liste déroulante.

Dans CoursesController.cs , supprimez les quatre méthodes Create et Edit, et


remplacez-les par le code suivant :

C#

public IActionResult Create()


{
PopulateDepartmentsDropDownList();
return View();
}

C#

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult>
Create([Bind("CourseID,Credits,DepartmentID,Title")] Course course)
{
if (ModelState.IsValid)
{
_context.Add(course);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}

C#

public async Task<IActionResult> Edit(int? id)


{
if (id == null)
{
return NotFound();
}
var course = await _context.Courses
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}

C#

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}

var courseToUpdate = await _context.Courses


.FirstOrDefaultAsync(c => c.CourseID == id);

if (await TryUpdateModelAsync<Course>(courseToUpdate,
"",
c => c.Credits, c => c.DepartmentID, c => c.Title))
{
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
PopulateDepartmentsDropDownList(courseToUpdate.DepartmentID);
return View(courseToUpdate);
}

Après la méthode HttpPost Edit , créez une méthode qui charge les informations des
départements pour la liste déroulante.

C#
private void PopulateDepartmentsDropDownList(object selectedDepartment =
null)
{
var departmentsQuery = from d in _context.Departments
orderby d.Name
select d;
ViewBag.DepartmentID = new SelectList(departmentsQuery.AsNoTracking(),
"DepartmentID", "Name", selectedDepartment);
}

La méthode PopulateDepartmentsDropDownList obtient une liste de tous les


départements triés par nom, crée une collection SelectList pour une liste déroulante et
passe la collection à la vue dans ViewBag . La méthode accepte le paramètre facultatif
selectedDepartment qui permet au code appelant de spécifier l’élément sélectionné lors

de l’affichage de la liste déroulante. La vue passe le nom « DepartmentID » pour le tag


helper <select> : le helper peut alors rechercher dans l’objet ViewBag une SelectList
nommée « DepartmentID ».

La méthode HttpGet Create appelle la méthode PopulateDepartmentsDropDownList sans


définir l’élément sélectionné, car pour un nouveau cours, le département n’est pas
encore établi :

C#

public IActionResult Create()


{
PopulateDepartmentsDropDownList();
return View();
}

La méthode HttpGet Edit définit l’élément sélectionné, en fonction de l’ID du


département qui est déjà affecté au cours à modifier :

C#

public async Task<IActionResult> Edit(int? id)


{
if (id == null)
{
return NotFound();
}

var course = await _context.Courses


.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}
PopulateDepartmentsDropDownList(course.DepartmentID);
return View(course);
}

Les méthodes HttpPost pour Create et pour Edit incluent également du code qui
définit l’élément sélectionné quand elles réaffichent la page après une erreur. Ceci
garantit que quand la page est réaffichée pour montrer le message d’erreur, le
département qui a été sélectionné le reste.

Ajouter .AsNoTracking aux méthodes Details et Delete


Pour optimiser les performances des pages Details et Delete pour les cours, ajoutez des
appels de AsNoTracking dans les méthodes Details et HttpGet Delete .

C#

public async Task<IActionResult> Details(int? id)


{
if (id == null)
{
return NotFound();
}

var course = await _context.Courses


.Include(c => c.Department)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}

return View(course);
}

C#

public async Task<IActionResult> Delete(int? id)


{
if (id == null)
{
return NotFound();
}

var course = await _context.Courses


.Include(c => c.Department)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.CourseID == id);
if (course == null)
{
return NotFound();
}

return View(course);
}

Modifier les vues des cours


Dans Views/Courses/Create.cshtml , ajoutez une option « Select Department » à la liste
déroulante Department, changez la légende de DepartmentID en Department et
ajoutez un message de validation.

CSHTML

<div class="form-group">
<label asp-for="Department" class="control-label"></label>
<select asp-for="DepartmentID" class="form-control" asp-
items="ViewBag.DepartmentID">
<option value="">-- Select Department --</option>
</select>
<span asp-validation-for="DepartmentID" class="text-danger" />
</div>

Dans Views/Courses/Edit.cshtml , faites les mêmes modifications pour le champ


Department que ce que vous venez de faire dans Create.cshtml .

Également dans Views/Courses/Edit.cshtml , ajoutez un champ de numéro de cours


avant le champ Titre. Comme le numéro de cours est la clé primaire, il est affiché mais
ne peut pas être modifié.

CSHTML

<div class="form-group">
<label asp-for="CourseID" class="control-label"></label>
<div>@Html.DisplayFor(model => model.CourseID)</div>
</div>

Il existe déjà un champ masqué ( <input type="hidden"> ) pour le numéro de cours dans
la vue Edit. L’ajout d’un tag helper <label> n’élimine la nécessité d’avoir le champ
masqué, car cela n’a pas comme effet que le numéro de cours est inclut dans les
données envoyées quand l’utilisateur clique sur Save dans la page Edit.
Dans Views/Courses/Delete.cshtml , ajoutez un champ pour le numéro de cours en haut
et changez l’ID de département en nom de département.

CSHTML

@model ContosoUniversity.Models.Course

@{
ViewData["Title"] = "Delete";
}

<h2>Delete</h2>

<h3>Are you sure you want to delete this?</h3>


<div>
<h4>Course</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.CourseID)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.CourseID)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Credits)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Credits)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Department)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Department.Name)
</dd>
</dl>

<form asp-action="Delete">
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</div>
</form>
</div>
Dans Views/Courses/Details.cshtml , apportez la même modification que vous venez de
faire pour Delete.cshtml .

Tester les pages de cours


Exécutez l’application, sélectionnez l’onglet Courses, cliquez sur Create New et entrez
les données pour un nouveau cours :

Cliquez sur Créer. La page Index des cours est affichée avec le nouveau cours ajouté à la
liste. Le nom du département dans la liste de la page Index provient de la propriété de
navigation, ce qui montre que la relation a été établie correctement.

Cliquez sur Edit pour un cours dans la page Index des cours.
Modifiez les données dans la page et cliquez sur Save. La page Index des cours est
affichée avec les données du cours mises à jour.

Ajouter une page de modification de formateur


Quand vous modifiez un enregistrement de formateur, vous voulez avoir la possibilité
de mettre à jour l’attribution du bureau du formateur. L’entité Instructor a une relation
un-à-zéro ou un-à-un avec l’entité OfficeAssignment , ce qui signifie que votre code doit
gérer les situations suivantes :

Si l’utilisateur efface l’attribution du bureau et qu’il existait une valeur à l’origine,


supprimez l’entité OfficeAssignment .

Si l’utilisateur entre une attribution de bureau et qu’elle était vide à l’origine, créez
une entité OfficeAssignment .
Si l’utilisateur change la valeur d’une attribution de bureau, changez la valeur dans
une entité OfficeAssignment existante.

Mettre à jour le contrôleur Instructors


Dans InstructorsController.cs , modifiez le code de la méthode HttpGet Edit afin
qu’elle charge la propriété de navigation OfficeAssignment de l’entité Instructor et
appelle AsNoTracking :

C#

public async Task<IActionResult> Edit(int? id)


{
if (id == null)
{
return NotFound();
}

var instructor = await _context.Instructors


.Include(i => i.OfficeAssignment)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (instructor == null)
{
return NotFound();
}
return View(instructor);
}

Remplacez la méthode HttpPost Edit par le code suivant pour gérer les mises à jour des
attributions de bureau :

C#

[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}

var instructorToUpdate = await _context.Instructors


.Include(i => i.OfficeAssignment)
.FirstOrDefaultAsync(s => s.ID == id);

if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i =>
i.OfficeAssignment))
{
if
(String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
return View(instructorToUpdate);
}

Le code effectue les actions suivantes :

Il change le nom de la méthode EditPost , car la signature est maintenant la même


que celle de la méthode HttpGet Edit (l’attribut ActionName spécifie que l’URL
/Edit/ est encore utilisée).

Obtient l'entité Instructor en cours à partir de la base de données à l’aide d’un


chargement hâtif de la propriété de navigation OfficeAssignment . C’est identique à
ce que vous avez fait dans la méthode HttpGet Edit .

Met à jour l’entité Instructor récupérée avec les valeurs du classeur de modèles.
La surcharge de TryUpdateModel vous permet de déclarer les propriétés que vous
voulez inclure. Ceci empêche la survalidation, comme expliqué dans le deuxième
didacticiel.

C#

if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i =>
i.OfficeAssignment))
Si l’emplacement du bureau est vide, il définit la propriété
Instructor.OfficeAssignment sur null, de façon que la ligne correspondante dans

la table OfficeAssignment soit supprimée.

C#

if
(String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Locatio
n))
{
instructorToUpdate.OfficeAssignment = null;
}

Il enregistre les modifications dans la base de données.

Mettre à jour la vue de modification des formateurs


Dans Views/Instructors/Edit.cshtml , ajoutez un nouveau champ pour la modification
de l’emplacement du bureau, à la fin et avant le bouton Save :

CSHTML

<div class="form-group">
<label asp-for="OfficeAssignment.Location" class="control-label">
</label>
<input asp-for="OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="OfficeAssignment.Location" class="text-danger"
/>
</div>

Exécutez l’application, sélectionnez l’onglet Instructors, puis cliquez sur Edit pour un
formateur. Modifiez Office Location et cliquez sur Save.
Ajouter des cours à la page de modification
Les instructeurs peuvent enseigner dans n’importe quel nombre de cours. Maintenant,
vous allez améliorer la page de modification des formateurs en ajoutant la possibilité de
modifier les affectations de cours avec un groupe de cases à cocher, comme le montre
la capture d’écran suivante :
La relation entre les entités Course et Instructor est de type plusieurs-à-plusieurs. Pour
ajouter et supprimer des relations, vous ajoutez et supprimez des entités à partir du jeu
d’entités de jointures CourseAssignments .

L’interface utilisateur qui vous permet de changer les cours auxquels un formateur est
affecté est un groupe de cases à cocher. Une case à cocher est affichée pour chaque
cours de la base de données, et ceux auxquels le formateur est actuellement affecté
sont sélectionnés. L’utilisateur peut cocher ou décocher les cases pour changer les
affectations de cours. Si le nombre de cours était beaucoup plus important, vous
pourriez utiliser une autre méthode de présentation des données dans la vue, mais vous
utiliseriez la même méthode de manipulation d’une entité de jointure pour créer ou
supprimer des relations.
Mettre à jour le contrôleur Instructors
Pour fournir des données à la vue pour la liste de cases à cocher, vous utilisez une classe
de modèle de vue.

Créez AssignedCourseData.cs dans le dossier SchoolViewModels et remplacez le code


existant par le code suivant :

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}

Dans InstructorsController.cs , remplacez la méthode Edit HttpGet par le code


suivant. Les modifications sont mises en surbrillance.

C#

public async Task<IActionResult> Edit(int? id)


{
if (id == null)
{
return NotFound();
}

var instructor = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments).ThenInclude(i => i.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (instructor == null)
{
return NotFound();
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}
private void PopulateAssignedCourseData(Instructor instructor)
{
var allCourses = _context.Courses;
var instructorCourses = new HashSet<int>
(instructor.CourseAssignments.Select(c => c.CourseID));
var viewModel = new List<AssignedCourseData>();
foreach (var course in allCourses)
{
viewModel.Add(new AssignedCourseData
{
CourseID = course.CourseID,
Title = course.Title,
Assigned = instructorCourses.Contains(course.CourseID)
});
}
ViewData["Courses"] = viewModel;
}

Le code ajoute un chargement hâtif pour la propriété de navigation Courses et appelle


la nouvelle méthode PopulateAssignedCourseData pour fournir des informations pour le
tableau de cases à cocher avec la classe de modèle de vue AssignedCourseData .

Le code de la méthode PopulateAssignedCourseData lit toutes les entités Course pour


charger une liste de cours avec la classe de modèle de vue. Pour chaque cours, le code
vérifie s’il existe dans la propriété de navigation Courses du formateur. Pour créer une
recherche efficace quand il est vérifié si un cours est affecté au formateur, les cours
affectés au formateur sont placés dans une collection HashSet . La propriété Assigned
est définie sur true pour les cours auxquels le formateur est affecté. La vue utilise cette
propriété pour déterminer quelles cases doivent être affichées cochées. Enfin, la liste est
passée à la vue dans ViewData .

Ensuite, ajoutez le code qui est exécuté quand l’utilisateur clique sur Save. Remplacez la
méthode EditPost par le code suivant et ajoutez une nouvelle méthode qui met à jour
la propriété de navigation Courses de l’entité Instructor.

C#

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, string[] selectedCourses)
{
if (id == null)
{
return NotFound();
}

var instructorToUpdate = await _context.Instructors


.Include(i => i.OfficeAssignment)
.Include(i => i.CourseAssignments)
.ThenInclude(i => i.Course)
.FirstOrDefaultAsync(m => m.ID == id);

if (await TryUpdateModelAsync<Instructor>(
instructorToUpdate,
"",
i => i.FirstMidName, i => i.LastName, i => i.HireDate, i =>
i.OfficeAssignment))
{
if
(String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Location))
{
instructorToUpdate.OfficeAssignment = null;
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction(nameof(Index));
}
UpdateInstructorCourses(selectedCourses, instructorToUpdate);
PopulateAssignedCourseData(instructorToUpdate);
return View(instructorToUpdate);
}

C#
private void UpdateInstructorCourses(string[] selectedCourses, Instructor
instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}

var selectedCoursesHS = new HashSet<string>(selectedCourses);


var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c =>
c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new
CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID =
course.CourseID });
}
}
else
{

if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove =
instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID ==
course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}

La signature de la méthode diffère maintenant de celle de la méthode HttpGet Edit : le


nom de la méthode change donc de EditPost en Edit .

Comme la vue n’a pas de collection d’entités Course, le classeur de modèles ne peut pas
mettre à jour automatiquement la propriété de navigation CourseAssignments . Au lieu
d’utiliser le classeur de modèles pour mettre à jour la propriété de navigation
CourseAssignments , vous faites cela dans la nouvelle méthode UpdateInstructorCourses .

Par conséquent, vous devez exclure la propriété CourseAssignments de la liaison de


modèle. Ceci ne nécessite aucune modification du code qui appelle TryUpdateModel , car
vous utilisez la surcharge nécessitant une approbation explicite, et CourseAssignments
n’est pas dans la liste des éléments à inclure.
Si aucune case n’a été cochée, le code de UpdateInstructorCourses initialise la propriété
de navigation CourseAssignments avec une collection vide et retourne :

C#

private void UpdateInstructorCourses(string[] selectedCourses, Instructor


instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}

var selectedCoursesHS = new HashSet<string>(selectedCourses);


var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c =>
c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new
CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID =
course.CourseID });
}
}
else
{

if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove =
instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID ==
course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}

Le code boucle ensuite à travers tous les cours dans la base de données, et vérifie
chaque cours par rapport à ceux actuellement affectés au formateur relativement à ceux
qui ont été sélectionnés dans la vue. Pour faciliter des recherches efficaces, les deux
dernières collections sont stockées dans des objets HashSet .

Si la case pour un cours a été cochée mais que le cours n’est pas dans la propriété de
navigation Instructor.CourseAssignments , le cours est ajouté à la collection dans la
propriété de navigation.

C#

private void UpdateInstructorCourses(string[] selectedCourses, Instructor


instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}

var selectedCoursesHS = new HashSet<string>(selectedCourses);


var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c =>
c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new
CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID =
course.CourseID });
}
}
else
{

if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove =
instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID ==
course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}

Si la case pour un cours a été cochée mais que le cours est dans la propriété de
navigation Instructor.CourseAssignments , le cours est supprimé de la propriété de
navigation.

C#

private void UpdateInstructorCourses(string[] selectedCourses, Instructor


instructorToUpdate)
{
if (selectedCourses == null)
{
instructorToUpdate.CourseAssignments = new List<CourseAssignment>();
return;
}

var selectedCoursesHS = new HashSet<string>(selectedCourses);


var instructorCourses = new HashSet<int>
(instructorToUpdate.CourseAssignments.Select(c =>
c.Course.CourseID));
foreach (var course in _context.Courses)
{
if (selectedCoursesHS.Contains(course.CourseID.ToString()))
{
if (!instructorCourses.Contains(course.CourseID))
{
instructorToUpdate.CourseAssignments.Add(new
CourseAssignment { InstructorID = instructorToUpdate.ID, CourseID =
course.CourseID });
}
}
else
{

if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove =
instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID ==
course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}

Mettre à jour les vues des formateurs


Dans Views/Instructors/Edit.cshtml , ajoutez un champ Courses avec un tableau de
cases à cocher, en ajoutant le code suivant immédiatement après le code les éléments
div pour le champ Office et avant l’élément div pour le bouton Save.

7 Notes

Quand vous collez le code dans Visual Studio, les sauts de ligne peuvent être
changés d’une façon qui casse le code. Si le code semble différent après un collage,
appuyez une fois sur Ctrl+Z pour annuler la mise en forme automatique. Ceci
permet de corriger les sauts de ligne de façon à ce qu’ils apparaissent comme ce
que vous voyez ici. L’indentation ne doit pas nécessairement être parfaite, mais les
lignes @:</tr><tr> , @:<td> , @:</td> et @:</tr> doivent chacune tenir sur une
seule ligne comme dans l’illustration, sinon vous recevrez une erreur d’exécution.
Avec le bloc de nouveau code sélectionné, appuyez trois fois sur Tab pour aligner le
nouveau code avec le code existant. Ce problème est résolu dans Visual
Studio 2019.

CSHTML

<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;

List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses =
ViewBag.Courses;

foreach (var course in courses)


{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ?
"checked=\"checked\"" : "")) />
@course.CourseID @: @course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>

Ce code crée un tableau HTML qui a trois colonnes. Dans chaque colonne se trouve une
case à cocher, suivie d’une légende qui est constituée du numéro et du titre du cours.
Toutes les cases à cocher ont le même nom (« selectedCourses »), qui indique au
classeur de modèles qu’ils doivent être traités comme un groupe. L’attribut de valeur de
chaque case à cocher est défini sur la valeur de CourseID . Quand la page est envoyée, le
classeur de modèles passe un tableau au contrôleur, constitué des valeurs de CourseID
seulement pour les cases qui sont cochées.

Quand les cases à cocher sont affichées à l’origine, celles qui correspondent à des cours
affectés au formateur ont des attributs cochés, qui les sélectionnent (ils les affichent
cochées).

Exécutez l’application, sélectionnez l’onglet Instructors, puis cliquez sur Edit pour un
formateur pour voir la page Edit.

Changez quelques affectations de cours et cliquez sur Save. Les modifications que vous
apportez sont reflétées dans la page Index.

7 Notes

L’approche adoptée ici pour modifier les données des cours des formateurs
fonctionne bien le nombre de cours est limité. Pour les collections qui sont
beaucoup plus volumineuses, une autre interface utilisateur et une autre méthode
de mise à jour seraient nécessaires.

Mettre à jour la page Delete


Dans InstructorsController.cs , supprimez la méthode DeleteConfirmed et insérez à la
place le code suivant.

C#

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
Instructor instructor = await _context.Instructors
.Include(i => i.CourseAssignments)
.SingleAsync(i => i.ID == id);

var departments = await _context.Departments


.Where(d => d.InstructorID == id)
.ToListAsync();
departments.ForEach(d => d.InstructorID = null);

_context.Instructors.Remove(instructor);

await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}

Ce code apporte les modifications suivantes :

Il effectue un chargement hâtif pour la propriété de navigation CourseAssignments .


Vous devez inclure ceci car sinon, EF ne dispose pas d’informations sur les entités
CourseAssignment associées et ne les supprime pas. Pour éviter de devoir les lire ici,

vous pouvez configurer une suppression en cascade dans la base de données.

Si le formateur à supprimer est attribué en tant qu’administrateur d’un


département, supprime l’attribution de l'instructeur de ces départements.

Ajouter des emplacements de bureau et des


cours à la page Create
Dans InstructorsController.cs , supprimez les méthodes HttpGet et HttpPost Create ,
puis ajoutez le code suivant à leur place :
C#

public IActionResult Create()


{
var instructor = new Instructor();
instructor.CourseAssignments = new List<CourseAssignment>();
PopulateAssignedCourseData(instructor);
return View();
}

// POST: Instructors/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult>
Create([Bind("FirstMidName,HireDate,LastName,OfficeAssignment")] Instructor
instructor, string[] selectedCourses)
{
if (selectedCourses != null)
{
instructor.CourseAssignments = new List<CourseAssignment>();
foreach (var course in selectedCourses)
{
var courseToAdd = new CourseAssignment { InstructorID =
instructor.ID, CourseID = int.Parse(course) };
instructor.CourseAssignments.Add(courseToAdd);
}
}
if (ModelState.IsValid)
{
_context.Add(instructor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
PopulateAssignedCourseData(instructor);
return View(instructor);
}

Ce code est similaire à ce que vous avez vu pour les méthodes Edit , excepté
qu’initialement, aucun cours n’est sélectionné. La méthode HttpGet Create appelle la
méthode PopulateAssignedCourseData , non pas en raison du fait qu’il peut exister des
cours sélectionnés, mais pour pouvoir fournir une collection vide pour la boucle foreach
dans la vue (sinon, le code de la vue lèverait une exception de référence null).

La méthode HttpPost Create ajoute chaque cours sélectionné à la propriété de


navigation CourseAssignments avant de vérifier s’il y a des erreurs de validation et ajoute
le nouveau formateur à la base de données. Les cours sont ajoutés même s’il existe des
erreurs de modèle : ainsi, quand c’est le cas (par exemple si l’utilisateur a tapé une date
non valide) et que la page est réaffichée avec un message d’erreur, les sélections de
cours qui ont été faites sont automatiquement restaurées.
Notez que pour pouvoir ajouter des cours à la propriété de navigation
CourseAssignments , vous devez initialiser la propriété en tant que collection vide :

C#

instructor.CourseAssignments = new List<CourseAssignment>();

Comme alternative à cette opération dans le code du contrôleur, vous pouvez l’effectuer
dans le modèle Instructor en modifiant le getter de propriété pour créer
automatiquement la collection si elle n’existe pas, comme le montre l’exemple suivant :

C#

private ICollection<CourseAssignment> _courseAssignments;


public ICollection<CourseAssignment> CourseAssignments
{
get
{
return _courseAssignments ?? (_courseAssignments = new
List<CourseAssignment>());
}
set
{
_courseAssignments = value;
}
}

Si vous modifiez la propriété CourseAssignments de cette façon, vous pouvez supprimer


le code d’initialisation explicite de la propriété dans le contrôleur.

Dans Views/Instructor/Create.cshtml , ajoutez une zone de texte pour l’emplacement


du bureau et des cases à cocher pour les cours avant le bouton Envoyer. Comme dans le
cas de la page Edit, corrigez la mise en forme si Visual Studio remet en forme le code
quand vous le collez.

CSHTML

<div class="form-group">
<label asp-for="OfficeAssignment.Location" class="control-label">
</label>
<input asp-for="OfficeAssignment.Location" class="form-control" />
<span asp-validation-for="OfficeAssignment.Location" class="text-danger"
/>
</div>

<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<table>
<tr>
@{
int cnt = 0;

List<ContosoUniversity.Models.SchoolViewModels.AssignedCourseData> courses =
ViewBag.Courses;

foreach (var course in courses)


{
if (cnt++ % 3 == 0)
{
@:</tr><tr>
}
@:<td>
<input type="checkbox"
name="selectedCourses"
value="@course.CourseID"
@(Html.Raw(course.Assigned ?
"checked=\"checked\"" : "")) />
@course.CourseID @: @course.Title
@:</td>
}
@:</tr>
}
</table>
</div>
</div>

Testez en exécutant l’application et en créant un formateur.

Gestion des transactions


Comme expliqué dans le didacticiel CRUD, Entity Framework implémente implicitement
les transactions. Pour les scénarios où vous avez besoin de plus de contrôle, par
exemple si vous voulez inclure des opérations effectuées en dehors d’Entity Framework
dans une transaction, consultez Transactions.

Obtenir le code
Télécharger ou afficher l’application complète.

Étapes suivantes
Dans ce tutoriel, vous allez :

" Personnalisez les pages de cours


" Ajoutez une page de modification de formateur
" Ajoutez des cours à la page de modification
" Mettez à jour la page Delete
" Emplacements de bureau et cours ajoutés à la page Create

Passez au tutoriel suivant pour découvrir comment gérer les conflits d’accès
concurrentiel.

Gérer les conflits d’accès concurrentiel

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : Gérer l’accès concurrentiel -
ASP.NET MVC avec EF Core
Article • 30/11/2023

Dans les didacticiels précédents, vous avez découvert comment mettre à jour des
données. Ce didacticiel montre comment gérer les conflits quand plusieurs utilisateurs
mettent à jour la même entité en même temps.

Vous allez créer des pages web qui utilisent l’entité Department et gérer les erreurs
d’accès concurrentiel. Les illustrations suivantes montrent les pages Edit et Delete,
notamment certains messages qui sont affichés si un conflit d’accès concurrentiel se
produit.
Dans ce tutoriel, vous allez :

" En savoir plus sur les conflits d’accès concurrentiel


" Ajouter une propriété de suivi
" Créer un contrôleur Departments et des vues
" Mettre à jour la vue Index
" Mettre à jour les méthodes de modification
" Mettre à jour la vue Edit
" Tester les conflits d'accès concurrentiel
" Mettre à jour la page Delete
" Mettre à jour les vues Details et Create

Prérequis
Mettre à jour les données associées

Conflits d’accès concurrentiel


Un conflit d’accès concurrentiel se produit quand un utilisateur affiche les données
d’une entité pour la modifier, puis qu’un autre utilisateur met à jour les données de la
même entité avant que les modifications du premier utilisateur soient écrites dans la
base de données. Si vous n’activez pas la détection de ces conflits, la personne qui met
à jour la base de données en dernier remplace les modifications de l’autre utilisateur.
Dans de nombreuses applications, ce risque est acceptable : s’il n’y a que quelques
utilisateurs ou quelques mises à jour, ou s’il n’est pas réellement critique que des
modifications soient remplacées, le coût de la programmation nécessaire à la gestion
des accès concurrentiels peut être supérieur au bénéfice qu’elle apporte. Dans ce cas,
vous ne devez pas configurer l’application pour gérer les conflits d’accès concurrentiel.

Accès concurrentiel pessimiste (verrouillage)


Si votre application doit éviter la perte accidentelle de données dans des scénarios
d’accès concurrentiel, une manière de le faire consiste à utiliser des verrous de base de
données. Ceci est appelé « accès concurrentiel pessimiste ». Par exemple, avant de lire
une ligne d’une base de données, vous demandez un verrou pour lecture seule ou pour
accès avec mise à jour. Si vous verrouillez une ligne pour accès avec mise à jour, aucun
autre utilisateur n’est autorisé à verrouiller la ligne pour lecture seule ou pour accès avec
mise à jour, car ils obtiendraient ainsi une copie de données qui sont en cours de
modification. Si vous verrouillez une ligne pour accès en lecture seule, d’autres
utilisateurs peuvent également la verrouiller pour accès en lecture seule, mais pas pour
accès avec mise à jour.

La gestion des verrous présente des inconvénients. Elle peut être complexe à
programmer. Elle nécessite des ressources de gestion de base de données importantes,
et peut provoquer des problèmes de performances au fil de l’augmentation du nombre
d’utilisateurs d’une application. Pour ces raisons, certains systèmes de gestion de base
de données ne prennent pas en charge l’accès concurrentiel pessimiste. Entity
Framework Core n’en fournit pas de prise en charge intégrée et ce didacticiel ne vous
montre comment l’implémenter.

Accès concurrentiel optimiste


La solution alternative à l’accès concurrentiel pessimiste est l’accès concurrentiel
optimiste. L’accès concurrentiel optimiste signifie autoriser la survenance des conflits
d’accès concurrentiel, puis de réagir correctement quand ils surviennent. Par exemple,
Jane consulte la page Department Edit et change le montant de « Budget » pour le
département « English » en le passant de $350 000,00 à $0,00.

Avant que Jane clique sur Save, John consulte la même page et change le champ Start
Date de 01/09/2007 en 01/09/2013.
Jane clique la première sur Save et voit sa modification quand le navigateur revient à la
page Index.
John clique à son tour sur Save sur une page Edit qui affiche toujours un budget de
$350 000,00. Ce qui se passe ensuite est déterminé par la façon dont vous gérez les
conflits d’accès concurrentiel.

Voici quelques-unes des options :

Vous pouvez effectuer le suivi des propriétés modifiées par un utilisateur et mettre
à jour seulement les colonnes correspondantes dans la base de données.

Dans l’exemple de scénario, aucune donnée ne serait perdue, car des propriétés
différentes ont été mises à jour par chacun des deux utilisateurs. La prochaine fois
que quelqu’un examine le département « English », il voit à la fois les modifications
de Jane et de John : une date de début au 01/09/2013 et un budget de zéro
dollars. Cette méthode de mise à jour peut réduire le nombre de conflits qui
peuvent entraîner des pertes de données, mais elle ne peut pas éviter la perte de
données si des modifications concurrentes sont apportées à la même propriété
d’une entité. Un tel fonctionnement d’Entity Framework dépend de la façon dont
vous implémentez votre code de mise à jour. Il n’est pas souvent pratique dans
une application web, car il peut nécessiter la gestion de grandes quantités d’états
pour effectuer le suivi de toutes les valeurs de propriété d’origine d’une entité,
ainsi que des nouvelles valeurs. La gestion de grandes quantités d’états peut
affecter les performances de l’application, car elle nécessite des ressources serveur,
ou doit être incluse dans la page web elle-même (par exemple dans des champs
masqués) ou dans un cookie.

Vous pouvez laisser les modifications de John remplacer les modifications de Jane.

La prochaine fois que quelqu’un consultera le département « English », il verra la


date du 01/09/2013 et la valeur $350 000,00 restaurée. Ceci s’appelle un scénario
Priorité au client ou Priorité au dernier entré (Last in Wins). (Toutes les valeurs
provenant du client sont prioritaires par rapport à ce qui se trouve dans le magasin
de données.) Comme indiqué dans l’introduction de cette section, si vous ne codez
rien pour la gestion des accès concurrentiels, ceci se produit automatiquement.

Vous pouvez empêcher les modifications de John de faire l’objet d’une mise à jour
dans la base de données.

En règle générale, vous affichez un message d’erreur, vous lui montrez l’état actuel
des données et vous lui permettez de réappliquer ses modifications s’il veut
toujours les faire. Il s’agit alors d’un scénario Priorité au magasin. (Les valeurs du
magasin de données sont prioritaires par rapport à celles soumises par le client.)
Dans ce tutoriel, vous allez implémenter le scénario Priorité au magasin. Cette
méthode garantit qu’aucune modification n’est remplacée sans qu’un utilisateur
soit averti de ce qui se passe.

Détection des conflits d’accès concurrentiel


Vous pouvez résoudre les conflits en gérant les exceptions DbConcurrencyException
levées par Entity Framework. Pour savoir quand lever ces exceptions, Entity Framework
doit être en mesure de détecter les conflits. Par conséquent, vous devez configurer de
façon appropriée la base de données et le modèle de données. Voici quelques options
pour l’activation de la détection des conflits :

Dans la table de base de données, incluez une colonne de suivi qui peut être
utilisée pour déterminer quand une ligne a été modifiée. Vous pouvez ensuite
configurer Entity Framework pour inclure cette colonne dans la clause WHERE des
commandes SQL UPDATE et DELETE.

Le type de données de la colonne de suivi est généralement rowversion . La valeur


de rowversion est un nombre séquentiel qui est incrémenté chaque fois que la
ligne est mise à jour. Dans une commande UPDATE ou DELETE, la clause WHERE
inclut la valeur d’origine de la colonne de suivi (la version d’origine de la ligne). Si
la ligne à mettre à jour a été changée par un autre utilisateur, la valeur de la
colonne rowversion est différente de la valeur d’origine : l’instruction UPDATE ou
DELETE ne peut donc pas trouver la ligne à mettre à jour en raison de la clause
WHERE. Quand Entity Framework trouve qu’aucune ligne n’a été mise à jour par la
commande UPDATE ou DELETE (c’est-à-dire quand le nombre de lignes affectées
est égal à zéro), il interprète ceci comme étant un conflit d’accès concurrentiel.

Configurez Entity Framework de façon à inclure les valeurs d’origine de chaque


colonne dans la table de la clause WHERE des commandes UPDATE et DELETE.

Comme dans la première option, si quelque chose dans la ligne a changé depuis la
première lecture de la ligne, la clause WHERE ne retourne pas de ligne à mettre à
jour, ce qui est interprété par Entity Framework comme un conflit d’accès
concurrentiel. Pour les tables de base de données qui ont beaucoup de colonnes,
cette approche peut aboutir à des clauses WHERE de très grande taille et
nécessiter la gestion de grandes quantités d’états. Comme indiqué précédemment,
la gestion de grandes quantités d’états peut affecter les performances de
l’application. Par conséquent, cette approche n’est généralement pas
recommandée et n’est pas la méthode utilisée dans ce didacticiel.

Si vous ne voulez pas implémenter cette approche de l’accès concurrentiel, vous


devez marquer toutes les propriétés qui ne sont pas des clés primaires de l’entité
dont vous voulez suivre les accès concurrentiels en leur ajoutant l’attribut
ConcurrencyCheck . Cette modification permet à Entity Framework d’inclure toutes

les colonnes dans la clause SQL WHERE des instructions UPDATE et DELETE.

Dans le reste de ce didacticiel, vous ajoutez une propriété de suivi rowversion à l’entité
Department, vous créez un contrôleur et des vues, et vous testez pour vérifier que tout
fonctionne correctement.

Ajouter une propriété de suivi


Dans Models/Department.cs , ajoutez une propriété de suivi nommée RowVersion :

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }

[StringLength(50, MinimumLength = 3)]


public string Name { get; set; }

[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }

public int? InstructorID { get; set; }

[Timestamp]
public byte[] RowVersion { get; set; }

public Instructor Administrator { get; set; }


public ICollection<Course> Courses { get; set; }
}
}
L’attribut Timestamp spécifie que cette colonne sera incluse dans la clause WHERE des
commandes UPDATE et DELETE envoyées à la base de données. L’attribut est nommé
Timestamp , car les versions précédentes de SQL Server utilisaient un type de données

SQL timestamp avant son remplacement par le type SQL rowversion . Le type .NET pour
rowversion est un tableau d’octets.

Si vous préférez utiliser l’API actuelle, vous pouvez utiliser la méthode


IsConcurrencyToken (dans Data/SchoolContext.cs ) pour spécifier la propriété de suivi,

comme indiqué dans l’exemple suivant :

C#

modelBuilder.Entity<Department>()
.Property(p => p.RowVersion).IsConcurrencyToken();

En ajoutant une propriété, vous avez changé le modèle de base de données et vous
devez donc effectuer une autre migration.

Enregistrez vos modifications et générez le projet, puis entrez les commandes suivantes
dans la fenêtre Commande :

CLI .NET

dotnet ef migrations add RowVersion

CLI .NET

dotnet ef database update

Créer un contrôleur Departments et des vues


Générez automatiquement un modèle de contrôleur Departments et des vues, comme
vous l’avez fait précédemment pour les étudiants, les cours et les enseignants.
Dans le fichier DepartmentsController.cs , changez les quatre occurrences de
« FirstMidName » en « FullName », de façon que les listes déroulantes de
l’administrateur du département contiennent le nom complet de l’enseignant et non pas
simplement son nom de famille.

C#

ViewData["InstructorID"] = new SelectList(_context.Instructors, "ID",


"FullName", department.InstructorID);

Mettre à jour la vue Index


Le moteur de génération de modèles automatique a créé une colonne RowVersion pour
la vue Index, mais ce champ ne doit pas être affiché.

Remplacez le code dans Views/Departments/Index.cshtml par le code suivant.

CSHTML

@model IEnumerable<ContosoUniversity.Models.Department>

@{
ViewData["Title"] = "Departments";
}

<h2>Departments</h2>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Budget)
</th>
<th>
@Html.DisplayNameFor(model => model.StartDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Administrator)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Budget)
</td>
<td>
@Html.DisplayFor(modelItem => item.StartDate)
</td>
<td>
@Html.DisplayFor(modelItem =>
item.Administrator.FullName)
</td>
<td>
<a asp-action="Edit" asp-route-
id="@item.DepartmentID">Edit</a> |
<a asp-action="Details" asp-route-
id="@item.DepartmentID">Details</a> |
<a asp-action="Delete" asp-route-
id="@item.DepartmentID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Ceci change l’en-tête en « Departments », supprime la colonne RowVersion et montre à


l’administrateur le nom complet au lieu du prénom.
Mettre à jour les méthodes de modification
Dans la méthode HttpGet Edit et la méthode Details , ajoutez AsNoTracking . Dans la
méthode HttpGet Edit , ajoutez un chargement hâtif pour l’administrateur.

C#

var department = await _context.Departments


.Include(i => i.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.DepartmentID == id);

Remplacez le code existant pour la méthode HttpPost Edit méthode par le code suivant
:

C#

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, byte[] rowVersion)
{
if (id == null)
{
return NotFound();
}

var departmentToUpdate = await _context.Departments.Include(i =>


i.Administrator).FirstOrDefaultAsync(m => m.DepartmentID == id);

if (departmentToUpdate == null)
{
Department deletedDepartment = new Department();
await TryUpdateModelAsync(deletedDepartment);
ModelState.AddModelError(string.Empty,
"Unable to save changes. The department was deleted by another
user.");
ViewData["InstructorID"] = new SelectList(_context.Instructors,
"ID", "FullName", deletedDepartment.InstructorID);
return View(deletedDepartment);
}

_context.Entry(departmentToUpdate).Property("RowVersion").OriginalValue
= rowVersion;

if (await TryUpdateModelAsync<Department>(
departmentToUpdate,
"",
s => s.Name, s => s.StartDate, s => s.Budget, s => s.InstructorID))
{
try
{
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Department)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty,
"Unable to save changes. The department was deleted by
another user.");
}
else
{
var databaseValues = (Department)databaseEntry.ToObject();

if (databaseValues.Name != clientValues.Name)
{
ModelState.AddModelError("Name", $"Current value:
{databaseValues.Name}");
}
if (databaseValues.Budget != clientValues.Budget)
{
ModelState.AddModelError("Budget", $"Current value:
{databaseValues.Budget:c}");
}
if (databaseValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("StartDate", $"Current value:
{databaseValues.StartDate:d}");
}
if (databaseValues.InstructorID !=
clientValues.InstructorID)
{
Instructor databaseInstructor = await
_context.Instructors.FirstOrDefaultAsync(i => i.ID ==
databaseValues.InstructorID);
ModelState.AddModelError("InstructorID", $"Current
value: {databaseInstructor?.FullName}");
}

ModelState.AddModelError(string.Empty, "The record you


attempted to edit "
+ "was modified by another user after you got the
original value. The "
+ "edit operation was canceled and the current
values in the database "
+ "have been displayed. If you still want to edit
this record, click "
+ "the Save button again. Otherwise click the Back
to List hyperlink.");
departmentToUpdate.RowVersion =
(byte[])databaseValues.RowVersion;
ModelState.Remove("RowVersion");
}
}
}
ViewData["InstructorID"] = new SelectList(_context.Instructors, "ID",
"FullName", departmentToUpdate.InstructorID);
return View(departmentToUpdate);
}

Le code commence par essayer de lire le département à mettre à jour. Si la méthode


FirstOrDefaultAsync retourne null, c’est que le département a été supprimé par un

autre utilisateur. Dans ce cas, le code utilise les valeurs du formulaire envoyé pour créer
une entité Department de façon que la page Edit puisse être réaffichée avec un message
d’erreur. Vous pouvez aussi ne pas recréer l’entité Department si vous affichez seulement
un message d’erreur sans réafficher les champs du département.

La vue stocke la valeur d’origine de RowVersion dans un champ masqué, et cette


méthode reçoit cette valeur dans le paramètre rowVersion . Avant d’appeler
SaveChanges , vous devez placer la valeur d’origine de la propriété RowVersion dans la

collection OriginalValues pour l’entité.

C#

_context.Entry(departmentToUpdate).Property("RowVersion").OriginalValue =
rowVersion;

Ensuite, quand Entity Framework crée une commande SQL UPDATE, cette commande
inclut une clause WHERE qui recherche une ligne contenant la valeur d’origine de
RowVersion . Si aucune ligne n’est affectée par la commande UPDATE (aucune ligne ne

contient la valeur RowVersion d’origine), Entity Framework lève une exception


DbUpdateConcurrencyException .

Le code du bloc catch pour cette exception obtient l’entité Department affectée qui a les
valeurs mises à jour de la propriété Entries sur l’objet d’exception.

C#

var exceptionEntry = ex.Entries.Single();

La collection Entries n’a qu’un objet EntityEntry . Vous pouvez utiliser cet objet pour
obtenir les nouvelles valeurs entrées par l’utilisateur et les valeurs actuelles de la base de
données.
C#

var clientValues = (Department)exceptionEntry.Entity;


var databaseEntry = exceptionEntry.GetDatabaseValues();

Le code ajoute un message d’erreur personnalisé pour chaque colonne dont les valeurs
dans la base de données diffèrent de ce que l’utilisateur a entré dans la page Edit (un
seul champ est montré ici par souci de concision).

C#

var databaseValues = (Department)databaseEntry.ToObject();

if (databaseValues.Name != clientValues.Name)
{
ModelState.AddModelError("Name", $"Current value:
{databaseValues.Name}");

Enfin, le code affecte la nouvelle valeur récupérée auprès de la base de données à


RowVersion pour departmentToUpdate . Cette nouvelle valeur de RowVersion est stockée

dans le champ masqué quand la page Edit est réaffichée et, la prochaine fois que
l’utilisateur clique sur Save, seules les erreurs d’accès concurrentiel qui se produisent
depuis le réaffichage de la page Edit sont interceptées.

C#

departmentToUpdate.RowVersion = (byte[])databaseValues.RowVersion;
ModelState.Remove("RowVersion");

L’instruction ModelState.Remove est nécessaire car ModelState contient l’ancienne valeur


RowVersion . Dans la vue, la valeur ModelState d’un champ est prioritaire par rapport aux

valeurs de propriétés du modèle quand les deux sont présentes.

Mettre à jour la vue Edit


Dans Views/Departments/Edit.cshtml , apportez les changements suivants :

Ajoutez un champ masqué pour enregistrer la valeur de la propriété RowVersion ,


immédiatement après le champ masqué pour la propriété DepartmentID .

Ajoutez une option « Select Administrator » à la liste déroulante.

CSHTML
@model ContosoUniversity.Models.Department

@{
ViewData["Title"] = "Edit";
}

<h2>Edit</h2>

<h4>Department</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<input type="hidden" asp-for="DepartmentID" />
<input type="hidden" asp-for="RowVersion" />
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Budget" class="control-label"></label>
<input asp-for="Budget" class="form-control" />
<span asp-validation-for="Budget" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" />
<span asp-validation-for="StartDate" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="InstructorID" class="control-label"></label>
<select asp-for="InstructorID" class="form-control" asp-
items="ViewBag.InstructorID">
<option value="">-- Select Administrator --</option>
</select>
<span asp-validation-for="InstructorID" class="text-danger">
</span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>

<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Tester les conflits d'accès concurrentiel


Exécutez l’application et accédez à la page Index des départements. Cliquez avec le
bouton droit sur le lien hypertexte Edit pour le département « English », sélectionnez
Ouvrir dans un nouvel onglet, puis cliquez sur le lien hypertexte Edit pour le
département « English ». Les deux onglets du navigateur affichent maintenant les
mêmes informations.

Changez un champ sous le premier onglet du navigateur, puis cliquez sur Save.

Le navigateur affiche la page Index avec la valeur modifiée.


Changez un champ sous le deuxième onglet du navigateur.

Cliquez sur Enregistrer. Vous voyez un message d’erreur :


Cliquez à nouveau sur Enregistrer. La valeur que vous avez entrée sous le deuxième
onglet du navigateur est enregistrée. Vous voyez les valeurs enregistrées quand la page
Index apparaît.

Mettre à jour la page Delete


Pour la page Delete, Entity Framework détecte les conflits d’accès concurrentiel
provoqués par un autre utilisateur qui modifie le service de façon similaire. Quand la
méthode HttpGet Delete affiche la vue de confirmation, la vue inclut la version d’origine
de RowVersion dans un champ masqué. Cette valeur est ensuite disponible pour la
méthode HttpPost Delete qui est appelée quand l’utilisateur confirme la suppression.
Quand Entity Framework crée la commande SQL DELETE, il inclut une clause WHERE
avec la valeur d’origine de RowVersion . Si la commande a pour résultat qu’aucune ligne
n’est affectée (ce qui signifie que la ligne a été modifiée après l’affichage de la page de
confirmation de la suppression), une exception d’accès concurrentiel est levée et la
méthode HttpGet Delete est appelée avec un indicateur d’erreur défini sur true pour
réafficher la page de confirmation avec un message d’erreur. Il est également possible
qu’aucune ligne ne soit affectée en raison du fait que la ligne a été supprimée par un
autre utilisateur : dans ce cas, aucun message d’erreur n’est donc affiché.

Mettre à jour les méthodes Delete dans le contrôleur


Departments
Dans DepartmentsController.cs , remplacez la méthode Delete HttpGet par le code
suivant :

C#

public async Task<IActionResult> Delete(int? id, bool? concurrencyError)


{
if (id == null)
{
return NotFound();
}

var department = await _context.Departments


.Include(d => d.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.DepartmentID == id);
if (department == null)
{
if (concurrencyError.GetValueOrDefault())
{
return RedirectToAction(nameof(Index));
}
return NotFound();
}
if (concurrencyError.GetValueOrDefault())
{
ViewData["ConcurrencyErrorMessage"] = "The record you attempted to
delete "
+ "was modified by another user after you got the original
values. "
+ "The delete operation was canceled and the current values in
the "
+ "database have been displayed. If you still want to delete
this "
+ "record, click the Delete button again. Otherwise "
+ "click the Back to List hyperlink.";
}

return View(department);
}

La méthode accepte un paramètre facultatif qui indique si la page est réaffichée après
une erreur d’accès concurrentiel. Si cet indicateur a la valeur true et que le département
spécifié n’existe plus, c’est qu’il a été supprimé par un autre utilisateur. Dans ce cas, le
code redirige vers la page Index. Si cet indicateur a la valeur true et que le département
existe, c’est qu’il a été modifié par un autre utilisateur. Dans ce cas, le code envoie un
message d’erreur à la vue en utilisant ViewData .

Remplacez le code de la méthode HttpPost Delete (nommée DeleteConfirmed ) par le


code suivant :

C#

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(Department department)
{
try
{
if (await _context.Departments.AnyAsync(m => m.DepartmentID ==
department.DepartmentID))
{
_context.Departments.Remove(department);
await _context.SaveChangesAsync();
}
return RedirectToAction(nameof(Index));
}
catch (DbUpdateConcurrencyException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
return RedirectToAction(nameof(Delete), new { concurrencyError =
true, id = department.DepartmentID });
}
}
Dans le code du modèle généré automatiquement que vous venez de remplacer, cette
méthode n’acceptait qu’un seul ID d’enregistrement :

C#

public async Task<IActionResult> DeleteConfirmed(int id)

Vous avez changé ce paramètre en une instance d’entité Department créée par le
classeur de modèles. Ceci permet à Entity Framework d’accéder à la valeur de la
propriété RowVersion en plus de la clé d’enregistrement.

C#

public async Task<IActionResult> Delete(Department department)

Vous avez également changé le nom de la méthode d’action de DeleteConfirmed en


Delete . Le code du modèle généré automatiquement utilisait le nom DeleteConfirmed

pour donner à la méthode HttpPost une signature unique. (Pour le CLR, les méthodes
surchargées doivent avoir des paramètres de méthode différents.) Maintenant que les
signatures sont uniques, vous pouvez appliquer la convention MVC et utiliser le même
nom pour les méthodes delete HttpPost et HttpGet.

Si le département est déjà supprimé, la méthode AnyAsync retourne la valeur false et


l’application revient simplement à la méthode Index.

Si une erreur d’accès concurrentiel est interceptée, le code réaffiche la page de


confirmation de suppression et fournit un indicateur indiquant qu’elle doit afficher un
message d’erreur d’accès concurrentiel.

Mettre à jour la vue Delete


Dans Views/Departments/Delete.cshtml , remplacez le code du modèle généré
automatiquement par le code suivant, qui ajoute un champ de message d’erreur et des
champs masqués pour les propriétés DepartmentID et RowVersion. Les modifications
sont mises en surbrillance.

CSHTML

@model ContosoUniversity.Models.Department

@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>

<p class="text-danger">@ViewData["ConcurrencyErrorMessage"]</p>

<h3>Are you sure you want to delete this?</h3>


<div>
<h4>Department</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Name)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Budget)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Budget)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.StartDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.StartDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Administrator)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Administrator.FullName)
</dd>
</dl>

<form asp-action="Delete">
<input type="hidden" asp-for="DepartmentID" />
<input type="hidden" asp-for="RowVersion" />
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</div>
</form>
</div>

Ceci apporte les modifications suivantes :

Ajoute un message d’erreur entre les titres h2 et h3 .

Il remplace FirstMidName par FullName dans le champ Administrator.

Supprime le champ RowVersion.


Ajoute un champ masqué pour la propriété RowVersion .

Exécutez l’application et accédez à la page Index des départements. Cliquez avec le


bouton droit sur le lien hypertexte Delete pour le département « English », sélectionnez
Ouvrir dans un nouvel onglet puis, sous le premier onglet, cliquez sur le lien hypertexte
Edit pour le département « English ».

Dans la première fenêtre, changez une des valeurs, puis cliquez sur Save :

Sous le deuxième onglet, cliquez sur Delete. Vous voyez le message d’erreur d’accès
concurrentiel et les valeurs du département sont actualisées avec ce qui est
actuellement dans la base de données.
Si vous recliquez sur Delete, vous êtes redirigé vers la page Index, qui montre que le
département a été supprimé.

Mettre à jour les vues Details et Create


Vous pouvez éventuellement nettoyer le code du modèle généré automatiquement
dans les vues Details et Create.

Remplacez le code de Views/Departments/Details.cshtml pour supprimer la colonne


RowVersion et afficher le nom complet de l’administrateur.

CSHTML

@model ContosoUniversity.Models.Department
@{
ViewData["Title"] = "Details";
}

<h2>Details</h2>

<div>
<h4>Department</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Name)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Budget)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Budget)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.StartDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.StartDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Administrator)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Administrator.FullName)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.DepartmentID">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>

Remplacez le code de Views/Departments/Create.cshtml pour ajouter une option de


sélection à la liste déroulante.

CSHTML

@model ContosoUniversity.Models.Department

@{
ViewData["Title"] = "Create";
}

<h2>Create</h2>
<h4>Department</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Budget" class="control-label"></label>
<input asp-for="Budget" class="form-control" />
<span asp-validation-for="Budget" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="StartDate" class="control-label"></label>
<input asp-for="StartDate" class="form-control" />
<span asp-validation-for="StartDate" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="InstructorID" class="control-label"></label>
<select asp-for="InstructorID" class="form-control" asp-
items="ViewBag.InstructorID">
<option value="">-- Select Administrator --</option>
</select>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default"
/>
</div>
</form>
</div>
</div>

<div>
<a asp-action="Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Obtenir le code
Télécharger ou afficher l’application complète.
Ressources supplémentaires
Pour plus d’informations sur la gestion de l’accès concurrentiel dans EF Core, consultez
Conflits d’accès concurrentiel.

Étapes suivantes
Dans ce tutoriel, vous allez :

" Découvrez les conflits d’accès concurrentiel


" Propriété de suivi ajoutée
" Créez un contrôleur Departments et des vues
" Vue Index mise à jour
" Méthodes de modification mises à jour
" Vue Edit mise à jour
" Conflits d’accès concurrentiel testés
" Mettre à jour la page Delete
" Vues Details et Create mises à jour

Passez au tutoriel suivant pour découvrir comment implémenter l’héritage table par
hiérarchie pour les entités Instructor et Student.

Suite : Implémenter l’héritage TPH (table par hiérarchie)

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : Implémenter l’héritage -
ASP.NET MVC avec EF Core
Article • 30/11/2023

Dans le didacticiel précédent, vous avez géré les exceptions d’accès concurrentiel. Ce
didacticiel vous indiquera comment implémenter l’héritage dans le modèle de données.

En programmation orientée objet, vous pouvez utiliser l’héritage pour faciliter la


réutilisation du code. Dans ce didacticiel, vous allez modifier les classes Instructor et
Student afin qu’elles dérivent d’une classe de base Person qui contient des propriétés

telles que LastName , communes aux formateurs et aux étudiants. Vous n’ajouterez ni ne
modifierez aucune page web, mais vous modifierez une partie du code et ces
modifications seront automatiquement répercutées dans la base de données.

Dans ce tutoriel, vous allez :

" Mapper l’héritage à la base de données


" Créer la classe Person
" Mettre à jour Student et Instructor
" Ajouter la classe Person au modèle
" Créer et mettre à jour des migrations
" Tester l’implémentation

Prérequis
Gérer l’accès concurrentiel

Mapper l’héritage à la base de données


Les classes Instructor et Student du modèle de données School ont plusieurs
propriétés identiques :
Supposons que vous souhaitez éliminer le code redondant pour les propriétés
partagées par les entités Instructor et Student . Ou vous souhaitez écrire un service
capable de mettre en forme les noms sans se soucier du fait que le nom provienne d’un
formateur ou d’un étudiant. Vous pouvez créer une classe de base Person qui contient
uniquement les propriétés partagées, puis paramétrer les classes Instructor et Student
pour qu’elles héritent de cette classe de base, comme indiqué dans l’illustration suivante
:

Il existe plusieurs façons de représenter cette structure d’héritage dans la base de


données. Vous pouvez avoir une table Person qui inclut des informations sur les
étudiants et les formateurs dans une table unique. Certaines des colonnes pourraient
s’appliquer uniquement aux formateurs (HireDate), certaines uniquement aux étudiants
(EnrollmentDate) et certaines aux deux (LastName, FirstName). En règle générale, vous
pouvez avoir une colonne de discriminateur pour indiquer le type que chaque ligne
représente. Par exemple, la colonne de discriminateur peut avoir « Instructor » pour les
formateurs et « Student » pour les étudiants.

Ce modèle de génération d’une structure d’héritage d’entité à partir d’une table de base
de données unique porte le nom d’héritage table-per-hierarchy (TPH) (table par
hiérarchie).

Une alternative consiste à faire en sorte que la base de données ressemble plus à la
structure d’héritage. Par exemple, vous pouvez avoir uniquement les champs de nom
dans la table Person , et des tables Instructor et Student distinctes avec les champs de
date.

2 Avertissement

La table par type (TPT) n’est pas prise en charge par EF Core 3.x, mais elle a été
implémentée dans EF Core 5.0.

Ce modèle consistant à créer une table de base de données pour chaque classe d’entité
est appelé héritage table-per-type (TPT) (table par type).
Une autre option encore consiste à mapper tous les types non abstraits à des tables
individuelles. Toutes les propriétés d’une classe, y compris les propriétés héritées, sont
mappées aux colonnes de la table correspondante. Ce modèle porte le nom d’héritage
Table-per-Concrete Class (TPC)(table par classe concrète). Si vous avez implémenté
l’héritage TPC pour les classes Person , Student et Instructor comme indiqué
précédemment, les tables Student et Instructor ne seraient pas différentes avant et
après l’implémentation de l’héritage.

Les modèles d’héritage TPC et TPH fournissent généralement de meilleures


performances que les modèles d’héritage TPT, car les modèles TPT peuvent entraîner
des requêtes de jointure complexes.

Ce didacticiel montre comment implémenter l’héritage TPH. TPH est le seul modèle
d’héritage pris en charge par Entity Framework Core. Vous allez créer une classe Person ,
modifier les classes Instructor et Student à dériver de Person , ajouter la nouvelle
classe à DbContext et créer une migration.

 Conseil

Pensez à enregistrer une copie du projet avant d’apporter les modifications


suivantes. Ensuite, si vous rencontrez des problèmes et devez recommencer, il sera
plus facile de démarrer à partir du projet enregistré que d’annuler les étapes
effectuées pour ce didacticiel ou de retourner au début de la série entière.

Créer la classe Person


Dans le dossier Models, créez Person.cs et remplacez le code du modèle par le code
suivant :

C#

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public abstract class Person
{
public int ID { get; set; }

[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than
50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }

[Display(Name = "Full Name")]


public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
}
}

Mettre à jour Student et Instructor


Dans Instructor.cs , dérivez la classe Instructor de la classe Person et supprimez les
champs de clé et de nom. Le code ressemblera à l’exemple suivant :

C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Instructor : Person
{
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }

public ICollection<CourseAssignment> CourseAssignments { get; set; }


public OfficeAssignment OfficeAssignment { get; set; }
}
}

Apportez les mêmes modifications dans Student.cs .

C#
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
public class Student : Person
{
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }

public ICollection<Enrollment> Enrollments { get; set; }


}
}

Ajouter la classe Person au modèle


Ajouter le type d’entité Person à SchoolContext.cs . Les nouvelles lignes apparaissent en
surbrillance.

C#

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}

public DbSet<Course> Courses { get; set; }


public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }
public DbSet<Person> People { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>
().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>
().ToTable("CourseAssignment");
modelBuilder.Entity<Person>().ToTable("Person");

modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}

C’est là tout ce dont Entity Framework a besoin pour configurer l’héritage TPH (table par
hiérarchie). Comme vous le verrez, lorsque la base de données sera mise à jour, elle aura
une table Person à la place des tables Student et Instructor.

Créer et mettre à jour des migrations


Enregistrez vos modifications et générez le projet. Ensuite, ouvrez la fenêtre de
commande dans le dossier du projet et entrez la commande suivante :

CLI .NET

dotnet ef migrations add Inheritance

N’exécutez pas encore la commande database update . Cette commande entraîne une
perte de données, car elle supprime la table Instructor et renomme la table Student en
Person. Vous devez fournir un code personnalisé pour préserver les données existantes.

Ouvrez Migrations/<timestamp>_Inheritance.cs et remplacez la méthode Up par le code


ci-dessous :

C#

protected override void Up(MigrationBuilder migrationBuilder)


{
migrationBuilder.DropForeignKey(
name: "FK_Enrollment_Student_StudentID",
table: "Enrollment");

migrationBuilder.DropIndex(name: "IX_Enrollment_StudentID", table:


"Enrollment");
migrationBuilder.RenameTable(name: "Instructor", newName: "Person");
migrationBuilder.AddColumn<DateTime>(name: "EnrollmentDate", table:
"Person", nullable: true);
migrationBuilder.AddColumn<string>(name: "Discriminator", table:
"Person", nullable: false, maxLength: 128, defaultValue: "Instructor");
migrationBuilder.AlterColumn<DateTime>(name: "HireDate", table:
"Person", nullable: true);
migrationBuilder.AddColumn<int>(name: "OldId", table: "Person",
nullable: true);

// Copy existing Student data into new Person table.


migrationBuilder.Sql("INSERT INTO dbo.Person (LastName, FirstName,
HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName,
null AS HireDate, EnrollmentDate, 'Student' AS Discriminator, ID AS OldId
FROM dbo.Student");
// Fix up existing relationships to match new PK's.
migrationBuilder.Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID
FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator =
'Student')");

// Remove temporary key


migrationBuilder.DropColumn(name: "OldID", table: "Person");

migrationBuilder.DropTable(
name: "Student");

migrationBuilder.CreateIndex(
name: "IX_Enrollment_StudentID",
table: "Enrollment",
column: "StudentID");

migrationBuilder.AddForeignKey(
name: "FK_Enrollment_Person_StudentID",
table: "Enrollment",
column: "StudentID",
principalTable: "Person",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
}

Ce code prend en charge les tâches de mise à jour de base de données suivantes :

Supprime les contraintes de clé étrangère et les index qui pointent vers la table
Student.

Renomme la table Instructor en Person et apporte les modifications nécessaires


pour qu’elle stocke les données des étudiants :

Ajoute une EnrollmentDate nullable pour les étudiants.


Ajoute la colonne Discriminator pour indiquer si une ligne est pour un étudiant ou
un formateur.

Rend HireDate nullable étant donné que les lignes d’étudiant n’ont pas de dates
d’embauche.

Ajoute un champ temporaire qui sera utilisé pour mettre à jour les clés étrangères
qui pointent vers les étudiants. Lorsque vous copiez des étudiants dans la table
Person, ils obtiennent de nouvelles valeurs de clés primaires.

Copie des données à partir de la table Student dans la table Person. Cela entraîne
l’affectation de nouvelles valeurs de clés primaires aux étudiants.

Corrige les valeurs de clés étrangères qui pointent vers les étudiants.

Crée de nouveau les index et les contraintes de clé étrangère, désormais pointées
vers la table Person.

(Si vous aviez utilisé un GUID à la place d’un entier comme type de clé primaire, les
valeurs des clés primaires des étudiants n’auraient pas changé, et plusieurs de ces
étapes auraient pu être omises.)

Exécutez la commande database update :

CLI .NET

dotnet ef database update

(Dans un système de production, vous apporteriez les modifications correspondantes à


la méthode Down au cas où vous auriez à l’utiliser pour revenir à la version précédente
de la base de données. Dans le cadre de ce tutoriel, vous n’utiliserez pas la méthode
Down .)

7 Notes

Vous pouvez obtenir d’autres erreurs en apportant des modifications au schéma


dans une base de données qui comporte déjà des données. Si vous obtenez des
erreurs de migration que vous ne pouvez pas résoudre, vous pouvez changer le
nom de la base de données dans la chaîne de connexion ou supprimer la base de
données. Avec une nouvelle base de données, il n’y a pas de données à migrer et la
commande de mise à jour de base de données a plus de chances de s’exécuter
sans erreur. Pour supprimer la base de données, utilisez SSOX ou exécutez la
commande CLI database drop .
Tester l’implémentation
Exécutez l’application et essayez différentes pages. Tout fonctionne comme avant.

Dans l’Explorateur d’objets SQL Server, développez Data Connections/SchoolContext


puis Tables, et vous constatez que les tables Student et Instructor ont été remplacées
par une table Person. Ouvrez le concepteur de la table Person et vous constatez qu’elle
possède toutes les colonnes qui existaient dans les tables Student et Instructor.

Cliquez avec le bouton droit sur la table Person, puis cliquez sur Afficher les données de
la table pour voir la colonne de discriminateur.
Obtenir le code
Télécharger ou afficher l’application complète.

Ressources supplémentaires
Pour plus d’informations sur l’héritage dans Entity Framework Core, consultez Héritage.

Étapes suivantes
Dans ce tutoriel, vous allez :

" Mappez l’héritage à la base de données


" Créez la classe Person
" Mettez à jour Student et Instructor
" Classe Person ajoutée au modèle
" Migrations créées et mises à jour
" Implémentation testée

Passez au tutoriel suivant pour découvrir comment gérer divers scénarios Entity
Framework relativement avancés.

Prochain : Rubriques avancées


6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : en savoir plus sur les scénarios
avancés - ASP.NET MVC avec EF Core
Article • 30/11/2023

Dans le didacticiel précédent, vous avez implémenté l’héritage TPH (table par
hiérarchie). Ce didacticiel présente plusieurs rubriques qu’il est utile de connaître lorsque
vous allez au-delà des principes de base du développement d’applications web ASP.NET
Core qui utilisent Entity Framework Core.

Dans ce tutoriel, vous allez :

" Exécuter des requêtes SQL brutes


" Appeler une requête pour retourner des entités
" Appeler une requête pour retourner d’autres types
" Appeler une requête de mise à jour
" Examiner les requêtes SQL
" Créer une couche d’abstraction
" En savoir plus sur la Détection automatique des modifications
" En savoir plus sur le code source et les plans de développement EF Core
" Apprendre à utiliser du code dynamique LINQ pour simplifier le code

Prérequis
Implémenter l’héritage

Exécuter des requêtes SQL brutes


L’un des avantages d’utiliser Entity Framework est que cela évite de lier votre code trop
étroitement à une méthode particulière de stockage des données. Il le fait en générant
des requêtes et des commandes SQL pour vous, ce qui vous évite d’avoir à les écrire
vous-même. Mais, dans certains scénarios exceptionnels, vous devez exécuter des
requêtes SQL spécifiques que vous créez manuellement. Pour ces scénarios, l’API Entity
Framework Code First comprend des méthodes qui vous permettent de transmettre des
commandes SQL directement à la base de données. Les options suivantes sont
disponibles dans EF Core 1.0 :

Utilisez la méthode DbSet.FromSql pour les requêtes qui renvoient des types
d’entités. Les objets renvoyés doivent être du type attendu par l’objet DbSet et ils
sont automatiquement suivis par le contexte de base de données, sauf si vous
désactivez le suivi.

Utilisez Database.ExecuteSqlCommand pour les commandes ne se rapportant pas aux


requêtes.

Si vous avez besoin d’exécuter une requête qui renvoie des types qui ne sont pas des
entités, vous pouvez utiliser ADO.NET avec la connexion de base de données fournie par
EF. Les données renvoyées ne font pas l’objet d’un suivi par le contexte de base de
données, même si vous utilisez cette méthode pour récupérer des types d’entités.

Comme c’est toujours le cas lorsque vous exécutez des commandes SQL dans une
application web, vous devez prendre des précautions pour protéger votre site contre
des attaques par injection de code SQL. Une manière de procéder consiste à utiliser des
requêtes paramétrables pour vous assurer que les chaînes soumises par une page web
ne peuvent pas être interprétées comme des commandes SQL. Dans ce didacticiel, vous
utiliserez des requêtes paramétrables lors de l’intégration de l’entrée utilisateur dans
une requête.

Appeler une requête pour retourner des entités


La classe DbSet<TEntity> fournit une méthode que vous pouvez utiliser pour exécuter
une requête qui renvoie une entité de type TEntity . Pour voir comment cela fonctionne
vous allez modifier le code dans la méthode Details du contrôleur Department.

Dans DepartmentsController.cs , dans la méthode Details , remplacez le code qui


récupère un département par un appel de méthode FromSql , comme indiqué dans le
code en surbrillance suivant :

C#

public async Task<IActionResult> Details(int? id)


{
if (id == null)
{
return NotFound();
}

string query = "SELECT * FROM Department WHERE DepartmentID = {0}";


var department = await _context.Departments
.FromSql(query, id)
.Include(d => d.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync();

if (department == null)
{
return NotFound();
}

return View(department);
}

Pour vérifier que le nouveau code fonctionne correctement, sélectionnez l’onglet


Departments, puis Details pour l’un des services.

Appeler une requête pour retourner d’autres


types
Précédemment, vous avez créé une grille de statistiques des étudiants pour la page
About, qui montrait le nombre d’étudiants pour chaque date d’inscription. Vous avez
obtenu les données à partir du jeu d’entités Students ( _context.Students ) et utilisé LINQ
pour projeter les résultats dans une liste d’objets de modèle de vue
EnrollmentDateGroup . Supposons que vous voulez écrire le code SQL lui-même plutôt

qu’utiliser LINQ. Pour ce faire, vous avez besoin d’exécuter une requête SQL qui renvoie
autre chose que des objets d’entité. Dans EF Core 1.0, une manière de procéder consiste
à écrire du code ADO.NET et à obtenir la connexion de base de données à partir d’EF.

Dans HomeController.cs , remplacez la méthode About par le code ci-dessous :


C#

public async Task<ActionResult> About()


{
List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
var conn = _context.Database.GetDbConnection();
try
{
await conn.OpenAsync();
using (var command = conn.CreateCommand())
{
string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount
"
+ "FROM Person "
+ "WHERE Discriminator = 'Student' "
+ "GROUP BY EnrollmentDate";
command.CommandText = query;
DbDataReader reader = await command.ExecuteReaderAsync();

if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var row = new EnrollmentDateGroup { EnrollmentDate =
reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
groups.Add(row);
}
}
reader.Dispose();
}
}
finally
{
conn.Close();
}
return View(groups);
}

Ajoutez une instruction using :

C#

using System.Data.Common;

Exécutez l’application et accédez à la page About. Elle affiche les mêmes données
qu’auparavant.
Appeler une requête de mise à jour
Supposons que les administrateurs de Contoso University veuillent effectuer des
modifications globales dans la base de données, comme par exemple modifier le
nombre de crédits pour chaque cours. Si l’université a un grand nombre de cours, il
serait inefficace de les récupérer tous sous forme d’entités et de les modifier
individuellement. Dans cette section, vous allez implémenter une page web permettant
à l’utilisateur de spécifier un facteur selon lequel il convient de modifier le nombre de
crédits pour tous les cours, et vous effectuerez la modification en exécutant une
instruction SQL UPDATE. La page web ressemblera à l’illustration suivante :

Dans CoursesController.cs , ajoutez les méthodes UpdateCourseCredits pour HttpGet et


HttpPost :

C#
public IActionResult UpdateCourseCredits()
{
return View();
}

C#

[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
if (multiplier != null)
{
ViewData["RowsAffected"] =
await _context.Database.ExecuteSqlCommandAsync(
"UPDATE Course SET Credits = Credits * {0}",
parameters: multiplier);
}
return View();
}

Lorsque le contrôleur traite une demande HttpGet, rien n’est renvoyé dans
ViewData["RowsAffected"] et la vue affiche une zone de texte vide et un bouton d’envoi,

comme indiqué dans l’illustration précédente.

Lorsque vous cliquez sur le bouton Update, la méthode HttpPost est appelée et le
multiplicateur a la valeur entrée dans la zone de texte. Le code exécute alors l’instruction
SQL qui met à jour les cours et renvoie le nombre de lignes affectées à la vue dans
ViewData . Lorsque la vue obtient une valeur RowsAffected , elle affiche le nombre de

lignes mis à jour.

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le dossier Vues/Cours,
puis cliquez sur Ajouter > Nouvel élément.

Dans la boîte de dialogue Ajouter un nouvel élément, cliquez sur ASP.NET Core sous
Installé dans le volet gauche, cliquez sur Vue Razor et nommez la nouvelle vue
UpdateCourseCredits.cshtml .

Dans Views/Courses/UpdateCourseCredits.cshtml , remplacez le code du modèle par le


code suivant :

CSHTML

@{
ViewBag.Title = "UpdateCourseCredits";
}
<h2>Update Course Credits</h2>

@if (ViewData["RowsAffected"] == null)


{
<form asp-action="UpdateCourseCredits">
<div class="form-actions no-color">
<p>
Enter a number to multiply every course's credits by:
@Html.TextBox("multiplier")
</p>
<p>
<input type="submit" value="Update" class="btn btn-default"
/>
</p>
</div>
</form>
}
@if (ViewData["RowsAffected"] != null)
{
<p>
Number of rows updated: @ViewData["RowsAffected"]
</p>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>

Exécutez la méthode UpdateCourseCredits en sélectionnant l’onglet Courses, puis en


ajoutant « /UpdateCourseCredits » à la fin de l’URL dans la barre d’adresse du
navigateur (par exemple : http://localhost:5813/Courses/UpdateCourseCredits ). Entrez
un nombre dans la zone de texte :

Cliquez sur Update. Vous voyez le nombre de lignes affectées :


Cliquez sur Revenir à la liste pour afficher la liste des cours avec le nombre révisé de
crédits.

Notez que le code de production garantit que les mises à jour fourniront toujours des
données valides. Le code simplifié indiqué ici peut multiplier le nombre de crédits
suffisamment pour générer des nombres supérieurs à 5. (La propriété Credits a un
attribut [Range(0, 5)] .) La requête de mise à jour fonctionne, mais des données non
valides peuvent provoquer des résultats inattendus dans d’autres parties du système qui
supposent que le nombre de crédits est inférieur ou égal à 5.

Pour plus d’informations sur les requêtes SQL brutes, consultez Requêtes SQL brutes.

Examiner les requêtes SQL


Il est parfois utile de pouvoir voir les requêtes SQL réelles qui sont envoyées à la base de
données. Les fonctionnalités de journalisation intégrées pour ASP.NET Core sont
utilisées automatiquement par EF Core pour écrire des journaux qui contiennent le code
SQL pour les requêtes et les mises à jour. Dans cette section, vous verrez des exemples
de journalisation SQL.

Ouvrez StudentsController.cs et dans la méthode Details , définissez un point d’arrêt


sur l’instruction if (student == null) .

Exécutez l’application en mode débogage et accédez à la page Details d’un étudiant.

Accédez à la fenêtre Output qui indique la sortie de débogage. Vous voyez la requête :

Microsoft.EntityFrameworkCore.Database.Command:Information: Executed
DbCommand (56ms) [Parameters=[@__id_0='?'], CommandType='Text',
CommandTimeout='30']
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].
[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N'Student') AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed
DbCommand (122ms) [Parameters=[@__id_0='?'], CommandType='Text',
CommandTimeout='30']
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID],
[s.Enrollments].[Grade], [s.Enrollments].[StudentID], [e.Course].[CourseID],
[e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] =
[e.Course].[CourseID]
INNER JOIN (
SELECT TOP(1) [s0].[ID]
FROM [Person] AS [s0]
WHERE ([s0].[Discriminator] = N'Student') AND ([s0].[ID] = @__id_0)
ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]

Vous pouvez remarquer ici quelque chose susceptible de vous surprendre : l’instruction
SQL sélectionne jusqu’à 2 lignes ( TOP(2) ) à partir de la table Person. La méthode
SingleOrDefaultAsync ne se résout pas à 1 ligne sur le serveur. Voici pourquoi :

Si la requête retourne plusieurs lignes, la méthode retourne la valeur Null.


Pour déterminer si la requête retourne plusieurs lignes, EF doit vérifier s’il retourne
au moins 2.

Notez que vous n’êtes pas tenu d’utiliser le mode débogage et de vous arrêter à un
point d’arrêt pour obtenir la sortie de journalisation dans la fenêtre Output. C’est
simplement un moyen pratique d’arrêter la journalisation au stade où vous voulez
examiner la sortie. Si vous ne le faites pas, la journalisation se poursuit et vous devez
revenir en arrière pour rechercher les parties qui vous intéressent.

Créer une couche d’abstraction


De nombreux développeurs écrivent du code pour implémenter les modèles d’unité de
travail et de référentiel comme un wrapper autour du code qui fonctionne avec Entity
Framework. Ces modèles sont destinés à créer une couche d’abstraction entre la couche
d’accès aux données et la couche de logique métier d’une application. L’implémentation
de ces modèles peut favoriser l’isolation de votre application face à des modifications
dans le magasin de données et peut faciliter le test unitaire automatisé ou le
développement piloté par les tests (TDD). Toutefois, l’écriture de code supplémentaire
pour implémenter ces modèles n’est pas toujours le meilleur choix pour les applications
qui utilisent EF, et ce pour plusieurs raisons :
La classe de contexte EF elle-même isole votre code face au code spécifique de
magasin de données.

La classe de contexte EF peut agir comme une classe d’unité de travail pour les
mises à jour de base de données que vous effectuez à l’aide d’EF.

EF inclut des fonctionnalités pour implémenter TDD sans écrire de code de


référentiel.

Pour plus d’informations sur la façon d’implémenter les modèles d’unité de travail et de
référentiel, consultez la version Entity Framework 5 de cette série de didacticiels.

Entity Framework Core implémente un fournisseur de base de données en mémoire qui


peut être utilisé pour les tests. Pour plus d’informations, consultez Tester avec
InMemory.

Détection automatique des modifications


Entity Framework détermine la manière dont une entité a changé (et par conséquent les
mises à jour qui doivent être envoyées à la base de données) en comparant les valeurs
en cours d’une entité avec les valeurs d’origine. Les valeurs d’origine sont stockées
lorsque l’entité fait l’objet d’une requête ou d’une jointure. Certaines des méthodes qui
provoquent la détection automatique des modifications sont les suivantes :

DbContext.SaveChanges

DbContext.Entry

ChangeTracker.Entries

Si vous effectuez le suivi d’un grand nombre d’entités et que vous appelez l’une de ces
méthodes de nombreuses fois dans une boucle, vous pouvez obtenir des améliorations
significatives des performances en désactivant temporairement la détection
automatique des modifications à l’aide de la propriété
ChangeTracker.AutoDetectChangesEnabled . Par exemple :

C#

_context.ChangeTracker.AutoDetectChangesEnabled = false;

Code source et plans de développement EF


Core
La source d’Entity Framework Core se trouve à l’adresse
https://github.com/dotnet/efcore . Le dépôt EF Core contient les builds nocturnes, le
suivi des problèmes, les spécifications des fonctionnalités, les notes des réunions de
conception et la feuille de route de développement futur . Vous pouvez signaler ou
rechercher des bogues, et apporter votre contribution.

Bien que le code source soit ouvert, Entity Framework Core est entièrement pris en
charge comme produit Microsoft. L’équipe Microsoft Entity Framework garde le contrôle
sur le choix des contributions qui sont acceptées et teste toutes les modifications du
code pour garantir la qualité de chaque version.

Ingénierie à rebours à partir de la base de


données existante
Pour rétroconcevoir un modèle de données comprenant des classes d’entité issues
d’une base de données existante, utilisez la commande scaffold-dbcontext. Consultez le
didacticiel de prise en main.

Utiliser du code dynamique LINQ pour


simplifier le code
Le troisième didacticiel de cette série montre comment écrire du code LINQ en codant
en dur les noms des colonnes dans une instruction switch . Avec deux colonnes
sélectionnables, cela fonctionne correctement, mais si vous avez de nombreuses
colonnes, le code peut devenir très détaillé. Pour résoudre ce problème, vous pouvez
utiliser la méthode EF.Property pour spécifier le nom de la propriété sous forme de
chaîne. Pour tester cette approche, remplacez la méthode Index dans
StudentsController par le code suivant.

C#

public async Task<IActionResult> Index(


string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] =
String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
ViewData["DateSortParm"] =
sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" :
"EnrollmentDate";

if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}

ViewData["CurrentFilter"] = searchString;

var students = from s in _context.Students


select s;

if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}

if (string.IsNullOrEmpty(sortOrder))
{
sortOrder = "LastName";
}

bool descending = false;


if (sortOrder.EndsWith("_desc"))
{
sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
descending = true;
}

if (descending)
{
students = students.OrderByDescending(e => EF.Property<object>(e,
sortOrder));
}
else
{
students = students.OrderBy(e => EF.Property<object>(e,
sortOrder));
}

int pageSize = 3;
return View(await
PaginatedList<Student>.CreateAsync(students.AsNoTracking(),
pageNumber ?? 1, pageSize));
}

Remerciements
Tom Dykstra et Rick Anderson (twitter @RickAndMSFT)) ont rédigé ce tutoriel. Rowan
Miller, Diego Vega et d’autres membres de l’équipe Entity Framework ont participé à la
revue du code et ont aidé à résoudre les problèmes qui se sont posés lorsque nous
avons écrit le code pour ces didacticiels. John Parente et Paul Goldman ont travaillé sur
la mise à jour de ce tutoriel pour ASP.NET Core 2.2.

Résolution des erreurs courantes

ContosoUniversity.dll est utilisé par un autre processus


Message d’erreur :

Impossible d’ouvrir ’...bin\Debug\netcoreapp1.0\ContosoUniversity.dll’ en écriture --


’Le processus ne peut pas accéder au fichier
’...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll’, car il est en cours d’utilisation
par un autre processus.

Solution :

Arrêtez le site dans IIS Express. Accédez à la barre d’état système de Windows,
recherchez IIS Express, cliquez avec le bouton droit sur son icône, sélectionnez le site
Contoso University, puis cliquez sur Arrêter le site.

Migration structurée sans code dans les méthodes Up et


Down
Cause probable :

Les commandes CLI d’EF ne ferment et n’enregistrent pas automatiquement des fichiers
de code. Si vous avez des modifications non enregistrées lorsque vous exécutez la
commande migrations add , EF ne trouve pas vos modifications.

Solution :

Exécutez la commande migrations remove , enregistrez vos modifications de code et


réexécutez la commande migrations add .

Erreurs lors de l’exécution de la mise à jour de base de


données
Vous pouvez obtenir d’autres erreurs en apportant des modifications au schéma dans
une base de données qui comporte déjà des données. Si vous obtenez des erreurs de
migration que vous ne pouvez pas résoudre, vous pouvez changer le nom de la base de
données dans la chaîne de connexion ou supprimer la base de données. Avec une
nouvelle base de données, il n’y a pas de données à migrer et la commande de mise à
jour de base de données a beaucoup plus de chances de s’exécuter sans erreur.

L’approche la plus simple consiste à renommer la base de données en


appsettings.json . La prochaine fois que vous exécuterez database update , une nouvelle

base de données sera créée.

Pour supprimer une base de données dans SSOX, cliquez avec le bouton droit sur la
base de données, cliquez sur Supprimer, puis, dans la boîte de dialogue Supprimer la
base de données, sélectionnez Fermer les connexions existantes et cliquez sur OK.

Pour supprimer une base de données à l’aide de l’interface CLI, exécutez la commande
CLI database drop :

CLI .NET

dotnet ef database drop

Erreur lors de la localisation de l’instance SQL Server


Message d’erreur :

Une erreur liée au réseau ou propre à une instance s’est produite lors de
l’établissement d’une connexion à SQL Server. Le serveur est introuvable ou
inaccessible. Vérifiez que le nom de l’instance est correct et que SQL Server est
configuré pour autoriser les connexions à distance. (fournisseur : interfaces réseau
SQL, erreur : 26 - Erreur lors de la localisation du serveur/de l’instance spécifiés)

Solution :

Vérifiez la chaîne de connexion. Si vous avez supprimé manuellement le fichier de base


de données, modifiez le nom de la base de données dans la chaîne de construction pour
recommencer avec une nouvelle base de données.

Obtenir le code
Télécharger ou afficher l’application complète.
Ressources supplémentaires
Pour plus d’informations sur EF Core, consultez la documentation sur Entity Framework
Core. Un ouvrage est également disponible : Entity Framework Core in Action .

Pour obtenir des informations sur la façon de déployer une application web, consultez
Héberger et déployer ASP.NET Core.

Pour obtenir des informations sur d’autres sujets associés à ASP.NET Core MVC, tels que
l’authentification et l’autorisation, consultez Vue d’ensemble d’ASP.NET Core.

Étapes suivantes
Dans ce tutoriel, vous allez :

" Exécuter des requêtes SQL brutes


" Appeler une requête pour retourner des entités
" Appeler une requête pour retourner d’autres types
" Appeler une requête de mise à jour
" Examiner les requêtes SQL
" Créer une couche d’abstraction
" En savoir plus sur la détection automatique des modifications
" En savoir plus sur le code source et les plans de développement EF Core
" Utiliser du code dynamique LINQ pour simplifier le code

Cette étape termine cette série de tutoriels sur l’utilisation d’Entity Framework Core dans
une application ASP.NET Core MVC. Cette série a fait appel à une nouvelle base de
données ; une alternative consiste à rétroconcevoir un modèle à partir d’une base de
données existante.

Tutoriel : EF Core avec MVC, base de données existante

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.
 Indiquer des commentaires sur
le produit
Vue d’ensemble des notions de base
d’ASP.NET Core
Article • 30/11/2023

Cet article fournit une vue d’ensemble des principes fondamentaux de la génération
d’applications ASP.NET Core, notamment l’injection de dépendances, la configuration,
les middlewares, etc.

Program.cs
Les applications ASP.NET Core créées avec les modèles web contiennent le code de
démarrage de l’application dans le fichier Program.cs . Le fichier Program.cs se trouve à
l’emplacement où :

Les services nécessaires à l’application sont configurés.


Le pipeline de traitement des requêtes de l’application est défini sous la forme
d’une série de composants de middlewares.

Le code de démarrage d’application suivant prend en charge les éléments suivants :

Razor Pages
Contrôleurs MVC avec vues
API web avec contrôleurs
API web minimales

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();

app.MapGet("/hi", () => "Hello!");

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Injection de dépendances (services)


ASP.NET Core inclut l’injection de dépendances (DI) qui rend les services configurés
disponibles dans l’ensemble d’une application. Les services sont ajoutés au conteneur
d’injection de dépendances avec WebApplicationBuilder.Services, builder.Services
dans le code précédent. Quand WebApplicationBuilder est instancié, de nombreux
services fournis par le framework sont ajoutés. builder est un WebApplicationBuilder
dans le code suivant :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

Dans le code en surbrillance précédent, builder permet l’ajout de la configuration, de la


journalisation et de nombreux autres services au conteneur d’injection de dépendances.

Le code suivant ajoute des pages Razor, des contrôleurs MVC avec vues et un
DbContext personnalisé au conteneur d’injection de dépendances :

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RPMovieConte
xt")));

var app = builder.Build();

Les services sont généralement résolus à partir de l’interface d’injection de dépendances


à l’aide de l’injection de constructeurs. Le framework d’injection de dépendances fournit
une instance de ce service au moment de l’exécution.

Le code suivant utilise l’injection de constructeurs pour résoudre le contexte de base de


données et le journaliseur à partir de l’injection de dépendances :

C#

public class IndexModel : PageModel


{
private readonly RazorPagesMovieContext _context;
private readonly ILogger<IndexModel> _logger;

public IndexModel(RazorPagesMovieContext context, ILogger<IndexModel>


logger)
{
_context = context;
_logger = logger;
}

public IList<Movie> Movie { get;set; }

public async Task OnGetAsync()


{
_logger.LogInformation("IndexModel OnGetAsync.");
Movie = await _context.Movie.ToListAsync();
}
}

Intergiciel (middleware)
Le pipeline de traitement des requêtes est composé d’une série de composants
d’intergiciel (middleware). Chaque composant effectue des opérations sur un
HttpContext, puis appelle le middleware suivant dans le pipeline, ou met fin à la requête.

Par convention, un composant de middleware est ajouté au pipeline via l’appel d’une
méthode d’extension Use{Feature} . Le middleware ajouté à l’application est mis en
évidence dans le code suivant :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapGet("/hi", () => "Hello!");

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Pour plus d’informations, consultez Intergiciel (middleware) ASP.NET Core.

Host
Au démarrage, une application ASP.NET Core génère un hôte. L’hôte encapsule toutes
les ressources de l’application, par exemple :

Une implémentation serveur HTTP


Composants d’intergiciel (middleware)
Journalisation
Services d’injection de dépendances
Configuration

Il existe trois hôtes différents capables d’exécuter une application ASP.NET Core :

ASP.NET Core WebApplication, également appelé hôte minimal


Hôte générique .NET combiné à ASP.NET CoreConfigureWebHostDefaults
WebHost ASP.NET Core

Les types ASP.NET Core WebApplication et WebApplicationBuilder sont recommandés


et utilisés dans tous les modèles ASP.NET Core. WebApplication se comporte de la
même manière que l’hôte générique .NET et expose la plupart des mêmes interfaces,
mais nécessite moins de rappels à configurer. WebHost ASP.NET Core est disponible
uniquement à des fins de compatibilité descendante.

L’exemple suivant instancie un WebApplication :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

La méthode WebApplicationBuilder.Build configure un hôte avec un ensemble d’options


par défaut, par exemple :

Utilisez Kestrel en tant que serveur web, et activez l’intégration d’IIS.


Chargez la configuration à partir de appsettings.json , des variables
d’environnement, des arguments de ligne de commande et d’autres sources de
configuration.
Envoyez la sortie de journalisation aux fournisseurs Console et Debug.

Scénarios non basés sur le web


L’hôte générique permet à d’autres types d’application d’utiliser des extensions de
framework composites, par exemple la journalisation, l’injection de dépendance, la
configuration et la gestion de la durée de vie de l’application. Pour plus d’informations,
consultez Hôte générique .NET dans ASP.NET Core et Tâches en arrière-plan avec des
services hébergés dans ASP.NET Core.

Serveurs
Une application ASP.NET Core utilise une implémentation de serveur HTTP pour écouter
les requêtes HTTP. Les surfaces de serveur envoient des requêtes à l’application comme
un ensemble de fonctionnalités de requête composées dans un HttpContext .
Windows

ASP.NET Core fournit les implémentations de serveur suivantes :

Kestrel est un serveur web multiplateforme. Kestrel est souvent exécuté dans
une configuration de proxy inverse à l’aide d’IIS . Dans ASP.NET Core 2.0 ou
les versions ultérieures, Kestrel peut être exécuté en tant que serveur de
périphérie public exposé directement à Internet.
Le serveur HTTP IIS est un serveur pour Windows qui utilise IIS. Avec ce
serveur, l’application ASP.NET Core et IIS s’exécutent dans le même processus.
HTTP.sys est un serveur Windows qui n’est pas utilisé avec IIS.

Pour plus d’informations, consultez Implémentations du serveur web dans ASP.NET


Core.

Configuration
ASP.NET Core fournit un framework de configuration qui obtient les paramètres sous
forme de paires nom-valeur à partir d’un ensemble ordonné de fournisseurs de
configuration. Des fournisseurs de configuration intégrés sont disponibles pour diverses
sources, par exemple les fichiers .json , les fichiers .xml , les variables d’environnement
et les arguments de ligne de commande. Écrivez des fournisseurs de configuration
personnalisés pour prendre en charge d’autres sources.

Par défaut, les applications ASP.NET Core sont configurées pour lire appsettings.json ,
les variables d’environnement, la ligne de commande, etc. Quand la configuration de
l’application est chargée, les valeurs des variables d’environnement remplacent les
valeurs de appsettings.json .

Pour gérer les données de configuration confidentielles telles que les mots de passe,
.NET Core fournit l’outil Secret Manager. Pour les secrets de production, nous vous
recommandons Azure Key Vault.

Pour plus d’informations, consultez Configuration dans ASP.NET Core.

Environnements
Les environnements d’exécution, par exemple Development , Staging et Production , sont
disponibles dans ASP.NET Core. Spécifiez l’environnement d’exécution d’une application
en définissant la variable d’environnement ASPNETCORE_ENVIRONMENT . ASP.NET Core lit la
variable d’environnement au démarrage de l’application et stocke la valeur dans une
implémentation IWebHostEnvironment . Cette implémentation est disponible n’importe où
dans une application via l’injection de dépendances.

L’exemple suivant configure le gestionnaire d’exceptions et le middleware (intergiciel)


HSTS (HTTP Strict Transport Security Protocol) quand l’environnement d’exécution n’est
pas l’environnement Development :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapGet("/hi", () => "Hello!");

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Pour plus d’informations, consultez Utiliser plusieurs environnements dans ASP.NET


Core.

Journalisation
ASP.NET Core prend en charge une API de journalisation qui fonctionne avec un large
éventail de fournisseurs de journalisation intégrés et tiers. Les fournisseurs disponibles
sont les suivants :

Console
Déboguer
Suivi des événements sur Windows
Journal des événements Windows
TraceSource
Azure App Service
Azure Application Insights

Pour créer des journaux, résolvez un service ILogger<TCategoryName> à partir de


l’injection de dépendances et des méthodes de journalisation des appels telles que
LogInformation. Par exemple :

C#

public class IndexModel : PageModel


{
private readonly RazorPagesMovieContext _context;
private readonly ILogger<IndexModel> _logger;

public IndexModel(RazorPagesMovieContext context, ILogger<IndexModel>


logger)
{
_context = context;
_logger = logger;
}

public IList<Movie> Movie { get;set; }

public async Task OnGetAsync()


{
_logger.LogInformation("IndexModel OnGetAsync.");
Movie = await _context.Movie.ToListAsync();
}
}

Pour plus d’informations, consultez Journalisation dans .NET Core et ASP.NET Core.

Routage
Un itinéraire est un modèle d’URL qui est mappé à un gestionnaire. Le gestionnaire est
généralement une page Razor, une méthode d’action dans un contrôleur MVC ou un
middleware. Le routage ASP.NET Core vous permet de contrôler les URL utilisées par
votre application.

Le code suivant, généré par le modèle d’application web ASP.NET Core, appelle
UseRouting :

C#
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Pour plus d’informations, consultez Routage dans ASP.NET Core.

Gestion des erreurs


ASP.NET Core offre des fonctionnalités intégrées pour gérer des erreurs, telles que :

Une page d’exceptions du développeur


Pages d’erreur personnalisées
Pages de codes d’état statique
Gestion des exceptions de démarrage

Pour plus d’informations, consultez Gérer les erreurs dans ASP.NET Core.

Effectuer des requêtes HTTP


Une implémentation de IHttpClientFactory est disponible pour la création d’instances
HttpClient . La fabrique :

Fournit un emplacement central pour le nommage et la configuration d’instance


de HttpClient logiques. Par exemple, inscrivez et configurez un client github pour
accéder à GitHub. Inscrivez et configurez un client par défaut à d’autres fins.
Prend en charge l’inscription et le chaînage de plusieurs gestionnaires de
délégation pour créer un pipeline de middlewares pour les requêtes sortantes. Ce
modèle est similaire au pipeline de middleware entrant d’ASP.NET Core. Le modèle
fournit un mécanisme permettant de gérer les problèmes transversaux pour les
requêtes HTTP, notamment la mise en cache, la gestion des erreurs, la sérialisation
et la journalisation.
S’intègre à Polly, une bibliothèque tierce populaire pour la gestion des erreurs
temporaires.
Gère le regroupement et la durée de vie des instances de HttpClientHandler sous-
jacentes pour éviter les problèmes DNS courants qui se produisent durant la
gestion manuelle des durées de vie de HttpClient .
Ajoute une expérience de journalisation configurable (via ILogger) pour toutes les
requêtes envoyées via les clients créés par la fabrique.

Pour plus d’informations, consultez Effectuer des requêtes HTTP en utilisant


IHttpClientFactory dans ASP.NET Core.

Racine de contenu
La racine du contenu est le chemin de base pour :

L’exécutable hébergeant l’application (.exe).


Les assemblys compilés qui composent l’application (.dll).
Fichiers de contenu utilisés par l’application, par exemple :
Fichiers Razor ( .cshtml , .razor )
Fichiers config ( .json , .xml )
Fichiers de données ( .db )
La racine web, généralement le dossier wwwroot.

Durant le développement, la racine du contenu correspond par défaut au répertoire


racine du projet. Ce répertoire est également le chemin de base des fichiers de contenu
de l’application et de la racine web. Spécifiez une autre racine de contenu en définissant
son chemin au moment de la génération de l’hôte. Pour plus d’informations, consultez
Racine de contenu.

Racine web
La racine web est le chemin de base des fichiers de ressources publics statiques, par
exemple :

Feuilles de style ( .css )


JavaScript ( .js )
Images ( .png , .jpg )

Par défaut, les fichiers statiques sont traités uniquement à partir du répertoire racine
web et de ses sous-répertoires. Le chemin de la racine web par défaut est
{racine_du_contenu}/wwwroot. Spécifiez une autre racine web en définissant son chemin
au moment de la génération de l’hôte. Pour plus d’informations, consultez Racine web.

Empêchez la publication de fichiers dans wwwroot avec l’élément de projet <Content>


dans le fichier projet. L’exemple suivant empêche la publication de contenu dans
wwwroot/local et ses sous-répertoires :

XML

<ItemGroup>
<Content Update="wwwroot\local\**\*.*" CopyToPublishDirectory="Never" />
</ItemGroup>

Dans les fichiers Razor .cshtml , ~/ pointe vers la racine web. Un chemin commençant
par ~/ est appelé chemin virtuel.

Pour plus d’informations, consultez Fichiers statiques dans ASP.NET Core.

Ressources supplémentaires
Code source de WebApplicationBuilder

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Démarrage d’une application dans
ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Les applications ASP.NET Core créées avec les modèles web contiennent le code de
démarrage de l’application dans le fichier Program.cs .

Le code de démarrage d’application suivant prend en charge les éléments suivants :

Razor Pages
Contrôleurs MVC avec vues
API web avec contrôleurs
API minimales

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapGet("/hi", () => "Hello!");

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Les applications utilisant EventSource peuvent mesurer le temps de démarrage pour


comprendre et optimiser les performances de démarrage. L’événement ServerReady
dans Microsoft.AspNetCore.Hosting représente le point où le serveur est prêt à
répondre aux demandes.

Pour plus d’informations sur le démarrage de l’application, consultez ASP.NET Core vue
d’ensemble des principes fondamentaux.

Étendre le démarrage avec les filtres de


démarrage
Utilisez IStartupFilter :

Pour configurer l’intergiciel (middleware) au début ou à la fin du pipeline


d’intergiciels d’une application sans appel explicite à Use{Middleware} . Permet
IStartupFilter d’ajouter des valeurs par défaut au début du pipeline sans inscrire

explicitement l’intergiciel par défaut. IStartupFilter permet à un autre composant


d’appeler Use{Middleware} au nom de l’auteur de l’application.
Pour créer un pipeline de méthodes Configure . IStartupFilter.Configure peut
définir un middleware à exécuter avant ou après les middlewares qui sont ajoutés
par les bibliothèques.

IStartupFilter implémente Configure, qui reçoit et retourne un

Action<IApplicationBuilder> . IApplicationBuilder définit une classe pour configurer le

pipeline de requête d’une application. Pour plus d’informations, consultez Créer un


pipeline de middlewares avec IApplicationBuilder.

Chaque IStartupFilter peut ajouter un ou plusieurs middlewares au pipeline de


requête. Les filtres sont appelés dans l’ordre dans lequel ils ont été ajoutés au conteneur
de service. Les filtres peuvent ajouter l’intergiciel avant ou après la transmission du
contrôle au filtre suivant. Par conséquent, ils s’ajoutent au début ou à la fin du pipeline
de l’application.

L’exemple suivant montre comment inscrire un middleware auprès de IStartupFilter .


Le middleware RequestSetOptionsMiddleware définit une valeur d’options à partir d’un
paramètre de chaîne de requête :

C#

public class RequestSetOptionsMiddleware


{
private readonly RequestDelegate _next;

public RequestSetOptionsMiddleware(RequestDelegate next)


{
_next = next;
}

// Test with https://localhost:5001/Privacy/?option=Hello


public async Task Invoke(HttpContext httpContext)
{
var option = httpContext.Request.Query["option"];

if (!string.IsNullOrWhiteSpace(option))
{
httpContext.Items["option"] = WebUtility.HtmlEncode(option);
}

await _next(httpContext);
}
}

RequestSetOptionsMiddleware est configuré dans la classe

RequestSetOptionsStartupFilter :

C#

namespace WebStartup.Middleware;
// <snippet1>
public class RequestSetOptionsStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder>
next)
{
return builder =>
{
builder.UseMiddleware<RequestSetOptionsMiddleware>();
next(builder);
};
}
}
// </snippet1>

Le IStartupFilter est inscrit dans Program.cs :

C#

using WebStartup.Middleware;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddTransient<IStartupFilter,
RequestSetOptionsStartupFilter>();

var app = builder.Build();


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Quand un paramètre de chaîne de requête pour option est fourni, le middleware traite
l’affectation de valeur avant que le middleware ASP.NET Core n’affiche la réponse :

CSHTML

@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

<p> Append query string ?option=hello</p>


Option String: @HttpContext.Items["option"];

L’ordre d’exécution de l’intergiciel est défini par l’ordre des inscriptions


d’ IStartupFilter :

Plusieurs implémentations d’ IStartupFilter peuvent interagir avec les mêmes


objets. Si l’ordre est important, définissez l’ordre des inscriptions de leurs services
IStartupFilter pour qu’il corresponde à l’ordre dans lequel leurs intergiciels

doivent s’exécuter.

Les bibliothèques peuvent ajouter des intergiciels avec une ou plusieurs


implémentations IStartupFilter qui s’exécutent avant ou après l’autre intergiciel
d’application inscrit avec IStartupFilter . Pour appeler un middleware
IStartupFilter avant un middleware ajouté par le IStartupFilter d’une

bibliothèque :
Placez l’inscription du service avant l’ajout de la bibliothèque au conteneur de
service.
Pour les appels suivants, placez l’inscription du service après l’ajout de la
bibliothèque.

Remarque : Vous ne pouvez pas étendre l’application ASP.NET Core lorsque vous
remplacez Configure . Pour plus d’informations sur la mise en forme, consultez ce
problème GitHub .

Ajouter la configuration au démarrage à partir


d’un assembly externe
Une implémentation de IHostingStartup permet d’ajouter des améliorations à une
application au démarrage à partir d’un assembly externe, en dehors de le fichier
Program.cs de l’application. Pour plus d’informations, consultez Utiliser des assemblys

d’hébergement au démarrage dans ASP.NET Core.

Startup, ConfigureServices et Configure


Pour plus d’informations sur l’utilisation des méthodes ConfigureServices et Configure
avec le modèle d’hébergement minimal, consultez :

Utiliser Startup avec le nouveau modèle d'hébergement minimal


Version ASP.NET Core 5.0 de cet article :
Méthode ConfigureServices
Méthode Configure

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Injection de dépendances dans ASP.NET
Core
Article • 30/11/2023

Par Kirk Larkin , Steve Smith et Brandon Dahler

ASP.NET Core prend en charge le modèle de conception de logiciel avec injection de


dépendances (DI), une technique permettant d’obtenir une inversion de contrôle (IoC)
entre les classes et leurs dépendances.

Pour plus d’informations sur l’injection de dépendances dans les contrôleurs MVC,
consultez Injection de dépendances dans les contrôleurs dans ASP.NET Core.

Pour obtenir des informations sur l’utilisation de l’injection de dépendances dans des
applications autres que des applications web, consultez Injection de dépendances dans
.NET.

Pour plus d’informations sur l’injection de dépendances d’options, consultez Modèle


d’options dans ASP.NET Core.

Cette rubrique fournit des informations sur l’injection de dépendances dans ASP.NET
Core. La documentation principale sur l’utilisation de l’injection de dépendances est
incluse dans Injection de dépendances dans .NET.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Vue d’ensemble de l’injection de dépendances


Une dépendance est un objet dont dépend un autre objet. Examinez la classe
MyDependency suivante avec une méthode WriteMessage dont dépendent d’autres
classes :

C#

public class MyDependency


{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message:
{message}");
}
}
Une classe peut créer une instance de la classe MyDependency pour utiliser sa méthode
WriteMessage . Dans l’exemple suivant, la classe MyDependency est une dépendance de la

classe IndexModel :

C#

public class IndexModel : PageModel


{
private readonly MyDependency _dependency = new MyDependency();

public void OnGet()


{
_dependency.WriteMessage("IndexModel.OnGet");
}
}

La classe est créee et dépend directement de la classe MyDependency . Les dépendances


de code, comme dans l’exemple précédent, posent problème et doivent être évitées
pour les raisons suivantes :

Pour remplacer MyDependency par une autre implémentation, la classe IndexModel


doit être modifiée.
Si MyDependency possède des dépendances, elles doivent être configurées par la
classe IndexModel . Dans un grand projet comportant plusieurs classes dépendant
de MyDependency , le code de configuration est disséminé dans l’application.
Cette implémentation complique le test unitaire.

L’injection de dépendances résout ces problèmes via :

L’utilisation d’une interface ou classe de base pour extraire l’implémentation des


dépendances.
L’inscription de la dépendance dans un conteneur de service. ASP.NET Core fournit
un conteneur de service intégré, IServiceProvider. Les services sont généralement
inscrits dans le fichier Program.cs de l’application.
Injection du service dans le constructeur de la classe où il est utilisé. Le framework
prend la responsabilité de la création d’une instance de la dépendance et de sa
suppression lorsqu’elle n’est plus nécessaire.

Dans l’exemple d’application , l’interface IMyDependency définit la méthode


WriteMessage :

C#
public interface IMyDependency
{
void WriteMessage(string message);
}

Cette interface est implémentée par un type concret, MyDependency :

C#

public class MyDependency : IMyDependency


{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}

L’exemple d’application inscrit le service IMyDependency avec le type MyDependency


concret. La méthode AddScoped inscrit le service avec une durée de vie délimitée, la
durée de vie d’une requête unique. Les durées de vie du service sont décrites plus loin
dans cette rubrique.

C#

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

Dans l’exemple d’application, le service IMyDependency est demandé et utilisé pour


appeler la méthode WriteMessage :

C#

public class Index2Model : PageModel


{
private readonly IMyDependency _myDependency;

public Index2Model(IMyDependency myDependency)


{
_myDependency = myDependency;
}

public void OnGet()


{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}

En utilisant le modèle d’injection de dépendances, le contrôleur ou la page Razor :

N’utilise pas le type MyDependency concret, uniquement l’interface IMyDependency


qu’il implémente. Cela facilite la modification de l’implémentation sans modifier le
contrôleur ou la page Razor.
Ne crée pas d’instance de MyDependency ; elle est créée par le conteneur d’injection
de dépendances.

L’implémentation de l’interface IMyDependency peut être améliorée à l’aide de l’API de


journalisation intégrée :

C#

public class MyDependency2 : IMyDependency


{
private readonly ILogger<MyDependency2> _logger;

public MyDependency2(ILogger<MyDependency2> logger)


{
_logger = logger;
}

public void WriteMessage(string message)


{
_logger.LogInformation( $"MyDependency2.WriteMessage Message:
{message}");
}
}

Le fichier Program.cs mis à jour inscrit la nouvelle implémentation IMyDependency :

C#

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();

MyDependency2 dépend de ILogger<TCategoryName>, qu’il demande dans le

constructeur. ILogger<TCategoryName> est un service fourni par l’infrastructure.

Il n’est pas rare que l’injection de dépendances soit utilisée de manière chaînée. Dans ce
cas, chaque dépendance demandée demande à son tour ses propres dépendances. Le
conteneur résout les dépendances dans le graphique et retourne le service entièrement
résolu. L’ensemble collectif de dépendances qui doivent être résolues est généralement
appelé arborescence des dépendances, graphique de dépendance ou graphique d’objet.

Le conteneur résout ILogger<TCategoryName> en tirant parti de types ouverts


(génériques), ce qui élimine la nécessité d’inscrire chaque type construit (générique).

Dans la terminologie d’injection de dépendances, un service :

Est généralement un objet qui fournit un service à d’autres objets, tels que le
service IMyDependency .
N’est pas lié à un service web, bien que le service puisse utiliser un service web.

L’infrastructure fournit un système de journalisation robuste. Les implémentations


IMyDependency indiquées dans les exemples précédents ont été écrites pour démontrer

l’injection de dépendances de base, et non pour implémenter la journalisation. La


plupart des applications ne doivent pas avoir besoin d’écrire des enregistreurs
d’événements. Le code suivant illustre l’utilisation de la journalisation par défaut, qui ne
nécessite l’inscription d’aucun service :

C#

public class AboutModel : PageModel


{
private readonly ILogger _logger;

public AboutModel(ILogger<AboutModel> logger)


{
_logger = logger;
}

public string Message { get; set; } = string.Empty;

public void OnGet()


{
Message = $"About page visited at
{DateTime.UtcNow.ToLongTimeString()}";
_logger.LogInformation(Message);
}
}

En utilisant le code précédent, il n’est pas nécessaire de mettre à jour Program.cs , car la
journalisation est fournie par l’infrastructure.

Inscrire des groupes de services avec des


méthodes d’extension
L’infrastructure ASP.NET Core utilise une convention pour l’inscription d’un groupe de
services associés. La convention consiste à utiliser une seule méthode d’extension
Add{GROUP_NAME} pour inscrire tous les services requis par une fonctionnalité

d’infrastructure. Par exemple, la méthode d’extension AddControllers inscrit les services


requis pour les contrôleurs MVC.

Le code suivant est généré par le modèle des pages Razor à l’aide de comptes
d’utilisateur individuels et montre comment ajouter des services supplémentaires au
conteneur à l’aide des méthodes d’extension AddDbContext et AddDefaultIdentity :

C#

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Examinons les éléments suivants permettant d’inscrire les services et de configurer les
options :

C#
using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Les groupes d’inscriptions associés peuvent être déplacés vers une méthode d’extension
pour inscrire les services. Les services de configuration sont par exemple ajoutés à la
classe suivante :

C#

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));

return services;
}

public static IServiceCollection AddMyDependencyGroup(


this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();

return services;
}
}
}

Les services qui restent sont inscrits dans une classe similaire. Le code suivant utilise les
nouvelles méthodes d’extension pour inscrire les services :

C#

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Remarque : chaque méthode d’extension services.Add{GROUP_NAME} ajoute des services


et les configure éventuellement. Par exemple, AddControllersWithViews ajoute les
services nécessaires à MVC avec vues et AddRazorPages ajoute les services nécessaires
aux pages Razor.

Durées de service
Consultez Durées de service dans Injection de dépendances dans .NET

Pour utiliser des services délimités dans un intergiciel, utilisez l’une des approches
suivantes :

Injectez le service dans la méthode Invoke ou InvokeAsync de l’intergiciel.


L’utilisation de l’injection de constructeur lève une exception de runtime, car elle
force le service délimité à se comporter comme un singleton. L’exemple utilisé
dans la section Options de durée de vie et d’inscription illustre l’approche
InvokeAsync .

Utilisez un intergiciel basé sur une fabrique. L’intergiciel inscrit à l’aide de cette
approche est activé par demande client (connexion), ce qui permet d’injecter des
services délimités dans le constructeur de l’intergiciel.

Pour plus d’informations, consultez Écrire un intergiciel ASP.NET Core personnalisé.


Méthodes d’inscription du service
Consultez Méthodes d’inscription de service dans Injection de dépendances dans .NET

Il est courant d’utiliser plusieurs implémentations lors d’une simulation de types à des
fins de test.

L’inscription d’un service avec uniquement un type d’implémentation revient à inscrire


ce service avec le même type d’implémentation et de service. C’est pourquoi plusieurs
implémentations d’un service ne peuvent pas être inscrites à l’aide des méthodes qui
n’acceptent pas de type de service explicite. Ces méthodes peuvent inscrire plusieurs
instances d’un service, mais elles auront toutes le même type d’implémentation.

L’une des méthodes d’inscription de service ci-dessus peut être utilisée pour inscrire
plusieurs instances de service du même type de service. Dans l’exemple suivant,
AddSingleton est appelé deux fois avec IMyDependency comme type de service. Le

deuxième appel à AddSingleton remplace le précédent lorsqu’il est résolu en tant que
IMyDependency et s’ajoute au précédent lorsque plusieurs services sont résolus via

IEnumerable<IMyDependency> . Les services apparaissent dans l’ordre dans lequel ils ont

été inscrits lorsqu’ils sont résolus via IEnumerable<{SERVICE}> .

C#

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService


{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);

var dependencyArray = myDependencies.ToArray();


Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}

Services à clé
Les services à clé désigne un mécanisme d’inscription et de récupération des services
d’injection de dépendances (DI) à l’aide de clés. Un service est associé à une clé en
appelant AddKeyedSingleton (ou AddKeyedScoped ou AddKeyedTransient ) pour l’inscrire.
Accédez à un service inscrit en spécifiant la clé avec l’attribut [FromKeyedServices]. Le
code suivant montre comment utiliser les services à clé :

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) =>


bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>

smallCache.Get("date"));

app.MapControllers();

app.Run();

public interface ICache


{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache


{
public object Get(string key) => $"Resolving {key} from small cache.";
}

[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big-cache")]
public ActionResult<object> GetOk([FromKeyedServices("big")] ICache
cache)
{
return cache.Get("data-mvc");
}
}

public class MyHub : Hub


{
public void Method([FromKeyedServices("small")] ICache cache)
{
Console.WriteLine(cache.Get("signalr"));
}
}

Comportement d’injection de constructeurs


Consultez Comportement d’injection de constructeurs dans Injection de dépendances
dans .NET

Contextes Entity Framework


Par défaut, les contextes Entity Framework sont ajoutés au conteneur de service en
utilisant la durée de vie limitée, car la portée des opérations de base de données
d’application web est normalement limitée à la demande du client. Pour utiliser une
durée de vie différente, spécifiez la durée de vie à l’aide d’une surcharge AddDbContext.
Les services d’une durée de vie donnée ne doivent pas utiliser un contexte de base de
données dont la durée de vie est plus courte que celle du service.

Options de durée de vie et d’inscription


Pour illustrer la différence entre les options de durée de vie et d’inscription de service,
considérez les interfaces suivantes qui représentent une tâche en tant qu’opération avec
un identificateur, OperationId . Selon la façon dont la durée de vie d’un service
d’opérations est configurée pour les interfaces suivantes, le conteneur fournit la même
instance ou une instance différente du service lorsqu’une classe le demande :

C#

public interface IOperation


{
string OperationId { get; }
}

public interface IOperationTransient : IOperation { }


public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

La classe Operation suivante implémente toutes les interfaces précédentes. Le


constructeur Operation génère un GUID et stocke les 4 derniers caractères dans la
propriété OperationId :
C#

public class Operation : IOperationTransient, IOperationScoped,


IOperationSingleton
{
public Operation()
{
OperationId = Guid.NewGuid().ToString()[^4..];
}

public string OperationId { get; }


}

Le code suivant crée plusieurs inscriptions de la classe Operation en fonction des durées
de vie nommées :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

L’exemple d’application illustre les durées de vie des objets au sein et entre des
demandes. IndexModel et l’intergiciel demandent chaque type de IOperation et
consignent OperationId pour chacun :
C#

public class IndexModel : PageModel


{
private readonly ILogger _logger;
private readonly IOperationTransient _transientOperation;
private readonly IOperationSingleton _singletonOperation;
private readonly IOperationScoped _scopedOperation;

public IndexModel(ILogger<IndexModel> logger,


IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation)
{
_logger = logger;
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}

public void OnGet()


{
_logger.LogInformation("Transient: " +
_transientOperation.OperationId);
_logger.LogInformation("Scoped: " +
_scopedOperation.OperationId);
_logger.LogInformation("Singleton: " +
_singletonOperation.OperationId);
}
}

Comme IndexModel , l’intergiciel résout les mêmes services :

C#

public class MyMiddleware


{
private readonly RequestDelegate _next;
private readonly ILogger _logger;

private readonly IOperationSingleton _singletonOperation;

public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,


IOperationSingleton singletonOperation)
{
_logger = logger;
_singletonOperation = singletonOperation;
_next = next;
}

public async Task InvokeAsync(HttpContext context,


IOperationTransient transientOperation, IOperationScoped
scopedOperation)
{
_logger.LogInformation("Transient: " +
transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " +
_singletonOperation.OperationId);

await _next(context);
}
}

public static class MyMiddlewareExtensions


{
public static IApplicationBuilder UseMyMiddleware(this
IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}

Les services délimités et temporaires doivent être résolus dans la méthode InvokeAsync :

C#

public async Task InvokeAsync(HttpContext context,


IOperationTransient transientOperation, IOperationScoped
scopedOperation)
{
_logger.LogInformation("Transient: " + transientOperation.OperationId);
_logger.LogInformation("Scoped: " + scopedOperation.OperationId);
_logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

await _next(context);
}

La sortie de l’enregistreur d’événements montre :

Les objets Transient sont toujours différents. La valeur OperationId temporaire est
différente dans IndexModel et dans l’intergiciel.
Les objets délimités sont les mêmes pour une demande donnée, mais diffèrent
pour chaque nouvelle demande.
Les objets singleton sont identiques pour chaque demande.

Pour réduire la sortie de journalisation, définissez « Logging:LogLevel:Microsoft:Error »


dans le fichier appsettings.Development.json :

JSON
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}

Résoudre un service au démarrage de


l’application
Le code suivant montre comment résoudre un service délimité pendant une durée
limitée quand l’application démarre :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())


{
var services = serviceScope.ServiceProvider;

var myDependency = services.GetRequiredService<IMyDependency>();


myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Validation de l’étendue
Consultez Comportement d’injection de constructeurs dans Injection de dépendances
dans .NET

Pour plus d’informations, consultez Validation de l’étendue.


Services de requête
Les services et leurs dépendances au sein d’une demande ASP.NET Core sont exposés
via HttpContext.RequestServices.

L’infrastructure crée une étendue par demande et RequestServices expose le


fournisseur de services délimité. Tous les services délimités sont valides tant que la
demande est active.

7 Notes

Préférez demander des dépendances en tant que paramètres de constructeur


plutôt que résoudre les services à partir de RequestServices . La demande de
dépendances en tant que paramètres de constructeur génère des classes plus
faciles à tester.

Conception de services pour l’injection de


dépendances
Lors de la conception de services pour l’injection de dépendances :

Évitez les classes et les membres statiques avec état. Évitez de créer un état global
en concevant des applications pour utiliser des services singleton à la place.
Éviter une instanciation directe de classes dépendantes au sein de services.
L’instanciation directe associe le code à une implémentation particulière.
Limitez la taille des services, faites en sorte qu’elles soient bien factorisées et
facilement testées.

Si une classe possède de nombreuses dépendances injectées, il peut s’agir d’un signe
que la classe a trop de responsabilités et viole le principe de responsabilité unique (SRP).
Essayez de refactoriser la classe en déplaçant certaines de ses responsabilités dans de
nouvelles classes. N’oubliez pas que les classes du modèle de page Razor Pages et les
classes du contrôleur MVC doivent se concentrer sur les problèmes d’interface
utilisateur.

Suppression des services


Le conteneur appelle Dispose pour les types IDisposable qu’il crée. Les services résolus à
partir du conteneur ne doivent jamais être supprimés par le développeur. Si un type ou
une fabrique est inscrit en tant que singleton, le conteneur supprime automatiquement
le singleton.

Dans l’exemple suivant, les services sont créés par le conteneur de service et supprimés
automatiquement : dependency-
injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs

C#

public class Service1 : IDisposable


{
private bool _disposed;

public void Write(string message)


{
Console.WriteLine($"Service1: {message}");
}

public void Dispose()


{
if (_disposed)
return;

Console.WriteLine("Service1.Dispose");
_disposed = true;
}
}

public class Service2 : IDisposable


{
private bool _disposed;

public void Write(string message)


{
Console.WriteLine($"Service2: {message}");
}

public void Dispose()


{
if (_disposed)
return;

Console.WriteLine("Service2.Dispose");
_disposed = true;
}
}

public interface IService3


{
public void Write(string message);
}

public class Service3 : IService3, IDisposable


{
private bool _disposed;

public Service3(string myKey)


{
MyKey = myKey;
}

public string MyKey { get; }

public void Write(string message)


{
Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
}

public void Dispose()


{
if (_disposed)
return;

Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}

C#

using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var myKey = builder.Configuration["MyKey"];


builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();

C#

public class IndexModel : PageModel


{
private readonly Service1 _service1;
private readonly Service2 _service2;
private readonly IService3 _service3;

public IndexModel(Service1 service1, Service2 service2, IService3


service3)
{
_service1 = service1;
_service2 = service2;
_service3 = service3;
}

public void OnGet()


{
_service1.Write("IndexModel.OnGet");
_service2.Write("IndexModel.OnGet");
_service3.Write("IndexModel.OnGet");
}
}

La console de débogage affiche la sortie suivante après chaque actualisation de la page


Index :

Console

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose

Services non créés par le conteneur de services


Prenez le code suivant :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

Dans le code précédent :

Les instances de service ne sont pas créées par le conteneur de service.


L’infrastructure ne supprime pas automatiquement les services.
Le développeur est responsable de la suppression des services.

Conseils sur l’interface IDisposable pour les instances


temporaires et partagées
Consultez Conseils sur l’interface IDisposable pour les instances temporaires et
partagées dans Injection de dépendances dans .NET

Remplacement de conteneur de services par


défaut
Consultez Remplacement de conteneur de services par défaut dans Injection de
dépendances dans .NET

Recommandations
Consultez Recommandations dans Injection de dépendances dans .NET

Évitez d’utiliser le modèle de localisateur de service. Par exemple, n’appelez pas


GetService pour obtenir une instance de service si vous pouvez utiliser l’injection
de dépendance à la place :

Incorrect :

Correct :

C#

public class MyClass


{
private readonly IOptionsMonitor<MyOptions> _optionsMonitor;

public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)


{
_optionsMonitor = optionsMonitor;
}
public void MyMethod()
{
var option = _optionsMonitor.CurrentValue.Option;

...
}
}

Une autre variante du localisateur de service à éviter est l’injection d’une fabrique
qui résout les dépendances au moment de l’exécution. Ces deux pratiques
combinent des stratégies Inversion de contrôle.

Évitez l’accès statique à HttpContext (par exemple,


IHttpContextAccessor.HttpContext).

L’injection de dépendance constitue une alternative aux modèles d’accès aux objets
statiques/globaux. Il est possible que vous ne bénéficiez pas des avantages de l’injection
de dépendances si vous la combinez avec l’accès aux objets statiques.

Modèles recommandés pour la multilocation


dans l’injection de dépendances
Orchard Core est une infrastructure d’application permettant de créer des applications
modulaires multilocataires sur ASP.NET Core. Pour plus d’informations, consultez la
documentation Orchard Core .

Consultez les exemples Orchard Core pour obtenir des exemples de création
d’applications modulaires et multilocataires utilisant simplement l’infrastructure Orchard
Core sans aucune de ses fonctionnalités spécifiques à CMS.

Services fournis par le framework


Program.cs inscrit les services utilisés par l’application, notamment les fonctionnalités de

plateforme comme Entity Framework Core et ASP.NET Core MVC. Au départ,


IServiceCollection fournie à Program.cs a les services définis par l’infrastructure en

fonction de la manière dont l’hôte a été configuré. Pour les applications basées sur les
modèles ASP.NET Core, l’infrastructure inscrit plus de 250 services.

Le tableau suivant répertorie un petit exemple de ces services inscrits dans


l’infrastructure :
Type de service Durée de vie

Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Temporaire

IHostApplicationLifetime Singleton

IWebHostEnvironment Singleton

Microsoft.AspNetCore.Hosting.IStartup Singleton

Microsoft.AspNetCore.Hosting.IStartupFilter Temporaire

Microsoft.AspNetCore.Hosting.Server.IServer Singleton

Microsoft.AspNetCore.Http.IHttpContextFactory Temporaire

Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton

Microsoft.Extensions.Logging.ILoggerFactory Singleton

Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton

Microsoft.Extensions.Options.IConfigureOptions<TOptions> Temporaire

Microsoft.Extensions.Options.IOptions<TOptions> Singleton

System.Diagnostics.DiagnosticSource Singleton

System.Diagnostics.DiagnosticListener Singleton

Ressources supplémentaires
Injection de dépendances dans les vues dans ASP.NET Core
Injection de dépendances dans les contrôleurs dans ASP.NET Core
Injection de dépendances dans les gestionnaires d’exigences dans ASP.NET Core
Injection de dépendances ASP.NET Core Blazor
NDC Conference Patterns for DI app development
Démarrage d’une application dans ASP.NET Core
Activation d’un intergiciel (middleware) basé sur une fabrique dans ASP.NET Core
INJECTION DE DÉPENDANCES ASP.NET CORE : QU’EST-CE QUE
ISERVICECOLLECTION ?
Quatre façons de supprimer les IDisposables dans ASP.NET Core
Écrire un code clair dans ASP.NET Core avec l’injection de dépendance (MSDN)
Principe des dépendances explicites
Conteneurs d’inversion de contrôle et modèle d’injection de dépendances (Martin
Fowler)
Comment inscrire un service avec plusieurs interfaces dans l’injection de
dépendance ASP.NET Core

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Prise en charge ASP.NET Core pour l’AOT
native
Article • 12/01/2024
) Contenu assisté par l'IA. Cet article a été en partie créé à l’aide d’une IA. Un auteur a examiné et
si besoin révisé le contenu. En savoir plus

ASP.NET Core 8.0 introduit la prise en charge pour l’avance de temps (AOT, ahead-of-
time) native .NET.

Pourquoi utiliser l’AOT native avec ASP.NET


Core
La publication et le déploiement d’une application AOT native offrent les avantages
suivants :

Empreinte disque réduite : lors de la publication à l’aide de l’AOT native, un fichier


exécutable unique est produit qui contient uniquement le code des dépendances
externes utilisé pour prendre en charge le programme. Une taille d’exécutable
réduite peut entraîner :
Des images de conteneur plus petites, par exemple dans les scénarios de
déploiement conteneurisé.
Un temps de déploiement réduit à partir d’images plus petites.
Temps de démarrage réduit : les applications AOT natives peuvent présenter des
temps de démarrage réduits, ce qui signifie
Que l’application est prête à traiter plus rapidement les requêtes.
Un déploiement amélioré où les orchestrateurs de conteneurs doivent gérer la
transition d’une version de l’application à une autre.
Demande en mémoire réduite : les applications AOT natives peuvent avoir des
demandes en mémoire réduites en fonction du travail effectué par l’application.
Une consommation réduite de mémoire peut entraîner une plus grande densité de
déploiement et une meilleure scalabilité.

L’application modèle a été exécutée dans notre laboratoire d’évaluation pour comparer
les performances d’une application publiée AOT, d’une application runtime découpée et
d’une application runtime non découpée. Le graphique suivant montre les résultats de
l’évaluation :
Le graphique précédent montre que l’AOT native présente des valeurs plus faibles de
taille d’application, d’utilisation de la mémoire et du temps de démarrage.

Compatibilité ASP.NET Core et d’AOT native


Toutes les fonctionnalités d’ASP.NET Core ne sont pas actuellement compatibles avec
l’AOT native. Le tableau suivant récapitule la compatibilité des fonctionnalités ASP.NET
Core avec l’AOT native :

ノ Agrandir le tableau

Fonctionnalité Prise en charge Prise en charge Non prise en


intégrale partielle charge

gRPC ✔️

API minimales ✔️

MVC ❌

Blazor Server ❌

SignalR ❌

Authentification JWT ✔️

Autre authentification ❌

CORS ✔️
Fonctionnalité Prise en charge Prise en charge Non prise en
intégrale partielle charge

HealthChecks ✔️

HttpLogging ✔️

Localisation ✔️

OutputCaching ✔️

RateLimiting ✔️

RequestDecompression ✔️

ResponseCaching ✔️

ResponseCompression ✔️

Réécrire ✔️

Session ❌

Spa ❌

StaticFiles ✔️

WebSockets ✔️

Pour plus d’informations sur les limitations, consultez :

Limitations du déploiement AOT natif


Présentation des avertissements AOT
Incompatibilités connues de la suppression des espaces
Introduction aux avertissements de découpage
Problème GitHub dotnet/core #8288

Il est important de tester soigneusement une application lors du passage à un modèle


de déploiement AOT natif. L’application déployée AOT doit être testée pour vérifier que
la fonctionnalité n’a pas changé à partir de l’application compilée juste-à-temps (JIT) et
non découpée. Lors de la génération de l’application, passez en revue et corrigez les
avertissements AOT. Une application qui génère des avertissements AOT pendant la
publication peut ne pas fonctionner correctement. Si aucun avertissement AOT n’est
émis au moment de la publication, l’application AOT publiée doit fonctionner de la
même façon que l’application non tronquée compilée par JIT.

Publication AOT native


L’AOT native est activée avec la propriété MSBuild PublishAot . L’exemple suivant
montre comment activer l’AOT native dans un fichier projet :

XML

<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>

Ce paramètre permet la compilation AOT native pendant la publication et l’analyse


dynamique de l’utilisation du code pendant la génération et la modification. Un projet
qui utilise la publication AOT native utilise la compilation JIT lors de l’exécution locale.
Une application AOT présente les différences suivantes par rapport à une application
compilée juste-à-temps (JIT) :

Certaines fonctionnalités non compatibles avec l’AOT native sont désactivées et


lèvent des exceptions au moment de l’exécution.
Un analyseur de source est activé pour mettre en surbrillance le code qui n’est pas
compatible avec l’AOT native. Au moment de la publication, la compatibilité de
l’application entière, y compris les packages NuGet, est à nouveau analysée.

L’analyse d’AOT native inclut tout le code de l’application et les bibliothèques dont
dépend l’application. Passez en revue les avertissements de l’AOT native et prenez des
mesures correctives. Il est judicieux de publier fréquemment les applications pour
détecter les problèmes au début du cycle de vie du développement.

Dans .NET 8, l’AOT native est prise en charge par les types d’applications ASP.NET Core
suivants :

API minimales : pour plus d’informations, consultez la section Modèle d’API web
(AOT native) plus loin dans cet article.
gRPC : pour plus d’informations, consultez gRPC et AOT native.
Services Worker : pour plus d’informations, consultez AOT dans les modèles de
service Worker.

Modèle d’API web (AOT native)


Le modèle API web ASP.NET Core (AOT native) (nom court webapiaot ) crée un projet
avec AOT activé. Le modèle diffère du modèle de projet d’API web de la manière
suivante :

Utilise uniquement des API minimales, car MVC n’est pas encore compatible avec
l’AOT native.
Utilise l’API CreateSlimBuilder() pour garantir que seules les fonctionnalités
essentielles sont activées par défaut, ce qui réduit la taille déployée de
l’application.
Est configuré pour écouter uniquement sur HTTP, car le trafic HTTPS est
généralement géré par un service d’entrée dans les déploiements natifs cloud.
N’inclut pas de profil de lancement pour l’exécution sous IIS ou IIS Express.
Crée un fichier .http configuré avec des exemples de requêtes HTTP qui peuvent
être envoyées aux points de terminaison de l’application.
Inclut un exemple d’API Todo au lieu de l’exemple de prévision météorologique.
Ajoute PublishAot au fichier projet, comme indiqué précédemment dans cet
article.
Active les générateurs de source du sérialiseur JSON. Le générateur de source est
utilisé pour générer du code de sérialisation au moment de la génération, ce qui
est requis pour la compilation AOT native.

Modifications nécessaires à la prise en charge de la


génération de source
L’exemple suivant montre le code ajouté au fichier Program.cs pour prendre en charge
la génération de source de sérialisation JSON :

diff

using MyFirstAotWebApi;
+using System.Text.Json.Serialization;

-var builder = WebApplication.CreateBuilder();


+var builder = WebApplication.CreateSlimBuilder(args);

+builder.Services.ConfigureHttpJsonOptions(options =>
+{
+ options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
+});

var app = builder.Build();

var sampleTodos = TodoGenerator.GenerateTodos().ToArray();

var todosApi = app.MapGroup("/todos");


todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
? Results.Ok(todo)
: Results.NotFound());

app.Run();
+[JsonSerializable(typeof(Todo[]))]
+internal partial class AppJsonSerializerContext : JsonSerializerContext
+{
+
+}

Sans ce code ajouté, System.Text.Json utilise la réflexion pour sérialiser et désérialiser


JSON. La réflexion n’est pas prise en charge dans l’AOT native.

Pour en savoir plus, consultez :

Combiner des générateurs de sources


TypeInfoResolverChain

Modifications apportées à launchSettings.json


Le fichier launchSettings.json créé par le modèle API web (AOT native) ne contient pas
la section iisSettings et le profil IIS Express :

diff

{
"$schema": "http://json.schemastore.org/launchsettings.json",
- "iisSettings": {
- "windowsAuthentication": false,
- "anonymousAuthentication": true,
- "iisExpress": {
- "applicationUrl": "http://localhost:11152",
- "sslPort": 0
- }
- },
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "todos",
"applicationUrl": "http://localhost:5102",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
- "IIS Express": {
- "commandName": "IISExpress",
- "launchBrowser": true,
- "launchUrl": "todos",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- }
}
}

Méthode CreateSlimBuilder
Le modèle utilise la méthode CreateSlimBuilder() au lieu de la méthode CreateBuilder().

C#

using System.Text.Json.Serialization;
using MyFirstAotWebApi;

var builder = WebApplication.CreateSlimBuilder(args);


builder.Logging.AddConsole();

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

var sampleTodos = TodoGenerator.GenerateTodos().ToArray();

var todosApi = app.MapGroup("/todos");


todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
? Results.Ok(todo)
: Results.NotFound());

app.Run();

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

La méthode CreateSlimBuilder initialise le WebApplicationBuilder avec les


fonctionnalités ASP.NET Core minimales nécessaires à l’exécution d’une application.

Comme indiqué précédemment, la méthode CreateSlimBuilder n’inclut pas la prise en


charge du protocole HTTPS ou HTTP/3. Ces protocoles ne sont généralement pas requis
pour les applications qui s’exécutent derrière un proxy de terminaison TLS. Par exemple,
consultez Terminaison TLS et TLS de bout en bout avec Application Gateway. HTTPS
peut être activé en appelant builder.WebHost.UseKestrelHttpsConfiguration HTTP/3
peut être activé en appelant builder.WebHost.UseQuic.

Comparaison de CreateSlimBuilder et de CreateBuilder


La méthode CreateSlimBuilder ne prend pas en charge les fonctionnalités suivantes
prises en charge par la méthode CreateBuilder :

Assemblys d’hébergement au démarrage


UseStartup
Les fournisseurs de journalisation suivants :
Windows EventLog
Déboguer
Source de l'événement
Fonctionnalités d’hébergement web :
UseStaticWebAssets
Intégration IIS
Configuration Kestrel
Points de terminaison HTTPS dans Kestrel
Quic (HTTP/3)
Contraintes regex et alpha utilisées dans le routage

La méthode CreateSlimBuilder comprend les fonctionnalités suivantes nécessaires à


une expérience de développement efficace :

JSConfiguration du fichier ON pour appsettings.json et appsettings.


{EnvironmentName}.json .

Configuration des secrets utilisateur.


Journalisation de la console.
Configuration de la journalisation.

Pour un générateur qui omet les fonctionnalités précédentes, consultez la méthode


CreateEmptyBuilder.

L’inclusion de fonctionnalités minimales présente des avantages pour le découpage


ainsi que pour l’AOT. Pour plus d’informations, consultez Découper les déploiements
autonomes et les exécutables.

Pour plus d’informations, consultez Comparaison de WebApplication.CreateBuilder à


CreateSlimBuilder

Générateurs de source
Étant donné que le code inutilisé est découpé lors de la publication pour l’AOT native,
l’application ne peut pas utiliser la réflexion illimitée au moment de l’exécution. Les
générateurs de sources sont utilisés pour produire du code afin d’éviter la réflexion.
Dans certains cas, les générateurs de sources produisent du code optimisé pour l’AOT,
même lorsqu’un générateur n’est pas requis.

Pour afficher le code source généré, ajoutez la propriété EmitCompilerGeneratedFiles au


fichier .csproj d’une application, comme illustré dans l’exemple suivant :

XML

<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<!-- Other properties omitted for brevity -->
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

</Project>

Exécutez la commande dotnet build pour afficher le code généré. La sortie inclut un
répertoire obj/Debug/net8.0/generated/ qui contient tous les fichiers générés pour le
projet.

La commande dotnet publish compile également les fichiers sources et génère des
fichiers compilés. En outre, dotnet publish transmet les assemblys générés à un
compilateur IL natif. Le compilateur IL produit l’exécutable natif. L’exécutable natif
contient le code de l’ordinateur natif.

Bibliothèques et AOT native


La plupart des bibliothèques populaires utilisées dans les projets ASP.NET Core
présentent actuellement des problèmes de compatibilité lorsqu’elles sont utilisées dans
un projet ciblant l’AOT native, par exemple :

Utilisation de la réflexion pour inspecter et découvrir des types.


Chargement conditionnel de bibliothèques au moment de l’exécution.
Génération de code à la volée pour implémenter des fonctionnalités.

Les bibliothèques utilisant ces fonctionnalités dynamiques doivent être mises à jour
pour fonctionner avec l’AOT native. Elles peuvent être mises à jour à l’aide d’outils tels
que les générateurs de sources Roslyn.
Les auteurs de bibliothèque qui espèrent prendre en charge l’AOT native sont
encouragés à :

Découvrir les exigences de compatibilité d’AOT native.


Préparez la bibliothèque pour le découpage.

API minimales et charges utiles JSON


L’infrastructure d’API minimales est optimisée pour recevoir et retourner des charges
utiles JSON à l’aide de System.Text.Json. System.Text.Json :

Impose des exigences de compatibilité pour JSON et l’AOT native.


Nécessite l’utilisation du générateur source System.Text.Json.

Tous les types transmis dans le corps HTTP ou retournés par les délégués de requête
dans les applications API minimales doivent être configurés sur un JsonSerializerContext
inscrit via l’injection de dépendances d’ASP.NET Core :

C#

using System.Text.Json.Serialization;
using MyFirstAotWebApi;

var builder = WebApplication.CreateSlimBuilder(args);


builder.Logging.AddConsole();

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

var sampleTodos = TodoGenerator.GenerateTodos().ToArray();

var todosApi = app.MapGroup("/todos");


todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
? Results.Ok(todo)
: Results.NotFound());

app.Run();
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

Dans le code en surbrillance précédent :

Le contexte de sérialiseur JSON est inscrit auprès du conteneur DI. Pour plus
d'informations, voir :
Combiner des générateurs de sources
TypeInfoResolverChain
Le JsonSerializerContext personnalisé est annoté avec l’attribut [JsonSerializable]
pour activer le code de sérialiseur JSON généré par la source pour le type ToDo .

Paramètre sur le délégué qui n’est pas lié au corps et n’a pas besoin d’être sérialisable.
Par exemple, un paramètre de chaîne de requête qui est un type d’objet enrichi et
implémente IParsable<T> .

C#

public class Todo


{
public int Id { get; set; }
public string? Title { get; set; }
public DateOnly? DueBy { get; set; }
public bool IsComplete { get; set; }
}

static class TodoGenerator


{
private static readonly (string[] Prefixes, string[] Suffixes)[] _parts
= new[]
{
(new[] { "Walk the", "Feed the" }, new[] { "dog", "cat", "goat"
}),
(new[] { "Do the", "Put away the" }, new[] { "groceries",
"dishes", "laundry" }),
(new[] { "Clean the" }, new[] { "bathroom", "pool", "blinds",
"car" })
};
// Remaining code omitted for brevity.

Problèmes connus
Consultez ce problème GitHub pour signaler ou examiner les problèmes liés à la prise
en charge de l’AOT native dans ASP.NET Core.
Voir aussi
Tutoriel : Publier une application ASP.NET Core à l'aide d'AOT natif
Déploiement de Native AOT
Optimiser les déploiements AOT
Générateur de source de liaison de configuration
À l’aide du générateur source du classeur de configuration
Modèle de compilation AOT de l’API minimale
Comparaison de WebApplication.CreateBuilder à CreateSlimBuilder
Exploration du nouveau générateur de source d’API minimal
Remplacement des appels de méthode par des intercepteurs
Derrière [LogProperties] et le nouveau générateur de source de journalisation des
données de télémétrie

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Tutoriel : publier une application
ASP.NET Core à l'aide de l’AOT native
Article • 30/11/2023
) Contenu assisté par l'IA. Cet article a été en partie créé à l’aide d’une IA. Un auteur a examiné et
si besoin révisé le contenu. En savoir plus

ASP.NET Core 8.0 introduit la prise en charge pour l’avance de temps (AOT, ahead-of-
time) native .NET.

7 Notes

La fonctionnalité AOT native est actuellement en préversion.


Dans .NET 8, toutes les fonctionnalités ASP.NET Core ne sont pas compatibles
avec l’AOT native.
Les onglets sont fournis pour les instructions CLI .NET Core et Visual
Studio :
Visual Studio est un prérequis même si l’onglet CLI est sélectionné.
L’interface CLI doit être utilisée pour publier même si l’onglet Visual Studio
est sélectionné.

Prérequis
CLI .NET Core

SDK .NET 8.0

Sur Linux, consultez Conditions préalables pour le déploiement AOT natif.

Préversion Visual Studio 2022 avec la charge de travail Développement


Desktop avec C++ installée.
7 Notes

La préversion de Visual Studio 2022 est requise, car l’AOT native nécessite
link.exe et les bibliothèques de runtime statique Visual C++. Il n’est pas prévu
de prendre en charge l’AOT native sans Visual Studio.

Créer une application web avec l’AOT native


Créer une application API ASP.NET Core configurée pour fonctionner avec l’AOT native :

CLI .NET Core

Exécutez les commandes suivantes :

CLI .NET

dotnet new webapiaot -o MyFirstAotWebApi && cd MyFirstAotWebApi

Une sortie similaire à l’exemple suivant s’affiche à l’écran :

Sortie

The template "ASP.NET Core Web API (native AOT)" was created
successfully.

Processing post-creation actions...


Restoring C:\Code\Demos\MyFirstAotWebApi\MyFirstAotWebApi.csproj:
Determining projects to restore...
Restored C:\Code\Demos\MyFirstAotWebApi\MyFirstAotWebApi.csproj (in
302 ms).
Restore succeeded.

Publier l’application AOT native


Vérifiez que l’application peut être publiée à l’aide de l’AOT native :

CLI .NET Core

CLI .NET

dotnet publish

La commande dotnet publish :

Compile les fichiers sources.


Génère des fichiers de code source compilés.
Transmet les assemblys générés à un compilateur IL natif. Le compilateur IL produit
l’exécutable natif. L’exécutable natif contient le code de l’ordinateur natif.

Une sortie similaire à l’exemple suivant s’affiche à l’écran :

Sortie

MSBuild version 17.<version> for .NET


Determining projects to restore...
Restored C:\Code\Demos\MyFirstAotWebApi\MyFirstAotWebApi.csproj (in 241
ms).
C:\Code\dotnet\aspnetcore\.dotnet\sdk\8.0.
<version>\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.RuntimeIde
ntifierInference.targets(287,5): message NETSDK1057: You are using a preview
version of .NET. See: https://aka.ms/dotne
t-support-policy [C:\Code\Demos\MyFirstAotWebApi\MyFirstAotWebApi.csproj]
MyFirstAotWebApi -> C:\Code\Demos\MyFirstAotWebApi\bin\Release\net8.0\win-
x64\MyFirstAotWebApi.dll
Generating native code
MyFirstAotWebApi -> C:\Code\Demos\MyFirstAotWebApi\bin\Release\net8.0\win-
x64\publish\

La sortie peut différer de l’exemple précédent en fonction de la version de .NET 8


utilisée, du répertoire utilisé et d’autres facteurs.
Passez en revue le contenu du répertoire de sortie :

dir bin\Release\net8.0\win-x64\publish

Une sortie similaire à l’exemple suivant s’affiche à l’écran :

Output

Directory: C:\Code\Demos\MyFirstAotWebApi\bin\Release\net8.0\win-
x64\publish

Mode LastWriteTime Length Name


---- ------------- ------ ----
-a--- 30/03/2023 1:41 PM 9480704 MyFirstAotWebApi.exe
-a--- 30/03/2023 1:41 PM 43044864 MyFirstAotWebApi.pdb

Le fichier exécutable est autonome et ne nécessite pas de runtime .NET pour s’exécuter.
Lors du lancement, il se comporte de la même façon que l’application exécutée dans
l’environnement de développement. Exécutez l’application AOT :

.\bin\Release\net8.0\win-x64\publish\MyFirstAotWebApi.exe

Une sortie similaire à l’exemple suivant s’affiche à l’écran :

Sortie

info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Code\Demos\MyFirstAotWebApi

Bibliothèques et AOT native


La plupart des bibliothèques populaires utilisées dans les projets ASP.NET Core
présentent actuellement des problèmes de compatibilité lorsqu’elles sont utilisées dans
un projet ciblant l’AOT native, par exemple :
Utilisation de la réflexion pour inspecter et découvrir des types.
Chargement conditionnel de bibliothèques au moment de l’exécution.
Génération de code à la volée pour implémenter des fonctionnalités.

Les bibliothèques utilisant ces fonctionnalités dynamiques doivent être mises à jour
pour fonctionner avec l’AOT native. Elles peuvent être mises à jour à l’aide d’outils tels
que les générateurs de sources Roslyn.

Les auteurs de bibliothèque qui espèrent prendre en charge l’AOT native sont
encouragés à :

Découvrir les exigences de compatibilité d’AOT native.


Préparez la bibliothèque pour le découpage.

Voir aussi
Prise en charge d’ASP.NET Core pour Native AOT
Déploiement de Native AOT
À l’aide du générateur source du classeur de configuration
Modèle de compilation AOT de l’API minimale
Comparaison de WebApplication.CreateBuilder à CreateSlimBuilder
Exploration du nouveau générateur de source d’API minimal
Remplacement des appels de méthode par des intercepteurs
Générateur de source de liaison de configuration

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Convertir les méthodes Map en
délégués de requête avec le Générateur
de délégués de requête ASP.NET Core
Article • 30/11/2023
) Contenu assisté par l'IA. Cet article a été en partie créé à l’aide d’une IA. Un auteur a examiné et
si besoin révisé le contenu. En savoir plus

Le Générateur de délégués de requête ASP.NET Core est un générateur de source au


moment de la compilation qui compile les gestionnaires de routage fournis à une API
minimale pour les délégués de requête qui peuvent être traités par l’infrastructure de
routage d’ASP.NET Core. Le Générateur de délégués de requête est implicitement activé
lorsque les applications sont publiées avec AoT activé et générées avec du code où AoT
natif et le « trimming » peuvent être utilisés.

7 Notes

La fonctionnalité AOT native est actuellement en préversion.


Dans .NET 8, toutes les fonctionnalités ASP.NET Core ne sont pas compatibles
avec l’AOT native.

Le RDG :

Est un générateur de source


Convertit chaque méthode Map en RequestDelegate associé avec la route
spécifique. Les méthodes Map incluent les méthodes des
EndpointRouteBuilderExtensions telles que MapGet, MapPost, MapPatch, MapPut
et MapDelete.

Lors de la publication avec AOT natif non activé :

Les méthodes Map associées à une route spécifique sont compilées en mémoire
dans un délégué de requête lorsque l’application démarre, et non quand elle est
générée.
Les délégués de requête sont générés au moment de l’exécution.

Lors de la publication avec AOT natif activé :

Les méthodes Map associées à une route spécifique sont compilées lorsque
l’application est générée. Le Générateur de délégués de requête crée le délégué de
requête pour la route et le délégué de requête est compilé dans l’image native de
l’application.
Plus besoin de générer le délégué de requête au moment de l’exécution.
Garantit que :
Les types utilisés dans les API de l’application sont rootés dans le code de
l’application d’une manière qui est statiquement analysable par la chaîne
d’outils AOT natif.
Le code requis ne fait pas l’objet d’un « trimming » (suppression des parties
inutilisées).

Le RDG :

Est activé automatiquement dans les projets lors de la publication avec AOT natif
activé.
Peut être activé manuellement même si vous n’utilisez pas AOT natif, en définissant
<EnableRequestDelegateGenerator>true</EnableRequestDelegateGenerator> dans le

fichier projet :

XML

<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<EnableRequestDelegateGenerator>true</EnableRequestDelegateGenerator>
</PropertyGroup>

</Project>

L’activation manuelle du Générateur de délégués de requête peut être utile pour :

Évaluation de la compatibilité d’un projet avec AOT natif.


Réduire le temps de démarrage de l’application en prégénérant les délégués de
requête.

Les API minimales sont optimisées pour l’utilisation de System.Text.Json, ce qui implique
l’utilisation du Générateur de source System.Text.Json. Tous les types acceptés en tant
que paramètres ou retournés par les délégués de requête dans les API minimales
doivent être configurés sur un JsonSerializerContext inscrit via l’injection de
dépendances d’ASP.NET Core :

C#
using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(
0, AppJsonSerializerContext.Default);
});

var app = builder.Build();

var sampleTodos = new Todo[] {


new(1, "Walk the dog"),
new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)),
new(3, "Do the laundry",
DateOnly.FromDateTime(DateTime.Now.AddDays(1))),
new(4, "Clean the bathroom"),
new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2)))
};

var todosApi = app.MapGroup("/todos");


todosApi.MapGet("/", () => sampleTodos);
todosApi.MapGet("/{id}", (int id) =>
sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo
? Results.Ok(todo)
: Results.NotFound());

app.Run();

public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool
IsComplete = false);

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

Diagnostics émis pour les scénarios RDG non


pris en charge
Le Générateur de délégués de requête émet des diagnostics pour les scénarios qui ne
sont pas pris en charge par AOT natif. Les diagnostics sont émis lorsque l’application est
générée. Les diagnostics sont émis sous forme d’avertissements et n’empêchent pas
l’application d’être générée. La classe DiagnosticDescriptors contient les diagnostics
émis par le Générateur de délégués de requête.
Consultez Diagnostics du Générateur de délégués de requête ASP.NET Core pour
obtenir la liste des diagnostics émis par le Générateur de délégués de requête.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Diagnostics du Générateur de délégués
de requête (RDG) ASP.NET Core
Article • 16/02/2024
) Contenu assisté par l'IA. Cet article a été en partie créé à l’aide d’une IA. Un auteur a examiné et
si besoin révisé le contenu. En savoir plus

Le Générateur de délégués de requête (RDG) ASP.NET Core est un outil qui génère des
délégués de requête pour les applications ASP.NET Core. Le RDG est utilisé par le
compilateur natif anticipé (Ahead Of Time) afin de générer des délégués de requête
pour les méthodes Map de l’application.

7 Notes

La fonctionnalité AOT native est actuellement en préversion.


Dans .NET 8, toutes les fonctionnalités ASP.NET Core ne sont pas compatibles
avec l’AOT native.

La liste suivante contient les diagnostics du RDG (Générateur de délégués de requête)


pour ASP.NET Core :

RDG002 : impossible de résoudre le gestionnaire de points de terminaison

RDG004 : impossible de résoudre le type anonyme


RDG005 : Type abstract non valide
RDG006 : paramètres de constructeur non valides
RDG007 : aucun constructeur valide trouvé
RDG008 : plusieurs constructeurs publics
RDG009 : AsParameters imbriqués non valides
RDG010 : InvalidAsParameters Nullable
RDG011 : paramètres de type non pris en charge
RDG012 : impossible de résoudre le type inaccessible
RDG013 : attributs sources non valides

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
RDG002 : Impossible de résoudre le
gestionnaire de points de terminaison
Article • 30/11/2023

Value

Identificateur de la règle RDG002

Le correctif est cassant ou non cassant Sans rupture

Cause
Ce diagnostic est émis par le Générateur de délégués de requête quand un point de
terminaison contient un gestionnaire de routage qui ne peut pas être analysé de
manière statique.

Description de la règle
Le Générateur de délégués de requête s’exécute au moment de la compilation et doit
pouvoir analyser de manière statique les gestionnaires de routage dans une application.
L’implémentation actuelle prend uniquement en charge les gestionnaires de routage
fournis sous forme d’expressions lambda, de références de groupe de méthodes ou de
références aux champs ou variables en lecture seule.

Le code suivant génère l’avertissement RDG002, car le gestionnaire de routage est


fourni en tant que référence à une méthode :

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

var del = Wrapper.GetTodos;


app.MapGet("/v1/todos", del);
app.Run();

record Todo(int Id, string Task);


[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

class Wrapper
{
public static Func<IResult> GetTodos = () =>
Results.Ok(new Todo(1, "Write test fix"));
}

Comment corriger les violations


Déclarez le gestionnaire de routage à l’aide de la syntaxe prise en charge, par exemple
une expression lambda inline :

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapGet("/v1/todos", () => Results.Ok(new Todo(1, "Write tests")));

app.Run();

record Todo(int Id, string Task);


[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

Quand supprimer les avertissements


Cet avertissement peut être supprimé sans problème. En cas de suppression,
l’infrastructure revient à la génération du délégué de requête au moment de l’exécution.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
RDG004 : Impossible de résoudre un
type anonyme
Article • 30/11/2023
) Contenu assisté par l'IA. Cet article a été en partie créé à l’aide d’une IA. Un auteur a examiné et
si besoin révisé le contenu. En savoir plus

Value

Identificateur de la règle RDG004

Le correctif est cassant ou non cassant Sans rupture

Cause
Ce diagnostic est émis par le Générateur de délégués de requête lorsqu’un point de
terminaison contient un gestionnaire de routage avec un type de retour anonyme.

Description de la règle
Le générateur de délégués de requête s’exécute au moment de la compilation et doit
être en mesure d’analyser statiquement les gestionnaires d’itinéraires dans une
application. Les types anonymes sont générés avec un nom de type uniquement connu
du compilateur et ne sont pas analysables statiquement. Le point de terminaison suivant
produit le diagnostic.

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapGet("/v1/todos", () => new { Id = 1, Task = "Write tests" });

app.Run();

record Todo(int Id, string Task);


[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

Comment corriger les violations


Déclarez le gestionnaire de routage avec un type concret comme type de retour.

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapGet("/v1/todos", () => new Todo(1, "Write tests fix"));

app.Run();

record Todo(int Id, string Task);


[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

Quand supprimer les avertissements


Cet avertissement peut être supprimé sans problème. En cas de suppression,
l’infrastructure revient à la génération du délégué de requête au moment de l’exécution.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et  Ouvrir un problème de
examiner les problèmes et les documentation
demandes de tirage. Pour plus
d’informations, consultez notre  Indiquer des commentaires sur
guide du contributeur.
le produit
RDG005 : Type abstract non valide
Article • 30/11/2023
) Contenu assisté par l'IA. Cet article a été en partie créé à l’aide d’une IA. Un auteur a examiné et
si besoin révisé le contenu. En savoir plus

Value

Identificateur de la règle RDG005

Le correctif est cassant ou non cassant Sans rupture

Cause
Ce diagnostic est émis par le Générateur de délégués de requête lorsqu’un point de
terminaison contient un gestionnaire de routage avec un paramètre annoté avec
l’attribut [AsParameters], qui est un type abstrait.

Description de la règle
L’implémentation de la liaison de substitution via l’attribut [AsParameters] dans des API
minimales prend uniquement en charge les types avec des implémentations concrètes.
L’utilisation d’un paramètre avec un type abstrait comme dans l’exemple suivant produit
le diagnostic.

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();


app.MapPut("/v1/todos/{id}",
([AsParameters] TodoRequest todoRequest) =>
Results.Ok(todoRequest.Todo));

app.Run();
abstract class TodoRequest
{
public int Id { get; set; }
public Todo? Todo { get; set; }
}

record Todo(int Id, string Task);

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

Comment corriger les violations


Utilisez un type concret pour le substitut :

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapPut("/v1/todos/{id}",
([AsParameters] TodoRequest todoRequest) =>
Results.Ok(todoRequest.Todo));

app.Run();

class TodoRequest
{
public int Id { get; set; }
public Todo? Todo { get; set; }
}

record Todo(int Id, string Task);

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

}
Quand supprimer les avertissements
Cet avertissement ne doit pas être supprimé. La suppression de l’avertissement entraîne
une exception de runtime associée au même avertissement.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
RDG006 : Paramètres de constructeur
non valides
Article • 30/11/2023

Value

Identificateur de la règle RDG006

Le correctif est cassant ou non cassant Sans rupture

Cause
Ce diagnostic est émis par le Générateur de délégués de requête lorsqu’un point de
terminaison contient un gestionnaire de routage avec un paramètre annoté avec
l’attribut [AsParameters] qui contient un constructeur non valide.

Description de la règle
Les types utilisés pour la liaison de substitution via l’attribut [AsParameters] doivent
contenir un constructeur paramétrable public où tous les paramètres du constructeur
correspondent aux propriétés publiques déclarées sur le type. Le type TodoRequest
génère ce diagnostic, car il n’existe aucun paramètre de constructeur correspondant
pour la propriété Todo .

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapPut("/v1/todos/{id}",
([AsParameters] TodoRequest todoRequest) =>
Results.Ok(todoRequest.Todo));

app.Run();
class TodoRequest(int id, string name)
{
public int Id { get; set; } = id;
public Todo? Todo { get; set; }
}

record Todo(int Id, string Task);

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

Comment corriger les violations


Vérifiez que toutes les propriétés du type ont un paramètre correspondant au niveau du
constructeur.

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapPut("/v1/todos/{id}",
([AsParameters] TodoRequest todoRequest) =>
Results.Ok(todoRequest.Todo));

app.Run();

class TodoRequest(int id, Todo? todo)


{
public int Id { get; set; } = id;
public Todo? Todo { get; set; } = todo;
}

record Todo(int Id, string Task);

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}

Quand supprimer les avertissements


Cet avertissement ne doit pas être supprimé. La suppression de l’avertissement entraîne
une exception de runtime associée au même avertissement.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
RDG007 : Aucun constructeur valide
trouvé
Article • 30/11/2023

Value

Identificateur de la règle RDG007

Le correctif est cassant ou non cassant Sans rupture

Cause
Ce diagnostic est émis par le Générateur de délégués de requête lorsqu’un point de
terminaison contient un gestionnaire de routage avec un paramètre annoté avec
l’attribut [AsParameters] sans constructeur valide.

Description de la règle
Les types utilisés pour la liaison de substitution via l’attribut AsParameters doivent
contenir un constructeur public. Le type TodoRequest génère ce diagnostic, car il n’existe
aucun constructeur public.

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapPut("/v1/todos/{id}", ([AsParameters] TodoRequest request)


=> Results.Ok(request.Todo));

app.Run();

public class TodoRequest


{
public int Id { get; set; }
public Todo Todo { get; set; }
private TodoRequest()
{
}
}

public record Todo(int Id, string Task);

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

Comment corriger les violations


Supprimez le constructeur non public ou ajoutez un nouveau constructeur public.

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapPut("/v1/todos/{id}", ([AsParameters] TodoRequest request)


=> Results.Ok(request.Todo));
app.Run();

public class TodoRequest(int Id, Todo todo)


{
public int Id { get; set; } = Id;
public Todo Todo { get; set; } = todo;
}

public record Todo(int Id, string Task);

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{

}
Quand supprimer les avertissements
Cet avertissement ne peut pas être supprimé en toute sécurité. En cas de suppression,
entraîne l’exception d’exécution InvalidOperationException .

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
RDG008 : Constructeurs publics
multiples
Article • 30/11/2023

Value

Identificateur de la règle RDG008

Le correctif est cassant ou non cassant Sans rupture

Cause
Ce diagnostic est émis par le Générateur de délégués de requête quand un point de
terminaison contient un gestionnaire de routage avec un paramètre annoté avec
l’attribut [AsParameters] avec plusieurs constructeurs publics.

Description de la règle
Les types utilisés pour la liaison de substitution via l’attribut AsParameters doivent
contenir un seul constructeur public. Le type TodoRequest génère ce diagnostic, car il
existe plusieurs constructeurs publics.

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder();


var todos = new[]
{
new Todo(1, "Write tests", DateTime.UtcNow.AddDays(2)),
new Todo(2, "Fix tests",DateTime.UtcNow.AddDays(1))
};
builder.Services.AddSingleton(todos);
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapGet("/v1/todos/{id}", ([AsParameters] TodoItemRequest request) =>


{
return request.Todos.ToList().Find(todoItem => todoItem.Id ==
request.Id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound();
});

app.Run();

public class TodoItemRequest


{
public int Id { get; set; }
public Todo[] Todos { get; set; }

public TodoItemRequest(int id, Todo[] todos)


{
Id = id;
Todos = todos;
}

// Additional Constructor
public TodoItemRequest()
{
Id = 1;
Todos = [new Todo(1, "Write tests", DateTime.UtcNow.AddDays(2))];
}

public class Todo


{
public DateTime DueDate { get; }
public int Id { get; private set; }
public string Task { get; private set; }

public Todo(int id, string task, DateTime dueDate)

{
Id = id;
Task = task;
DueDate = dueDate;
}
}

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}

Comment corriger les violations


Fournissez un seul constructeur public.

C#
using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder();


var todos = new[]
{
new Todo(1, "Write tests", DateTime.UtcNow.AddDays(2)),
new Todo(2, "Fix tests",DateTime.UtcNow.AddDays(1))
};
builder.Services.AddSingleton(todos);
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapGet("/v1/todos/{id}", ([AsParameters] TodoItemRequest request) =>


{
return request.Todos.ToList().Find(todoItem => todoItem.Id ==
request.Id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound();
});

app.Run();

public class TodoItemRequest


{
public int Id { get; set; }
public Todo[] Todos { get; set; }

public TodoItemRequest(int id, Todo[] todos)


{
Id = id;
Todos = todos;
}
}

public class Todo


{
public DateTime DueDate { get; }
public int Id { get; private set; }
public string Task { get; private set; }

public Todo(int id, string task, DateTime dueDate)


{
Id = id;
Task = task;
DueDate = dueDate;
}
}
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}

Quand supprimer les avertissements


Cet avertissement peut être supprimé sans problème.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
RDG009 : AsParameters imbriqués non
valides
Article • 30/11/2023

Value

Identificateur de la règle RDG009

Le correctif est cassant ou non cassant Sans rupture

Cause
Ce diagnostic est émis par le générateur de délégués de requête lorsqu’un point de
terminaison contient une imbrication non valide [AsParameters].

Description de la règle
Les types qui sont utilisés pour la liaison de substitution via l’attribut [AsParameters] ne
doivent pas contenir de types imbriqués qui sont également annotés avec l’attribut
[AsParameters] :

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder();

var todos = new[]


{
new Todo(1, "Write tests", DateTime.UtcNow.AddDays(2)),
new Todo(2, "Fix tests",DateTime.UtcNow.AddDays(1))
};

builder.Services.AddSingleton(todos);
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapGet("/v1/todos/{id}", ([AsParameters] TodoItemRequest request) =>


{
return request.todos.ToList().Find(todoItem => todoItem.Id ==
request.Id) is Todo todo
? Results.Ok(todo)
: Results.NotFound();
});

app.Run();

struct TodoItemRequest
{
public int Id { get; set; }
[AsParameters]
public Todo[] todos { get; set; }
}

internal record Todo(int Id, string Task, DateTime DueDate);

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}

Comment corriger les violations


Supprimez l’attribut imbriqué AsParameters imbriqué :

C#

using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder();


var todos = new[]
{
new Todo(1, "Write tests", DateTime.UtcNow.AddDays(2)),
new Todo(2, "Fix tests",DateTime.UtcNow.AddDays(1))
};
builder.Services.AddSingleton(todos);
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapGet("/v1/todos/{id}", ([AsParameters] TodoItemRequest request) =>


{
return request.todos.ToList().Find(todoItem => todoItem.Id ==
request.Id) is Todo todo
? Results.Ok(todo)
: Results.NotFound();
});
app.Run();

struct TodoItemRequest
{
public int Id { get; set; }
//[AsParameters]
public Todo[] todos { get; set; }
}

internal record Todo(int Id, string Task, DateTime DueDate);

[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}

Quand supprimer les avertissements


Cet avertissement ne peut pas être supprimé.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
RDG010 : InvalidAsParameters Nullable
Article • 30/11/2023

Value

Identificateur de la règle RDG010

Le correctif est cassant ou non cassant Sans rupture

Cause
Ce diagnostic est émis par le générateur de délégués de requête lorsqu’un point de
terminaison contient un gestionnaire de route avec un paramètre annoté avec l’attribut
[AsParameters] marqué comme nullable.

Description de la règle
L'implémentation de la liaison de substitution via l'attribut [AsParameters] dans les API
minimales ne prend en charge que les types qui ne sont pas nullables.

C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/todos/{id}", ([AsParameters] TodoRequest? request)


=> Results.Ok(new Todo(request!.Id)));

app.Run();

public record TodoRequest(HttpContext HttpContext, [FromRoute] int Id);


public record Todo(int Id);

Comment corriger les violations


Déclarez le paramètre comme non nullable.

C#
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/todos/{id}", ([AsParameters] TodoRequest request)


=> Results.Ok(new Todo(request.Id)));

app.Run();

public record TodoRequest(HttpContext HttpContext, [FromRoute] int Id);


public record Todo(int Id);

Quand supprimer les avertissements


Cet avertissement ne doit pas être supprimé. La suppression de l’avertissement entraîne
une exception de runtime associée au même avertissement.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
RDG011 : paramètres de type non pris en
charge
Article • 30/11/2023

Value

Identificateur de la règle RDG011

Le correctif est cassant ou non cassant Sans rupture

Cause
Ce diagnostic est émis par le Générateur de délégués de requête lorsqu’un point de
terminaison contient un gestionnaire d’itinéraires qui capture un type générique.

Description de la règle
Les points de terminaison qui utilisent des paramètres de type générique ne sont pas
pris en charge. Les points de terminaison dans MapEndpoints génèrent ce diagnostic en
raison du paramètre générique <T> .

C#

var builder = WebApplication.CreateSlimBuilder(args);

var app = builder.Build();


app.MapEndpoints<Todo>();
app.Run();

public static class RouteBuilderExtensions


{
public static IEndpointRouteBuilder MapEndpoints<T>(this
IEndpointRouteBuilder app) where T : class, new()
{
app.MapPost("/input", (T value) => value);
app.MapGet("/result", () => new T());
app.MapPost("/input-with-wrapper", (Wrapper<T> value) => value);
app.MapGet("/async", async () =>
{
await Task.CompletedTask;
return new T();
});
return app;
}
}
record Todo();
record Wrapper<T> { }

Comment corriger les violations


Supprimez le type générique des points de terminaison.

C#

var builder = WebApplication.CreateSlimBuilder(args);

var app = builder.Build();


app.MapTodoEndpoints();
app.Run();

public static class TodoRouteBuilderExtensions


{
public static IEndpointRouteBuilder MapTodoEndpoints(this
IEndpointRouteBuilder app)
{
app.MapPost("/input", (Todo value) => value);
app.MapGet("/result", () => new Todo());
app.MapPost("/input-with-wrapper", (Wrapper<Todo> value) => value);
app.MapGet("/async", async () =>
{
await Task.CompletedTask;
return new Todo();
});
return app;
}
}

record Todo();
record Wrapper<T> { }

Quand supprimer les avertissements


Cet avertissement peut être supprimé sans problème. En cas de suppression, le
framework génère le délégué de requête au moment de l’exécution comme solution de
repli.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub
La source de ce contenu se ASP.NET Core is an open source
trouve sur GitHub, où vous project. Select a link to provide
pouvez également créer et feedback:
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
RDG012 : impossible de résoudre le type
inaccessible
Article • 30/11/2023

Value

Identificateur de la règle RDG012

Le correctif est cassant ou non cassant Sans rupture

Cause
Ce diagnostic est émis par le Générateur de délégués de requête lorsqu’un point de
terminaison contient un gestionnaire de routage avec un paramètre sans les
modificateurs d’accessibilité appropriés.

Description de la règle
Les points de terminaison qui utilisent un type inaccessible ( private ou protected ) ne
sont pas pris en charge. Les points de terminaison dans MapEndpoints produisent ce
diagnostic en raison du type Todo doté des modificateurs d'accessibilité private .

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();


app.MapEndpoints();
app.Run();

public static class TodoRouteBuilderExtensions


{
public static IEndpointRouteBuilder MapEndpoints(this
IEndpointRouteBuilder app)
{
app.MapPost("/input", (Todo value) => value);
app.MapGet("/result", () => new Todo());
app.MapPost("/input-with-wrapper", (Wrapper<Todo> value) => value);
app.MapGet("/async", async () =>
{
await Task.CompletedTask;
return new Todo();
});
return app;
}
private record Todo { };
}

record Wrapper<T> { }

Comment corriger les violations


Le cas échéant, définissez le type de paramètre cible avec une accessibilité conviviale.

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();


app.MapEndpoints();
app.Run();

public static class TodoRouteBuilderExtensions


{
public static IEndpointRouteBuilder MapEndpoints(this
IEndpointRouteBuilder app)
{
app.MapPost("/input", (Todo value) => value);
app.MapGet("/result", () => new Todo());
app.MapPost("/input-with-wrapper", (Wrapper<Todo> value) => value);
app.MapGet("/async", async () =>
{
await Task.CompletedTask;
return new Todo();
});
return app;
}

public record Todo { };


}

record Wrapper<T> { }

Quand supprimer les avertissements


Cet avertissement peut être supprimé sans problème. En cas de suppression, le
framework génère le délégué de requête au moment de l’exécution comme solution de
repli.
6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
RDG013 : attributs sources non valides
Article • 30/11/2023

Value

Identificateur de la règle RDG013

Le correctif est cassant ou non cassant Sans rupture

Cause
Ce diagnostic est émis par le Générateur de délégués de requête lorsqu’un point de
terminaison contient un gestionnaire de routes avec un paramètre comprenant une
combinaison non valide d’attributs de source de service.

Description de la règle
ASP.NET Core prend en charge la résolution des services avec clé et sans clé via
l’injection de dépendances. Il est impossible de résoudre un service à la fois avec clé et
sans clé. Le code suivant génère le diagnostic et lève une erreur d’exécution avec le
même message :

C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<IService, FizzService>("fizz");
var app = builder.Build();

app.MapGet("/fizz", ([FromKeyedServices("fizz")][FromServices] IService


service) =>
{
return Results.Ok(service.Echo());
});

app.Run();

Comment corriger les violations


Résolvez le paramètre cible comme service avec clé ou sans clé.
C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<IService, FizzService>("fizz");
builder.Services.AddKeyedSingleton<IService, BuzzService>("buzz");
builder.Services.AddSingleton<IService, FizzBuzzService>();
var app = builder.Build();

app.MapGet("/fizz", ([FromKeyedServices("fizz")] IService service) =>


{
return Results.Ok(service.Echo());
});

app.MapGet("/buzz", ([FromKeyedServices("buzz")] IService service) =>


{
return Results.Ok(service.Echo());
});

app.MapGet("/fizzbuzz", ([FromServices] IService service) =>


{
return Results.Ok(service.Echo());
});

app.Run();

public interface IService


{
string Echo();
}

public class FizzService : IService


{
public string Echo() => "Fizz";
}

public class BuzzService : IService


{
public string Echo() => "Buzz";
}

public class FizzBuzzService : IService


{
public string Echo()
{
return "FizzBuzz";
}
}

Quand supprimer les avertissements


Cet avertissement ne doit pas être supprimé. La suppression de l’avertissement entraîne
une exception de runtime NotSupportedException The FromKeyedServicesAttribute is
not supported on parameters that are also annotated with IFromServiceMetadata.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Intergiciel (middleware) ASP.NET Core
Article • 30/11/2023

De Rick Anderson et Steve Smith

Un middleware est un logiciel qui est assemblé dans un pipeline d’application pour
gérer les requêtes et les réponses. Chaque composant :

Choisit de passer la requête au composant suivant dans le pipeline.


Peut travailler avant et après le composant suivant dans le pipeline.

Les délégués de requête sont utilisés pour créer le pipeline de requête. Les délégués de
requête gèrent chaque requête HTTP.

Les délégués de requête sont configurés à l’aide des méthodes d’extension Run, Map et
Use. Chaque délégué de requête peut être spécifié inline comme méthode anonyme
(appelée intergiciel inline) ou peut être défini dans une classe réutilisable. Ces classes
réutilisables et les méthodes anonymes inline sont des middlewares, également appelés
composants de middleware. Chaque composant de middleware du pipeline de requête
est chargé d’appeler le composant suivant du pipeline ou de court-circuiter le pipeline.
Lorsqu’un middleware effectue un court-circuit, on parle de middleware terminal, car il
empêche tout autre middleware de traiter la requête.

Migrer des gestionnaires et des modules HTTP vers des middlewares ASP.NET Core
explique les différences qui existent entre les pipelines de requêtes dans ASP.NET Core
et dans ASP.NET 4.x, et fournit d’autres exemples de middlewares (intergiciels).

Analyse du code des middlewares


ASP.NET Core inclut de nombreux analyseurs de plateforme de compilateur qui
inspectent le code d’application à des fins de qualité. Pour plus d’informations,
consultez Analyse du code dans les applications ASP.NET Core.

Créer un pipeline de middlewares avec


WebApplication
Le pipeline de requête ASP.NET Core est composé d’une séquence de délégués de
requête, appelés l’un après l’autre. Le diagramme suivant illustre le concept. Le thread
d’exécution suit les flèches noires.
Chaque délégué peut effectuer des opérations avant et après le délégué suivant. Les
délégués de gestion des exceptions doivent être appelés tôt dans le pipeline pour qu’ils
puissent intercepter les exceptions qui se produisent dans les phases ultérieures du
pipeline.

L’application ASP.NET Core la plus simple possible définit un seul délégué de requête
qui gère toutes les requêtes. Ce cas ne fait pas appel à un pipeline de requête réel. À la
place, une seule fonction anonyme est appelée en réponse à chaque requête HTTP.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.Run(async context =>


{
await context.Response.WriteAsync("Hello world!");
});

app.Run();

Chaînez plusieurs délégués de requête avec Use. Le paramètre next représente le


délégué suivant dans le pipeline. Vous pouvez court-circuiter le pipeline en n’appelant
pas le paramètre next . Vous pouvez généralement effectuer des actions à la fois avant
et après le délégué next , comme illustré dans cet exemple :

C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Use(async (context, next) =>


{
// Do work that can write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

Court-circuiter la pipeline de requête


Quand un délégué ne passe pas une requête au délégué suivant, on parle alors de
court-circuit du pipeline de requête. Un court-circuit est souvent souhaitable car il évite le
travail inutile. Par exemple, le middleware Fichier statique peut agir en tant que
middleware terminal en traitant une requête pour un fichier statique et en court-
circuitant le reste du pipeline. Le middleware ajouté au pipeline avant de le middleware
qui met fin à la poursuite du traitement traite tout de même le code après les
instructions next.Invoke . Toutefois, consultez l’avertissement suivant à propos de la
tentative d’écriture sur une réponse qui a déjà été envoyée.

2 Avertissement

N’appelez pas next.Invoke une fois que la réponse a été envoyée au client. Les
changements apportés à HttpResponse après le démarrage de la réponse lèvent
une exception. Par exemple, le fait de définir des en-têtes et du code d’état lève
une exception. Écrire dans le corps de la réponse après avoir appelé next :

Peut entraîner une violation de protocole. Par exemple, écrire plus que le
Content-Length indiqué.

Peut altérer le format du corps. Par exemple, l’écriture d’un pied de page
HTML dans un fichier CSS.

HasStarted est un indice utile pour indiquer si les en-têtes ont été envoyés ou si le
corps a fait l’objet d’écritures.
Pour plus d’informations, consultez Court-circuiter l’intergiciel après le routage.

Run délégués

Les délégués Run ne reçoivent pas de paramètre next . Le premier délégué Run est
toujours terminal et termine le pipeline. Run est une convention. Certains composants
middleware peuvent exposer des méthodes Run[Middleware] qui s’exécutent à la fin du
pipeline :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.Use(async (context, next) =>


{
// Do work that can write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});

app.Run();

Si vous souhaitez voir les commentaires de code traduits dans une langue autre que
l’anglais, dites-le nous dans cette discussion GitHub .

Dans l’exemple précédent, le délégué Run écrit "Hello from 2nd delegate." dans la
réponse, puis termine le pipeline. Si un autre délégué Use ou Run est ajouté après le
délégué Run , il ne sera pas appelé.

Préférez une surcharge app.Use qui nécessite le passage


du contexte à l’instruction next()
La méthode d’extension app.Use non-allouante :

Nécessite le passage du contexte à next .


Enregistre deux allocations internes par requête, qui sont nécessaires lors de
l’utilisation de l’autre surcharge.

Pour plus d’informations, consultez ce problème GitHub .


Ordre des middlewares
Le schéma suivant montre le pipeline complet de traitement des requêtes pour les
applications ASP.NET Core MVC et Razor Pages. Vous pouvez voir comment, dans une
application classique, les middlewares existants sont ordonnés et à quel endroit les
middlewares personnalisés sont ajoutés. Vous avez un contrôle total sur la façon de
réorganiser les middlewares existants et d’injecter de nouveaux middlewares
personnalisés si nécessaire pour vos scénarios.

Le middleware Endpoint du schéma précédent exécute le pipeline de filtre pour le type


d’application correspondant (MVC ou Razor Pages).

Dans le schéma précédent, le middleware Routing suit Static Files. Il s’agit de l’ordre
dans lequel les modèles du projet sont implémentés en appelant explicitement
app.UseRouting. Si vous n’appelez pas app.UseRouting , le middleware Routing
s’exécutera au début du pipeline par défaut. Pour plus d’informations, consultez
Routage.
L’ordre dans lequel les composants de middleware sont ajoutés au fichier Program.cs
définit l’ordre dans lequel ils seront appelés lors des requêtes et l’ordre inverse pour la
réponse. L’ordre est critique pour la sécurité, les performances et le fonctionnement.

Le code mis en évidence ci-dessous dans Program.cs ajoute des composants


middleware liés à la sécurité, dans l’ordre généralement recommandé :

C#

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string
'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();
app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

Dans le code précédent :

Les middlewares qui ne sont pas ajoutés lors de la création d’une application web
avec des comptes d’utilisateurs individuels sont commentés.
Tous les middlewares ne s’affichent pas dans cet ordre exact, mais beaucoup le
sont. Par exemple :
UseCors , UseAuthentication et UseAuthorization doivent s’afficher dans l’ordre

indiqué.
UseCors doit apparaître avant UseResponseCaching . Cette exigence est expliquée

dans le problème GitHub dotnet/aspnetcore n° 23218 .


UseRequestLocalization doit apparaître avant tout middleware pouvant vérifier
la culture de la requête, par exemple, app.UseStaticFiles() .
UseRateLimiter doit être appelé après UseRouting lorsque des API spécifiques
au point de terminaison de limitation de débit sont utilisées. Par exemple, si
l’attribut [EnableRateLimiting] est utilisé, UseRateLimiter doit être appelé après
UseRouting . Lorsque vous appelez uniquement des limiteurs globaux,
UseRateLimiter peut être appelé avant UseRouting .

Dans certains scénarios, les middlewares sont ordonnés différemment. Par exemple,
l’ordre de la mise en cache et de la compression dépend du scénario, et il existe
plusieurs ordres valides. Par exemple :

C#

app.UseResponseCaching();
app.UseResponseCompression();

Avec le code précédent, vous pouvez réduire l’utilisation du processeur en mettant en


cache la réponse compressée. Cependant, vous risquez ainsi de mettre en cache
plusieurs représentations d’une même ressource à l’aide d’algorithmes de compression
différents tels que Gzip ou Brotli.

L’ordre suivant combine des fichiers statiques pour permettre la mise en cache des
fichiers statiques compressés :

C#

app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

Le code Program.cs suivant ajoute des composants middleware utiles pour les scénarios
d’application courants :

1. Gestion des erreurs/exceptions

Quand l’application s’exécute dans l’environnement de développement :


Le middleware Page d’exceptions du développeur
(UseDeveloperExceptionPage) signale des erreurs de runtime de
l’application.
Le middleware Page d’erreur de base de données (UseDatabaseErrorPage)
signale des erreurs de runtime de la base de données.
Quand l’application s’exécute dans l’environnement de production :
Le middleware Gestionnaire d'exceptions (UseExceptionHandler)
intercepte des exceptions levées dans les middlewares suivants.
Le middleware Protocole HSTS (HTTP Strict Transport Security) (UseHsts)
ajoute l’en-tête Strict-Transport-Security .
2. Le middleware Redirection HTTPS (UseHttpsRedirection) redirige les requêtes HTTP
vers HTTPS.
3. Le middleware Fichier statique (UseStaticFiles) retourne des fichiers statiques et
court-circuite tout traitement supplémentaire de la requête.
4. Le middleware Cookie Policy (UseCookiePolicy) met l’application en conformité
avec les réglementations du RGPD (Règlement général sur la protection des
données).
5. Middleware Routing (UseRouting) pour router les requêtes.
6. Le middleware Authentification (UseAuthentication) tente d’authentifier l’utilisateur
avant qu’il ne soit autorisé à accéder aux ressources sécurisées.
7. Middleware Authorization (UseAuthorization) autorise un utilisateur à accéder aux
ressources sécurisées.
8. Le middleware Session (UseSession) établit et maintient l’état de la session. Si
l’application utilise l’état de session, appelez le middleware Session après le
middleware Cookie Policy et avant le middleware MVC.
9. Middleware Endpoint Routing (UseEndpoints avec MapRazorPages) pour ajouter
des points de terminaison Razor Pages au pipeline de requête.

C#

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();

Dans l’exemple de code précédent, chaque méthode d’extension d’intergiciel


(middleware) est exposée sur WebApplicationBuilder à travers l’espace de noms
Microsoft.AspNetCore.Builder.

UseExceptionHandler est le premier composant d’intergiciel ajouté au pipeline. Par


conséquent, le middleware Gestion des exceptions intercepte toutes les exceptions qui
se produisent dans les appels ultérieurs.
Le middleware Fichier statique est appelé tôt dans le pipeline pour qu’il puisse gérer les
requêtes et procéder au court-circuit sans passer par les composants restants. Le
middleware Fichier statique ne fournit aucune vérification d’autorisation. Tous les
fichiers distribués par le middleware Static File, notamment ceux sous wwwroot, sont
accessibles publiquement. Pour connaître une approche permettant de sécuriser les
fichiers statiques, consultez Fichiers statiques dans ASP.NET Core.

Si la requête n’est pas gérée par le middleware Fichier statique, elle est transmise au
middleware Authentification (UseAuthentication), qui effectue l’authentification. Le
middleware Authentification ne court-circuite pas les requêtes non authentifiées. Même
si le middleware Authentication authentifie les requêtes, l’autorisation et le refus
interviennent uniquement après que MVC a sélectionné un contrôleur ou une action
Razor Pages ou MVC.

L’exemple suivant montre un ordre de middlewares où les requêtes pour les fichiers
statiques sont gérées par le middleware Fichier statique avant le middleware
Compression de la réponse. Les fichiers statiques ne sont pas compressés avec cet ordre
de middlewares. Les réponses Razor Pages peuvent être compressées.

C#

// Static files aren't compressed by Static File Middleware.


app.UseStaticFiles();

app.UseRouting();

app.UseResponseCompression();

app.MapRazorPages();

Pour plus d’informations sur les applications monopage, consultez Vue d’ensemble des
applications monopage (SPA) dans ASP.NET Core.

Ordre des UseCors et des UseStaticFiles


L’ordre d’appel des UseCors et des UseStaticFiles dépend de l’application. Pour plus
d’informations, consultez Ordre des UseCors et des UseStaticFiles

Ordre du middleware Forwarded Headers


L’intergiciel d’en-têtes transférés doit s’exécuter avant tout autre intergiciel. Cet ordre
permet au middleware qui repose sur les informations des en-têtes transférés d’utiliser
les valeurs d’en-tête pour le traitement. Pour exécuter le middleware Forwarded Headers
après les middlewares Diagnostics et Error Handling, consultez Ordre du middleware
Forwarded Headers.

Créer une branche dans le pipeline de


middlewares
Les extensions Map sont utilisées comme convention pour créer une branche dans le
pipeline. Map crée une branche dans le pipeline de requête en fonction des
correspondances du chemin de requête donné. Si le chemin de requête commence par
le chemin donné, la branche est exécutée.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.Map("/map1", HandleMapTest1);

app.Map("/map2", HandleMapTest2);

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMapTest1(IApplicationBuilder app)


{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}

static void HandleMapTest2(IApplicationBuilder app)


{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
}

Le tableau suivant présente les requêtes et les réponses de http://localhost:1234 avec


le code précédent.
Requête Response

localhost:1234 Hello from non-Map delegate.

localhost:1234/map1 Map Test 1

localhost:1234/map2 Map Test 2

localhost:1234/map3 Hello from non-Map delegate.

Quand Map est utilisé, les segments de chemin mis en correspondance sont supprimés
de HttpRequest.Path et ajoutés à HttpRequest.PathBase pour chaque requête.

Map prend en charge l’imbrication, par exemple :

C#

app.Map("/level1", level1App => {


level1App.Map("/level2a", level2AApp => {
// "/level1/level2a" processing
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b" processing
});
});

Map peut également faire correspondre plusieurs segments à la fois :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.Map("/map1/seg1", HandleMultiSeg);

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleMultiSeg(IApplicationBuilder app)


{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}
MapWhen crée une branche dans le pipeline de requête en fonction du résultat du
prédicat donné. Un prédicat de type Func<HttpContext, bool> peut être utilisé pour
mapper les requêtes à une nouvelle branche du pipeline. Dans l’exemple suivant, un
prédicat est utilisé pour détecter la présence d’une variable de chaîne de requête
branch :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapWhen(context => context.Request.Query.ContainsKey("branch"),


HandleBranch);

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

static void HandleBranch(IApplicationBuilder app)


{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch used = {branchVer}");
});
}

Le tableau suivant présente les requêtes et les réponses de http://localhost:1234 avec


le code précédent :

Requête Response

localhost:1234 Hello from non-Map delegate.

localhost:1234/?branch=main Branch used = main

UseWhen crée également une branche dans le pipeline de requête en fonction du


résultat du prédicat donné. Contrairement à lorsque vous utilisez MapWhen , cette branche
est rejointe au pipeline principal si elle ne court-circuite pas ou ne contient pas un
middleware terminal :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();
app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
appBuilder => HandleBranchAndRejoin(appBuilder));

app.Run(async context =>


{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});

app.Run();

void HandleBranchAndRejoin(IApplicationBuilder app)


{
var logger =
app.ApplicationServices.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>


{
var branchVer = context.Request.Query["branch"];
logger.LogInformation("Branch used = {branchVer}", branchVer);

// Do work that doesn't write to the Response.


await next();
// Do other work that doesn't write to the Response.
});
}

Dans l’exemple précédent, une réponse Hello from non-Map delegate. est écrite pour
toutes les requêtes. Si la requête inclut une variable de chaîne de requête branch , sa
valeur est journalisée avant la jonction au pipeline principal.

Intergiciels (middleware) intégrés


ASP.NET Core est fourni avec les composants de middleware suivant. La colonne Ordre
fournit des notes sur l’emplacement du middleware dans le pipeline de traitement de la
requête et sur les conditions dans lesquelles le middleware peut mettre fin au traitement
de la requête. Lorsqu’un middleware court-circuite le pipeline de traitement de la
requête et empêche tout middleware en aval de traiter une requête, on parle de
middleware terminal. Pour plus d’informations sur le court-circuit, consultez la section
Créer un pipeline de middlewares avec WebApplication.

Intergiciel Description Commande


(middleware)

Authentification Prend en charge Avant que HttpContext.User ne soit


l’authentification. nécessaire. Terminal pour les rappels OAuth.
Intergiciel Description Commande
(middleware)

Autorisation Fournit la prise en Immédiatement après le middleware


charge des Authentication.
autorisations.

Cookie Policy Effectue le suivi de Avant le middleware qui émet des cookie.
consentement des Exemples : authentification, session, MVC
utilisateurs pour le (TempData).
stockage des
informations
personnelles et
applique des
standards minimaux
pour les champs
cookie, comme
secure et SameSite .

CORS Configure le partage Avant les composants qui utilisent CORS.


des ressources cross- UseCors doit se trouver avant
origin (CORS). UseResponseCaching en raison de ce
bogue .

DeveloperExceptionPage Génère une page Avant les composants qui génèrent des
contenant des erreurs. Les modèles de projet inscrivent
informations d’erreur automatiquement ce middleware comme
destinées à être premier middleware dans le pipeline lorsque
utilisées uniquement l’environnement est un environnement de
dans l’environnement développement.
de développement.

Diagnostics Plusieurs middlewares Avant les composants qui génèrent des


distincts qui erreurs. Terminal pour les exceptions ou la
fournissent une page distribution de la page web par défaut pour
d’exceptions du les nouvelles applications.
développeur, la
gestion des
exceptions, les pages
de codes d’état et la
page web par défaut
pour les nouvelles
applications.

En-têtes transférés Transfère les en-têtes Avant les composants qui consomment les
en proxy vers la champs mis à jour. Exemples : schéma, hôte,
requête actuelle. IP du client, méthode.

Contrôle d’intégrité Contrôle l’intégrité Terminal si une requête correspond à un


d’une application point de terminaison de contrôle d’intégrité.
Intergiciel Description Commande
(middleware)

ASP.NET Core et de
ses dépendances,
notamment la
disponibilité de la
base de données.

Propagation des en- Propage les en-


têtes têtes HTTP de la
requête entrante vers
les requêtes sortantes
du client HTTP.

Journalisation HTTP Journalise les requêtes Au début du pipeline de middlewares.


et les réponses HTTP.

Remplacement de la Autorise une requête Avant les composants qui consomment la


méthode HTTP POST entrante à méthode mise à jour.
remplacer la méthode.

Redirection HTTPS Redirige toutes les Avant les composants qui consomment
requêtes HTTP vers l’URL.
HTTPS.

HSTS (HTTP Strict Middleware Avant l’envoi des réponses et après les
Transport Security) d’amélioration de la composants qui modifient les requêtes.
sécurité qui ajoute un Exemples : en-têtes transférés, réécriture
en-tête de réponse d’URL.
spécial.

MVC Traite les requêtes Terminal si une requête correspond à un


avec MVC/Razor itinéraire.
Pages.

OWIN Interopérabilité avec Terminal si le middleware OWIN traite


le middleware, les entièrement la requête.
serveurs et les
applications OWIN.

Mise en cache de sortie Prend en charge la Avant les composants qui nécessitent la mise
mise en cache des en cache. UseRouting doit se situer avant
réponses en fonction UseOutputCaching . UseCORS doit se situer
de la configuration. avant UseOutputCaching .

Mise en cache des Prend en charge la Avant les composants qui nécessitent la mise
réponses mise en cache des en cache. UseCORS doit se situer avant
réponses. Nécessite la UseResponseCaching . N’est généralement pas
participation du client. bénéfique pour les applications d’interface
Utilisez la mise en utilisateur telles que Razor Pages, car les
Intergiciel Description Commande
(middleware)

cache de sortie pour navigateurs définissent habituellement des


un contrôle serveur en-têtes de requête qui empêchent la mise
complet. en cache. La mise en cache de sortie est utile
pour les applications d’interface utilisateur.

Décompression des Prend en charge la Avant les composants qui lisent le corps des
requêtes décompression des requêtes.
requêtes.

Compression des Prend en charge la Avant les composants qui nécessitent la


réponses compression des compression.
réponses.

Localisation des Prend en charge la Avant la localisation des composants


requêtes localisation. sensibles. Doit se situer après le middleware
Routing lorsque vous utilisez
RouteDataRequestCultureProvider.

Délais d'attente des tests Permet de configurer UseRequestTimeouts doit succéder à


les délais d'attente UseExceptionHandler ,
des requêtes, global UseDeveloperExceptionPage et UseRouting .
et par point
d'extrémité.

Routage de point de Définit et contraint Terminal pour les routes correspondantes.


terminaison des routes de requête.

SPA Gère toutes les Plus loin dans la chaîne, afin que les autres
requêtes issues de ce middlewares permettant de distribuer des
point dans la chaîne fichiers statiques, des actions MVC, etc.,
de middleware en soient prioritaires.
retournant la page par
défaut de l’application
monopage

Session Prend en charge la Avant les composants qui nécessitent la


gestion des sessions session.
utilisateur.

Fichiers statiques Prend en charge le Terminal si une requête correspond à un


traitement des fichiers fichier.
statiques et
l’exploration des
répertoires.

URL Rewrite Prend en charge la Avant les composants qui consomment


réécriture d’URL et la l’URL.
Intergiciel Description Commande
(middleware)

redirection des
requêtes.

W3CLogging Génère les journaux Au début du pipeline de middlewares.


d’accès au serveur au
format de fichier
journal étendu
W3C .

WebSockets Autorise le protocole Avant les composants qui sont nécessaires


WebSockets. pour accepter les requêtes WebSocket.

Ressources supplémentaires
Les options de durée de vie et d’inscription contiennent un exemple complet de
middleware avec des services de durée de vie délimitée, temporaire et singleton.
Écrire un intergiciel (middleware) ASP.NET Core personnalisé
Tester les middlewares ASP.NET Core
Configurer gRPC-Web dans ASP.NET Core
Migrer des gestionnaires et des modules HTTP vers des middlewares ASP.NET Core
Démarrage d’une application dans ASP.NET Core
Fonctionnalités de requête dans ASP.NET Core
Activation d’un intergiciel (middleware) basé sur une fabrique dans ASP.NET Core
Activation d’un intergiciel (middleware) avec un conteneur tiers dans ASP.NET Core

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Intergiciel de réécriture d’URL dans
ASP.NET Core
Article • 30/11/2023

Par Arvin Kahbazi , Maarten Balliauw et Rick Anderson

L’intergiciel Microsoft.AspNetCore.RateLimiting fournit un intergiciel de limitation de


débit. Les applications configurent des stratégies de limitation du débit, puis attachent
les stratégies aux points de terminaison. Les applications utilisant la limitation de débit
doivent être soigneusement testées et examinées avant le déploiement. Pour plus
d’informations, consultez Test des points de terminaison avec limitation de débit.

Algorithmes de limiteur de débit


La classe RateLimiterOptionsExtensions fournit les méthodes d’extension suivantes pour
la limitation du débit :

Fenêtre fixe
Fenêtre glissante
Compartiment de jetons
Concurrence

Limiteur de fenêtre fixe


La méthode AddFixedWindowLimiter utilise une fenêtre de temps fixe pour limiter les
requêtes. Lorsque la fenêtre de temps expire, une nouvelle fenêtre de temps démarre et
la limite de requête est réinitialisée.

Prenez le code suivant :

C#

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: "fixed", options =>
{
options.PermitLimit = 4;
options.Window = TimeSpan.FromSeconds(12);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 2;
}));

var app = builder.Build();

app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks &


0x11111).ToString("00000");

app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}"))


.RequireRateLimiting("fixed");

app.Run();

Le code précédent :

Appelle AddRateLimiter pour ajouter un service de limitation de débit à la


collection de services.
Appelle AddFixedWindowLimiter pour créer un limiteur de fenêtre fixe avec un nom
de stratégie de "fixed" et définit :
PermitLimit sur 4 et le temps Window sur 12. Un maximum de 4 requêtes pour
chaque fenêtre de 12 secondes est autorisé.
QueueProcessingOrder en OldestFirst.
QueueLimit sur 2.
Appelle UseRateLimiter pour activer la limitation du débit.

Les applications doivent utiliser Configuration pour définir les options de limiteur. Le
code suivant met à jour le code précédent à l’aide de MyRateLimitOptions pour la
configuration :

C#

using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);


builder.Services.Configure<MyRateLimitOptions>(
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));

var myOptions = new MyRateLimitOptions();


builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOpti
ons);
var fixedPolicy = "fixed";

builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: fixedPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));

var app = builder.Build();

app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks &


0x11111).ToString("00000");

app.MapGet("/", () => Results.Ok($"Fixed Window Limiter {GetTicks()}"))


.RequireRateLimiting(fixedPolicy);

app.Run();

UseRateLimiter doit être appelé après UseRouting lorsque des API spécifiques au point
de terminaison de limitation de débit sont utilisées. Par exemple, si l’attribut
[EnableRateLimiting] est utilisé, UseRateLimiter doit être appelé après UseRouting .
Lorsque vous appelez uniquement des limiteurs globaux, UseRateLimiter peut être
appelé avant UseRouting .

Limiteur à fenêtre glissante


Un algorithme à fenêtre glissante :

Est similaire au limiteur de fenêtre fixe, mais avec des segments par fenêtre. La
fenêtre fait glisser un segment par intervalle de segment. L’intervalle de segment
est (heure de la fenêtre)/(segments par fenêtre).
Limite les requêtes d’une fenêtre à permitLimit requêtes.
Chaque fenêtre de temps est divisée en n segments par fenêtre.
Les requêtes extraites du segment de temps expiré d’une fenêtre plus tôt
( n segments avant le segment actuel) sont ajoutées au segment actuel. Nous
faisons référence au segment de temps le plus expiré d’une fenêtre en arrière en
tant que segment expiré.

Considérez le tableau suivant qui montre un limiteur à fenêtre glissante avec une fenêtre
de 30 secondes, trois segments par fenêtre et une limite de 100 requêtes :

La ligne supérieure et la première colonne affichent le segment de temps.


La deuxième ligne indique les requêtes restantes disponibles. Les requêtes
restantes sont calculées en soustrayant aux demandes disponibles les demandes
traitées et en ajoutant les demandes recyclées.
Les requêtes se déplacent à chaque fois le long de la ligne bleue diagonale.
À partir de l’heure 30, la requête issue du segment de temps expiré est ajoutée à la
limite de requête, comme indiqué dans les lignes rouges.

Le tableau suivant montre les données du graphique précédent dans un autre format. La
colonne Disponible affiche les requêtes disponibles à partir du segment précédent
(Report de la ligne précédente). La première ligne indique 100 requêtes disponibles, car
il n’y a pas de segment précédent.

Temps Disponible Pris Recyclé à partir des expirées Report

0 100 20 0 80

10 80 30 0 50

20 50 40 0 10

30 10 30 20 0

40 0 10 30 20

50 20 10 40 50

60 50 35 30 45

Le code suivant utilise le limiteur de débit à fenêtre glissante :

C#

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);

var myOptions = new MyRateLimitOptions();


builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOpti
ons);
var slidingPolicy = "sliding";

builder.Services.AddRateLimiter(_ => _
.AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));

var app = builder.Build();

app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks &


0x11111).ToString("00000");

app.MapGet("/", () => Results.Ok($"Sliding Window Limiter {GetTicks()}"))


.RequireRateLimiting(slidingPolicy);

app.Run();

Limiteur de compartiment de jetons


Le limiteur de compartiment de jetons est similaire au limiteur à fenêtre glissante, mais
au lieu d’ajouter en arrière les requêtes prises à partir du segment expiré, un nombre
fixe de jetons est ajouté à chaque période de réapprovisionnement. Les jetons ajoutés à
chaque segment ne peuvent pas augmenter les jetons disponibles à un nombre
supérieur à la limite du compartiment de jetons. Le tableau suivant montre un limiteur
de compartiment de jetons avec une limite de 100 jetons et une période de
réapprovisionnement de 10 secondes.

Temps Disponible Pris Ajouté(e) Report

0 100 20 0 80

10 80 10 20 90

20 90 5 15 100

30 100 30 20 90
Temps Disponible Pris Ajouté(e) Report

40 90 6 16 100

50 100 40 20 80

60 80 50 20 50

Le code suivant utilise le limiteur de compartiment de jetons :

C#

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

var tokenPolicy = "token";


var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOpti
ons);

builder.Services.AddRateLimiter(_ => _
.AddTokenBucketLimiter(policyName: tokenPolicy, options =>
{
options.TokenLimit = myOptions.TokenLimit;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
options.ReplenishmentPeriod =
TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod);
options.TokensPerPeriod = myOptions.TokensPerPeriod;
options.AutoReplenishment = myOptions.AutoReplenishment;
}));

var app = builder.Build();

app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks &


0x11111).ToString("00000");

app.MapGet("/", () => Results.Ok($"Token Limiter {GetTicks()}"))


.RequireRateLimiting(tokenPolicy);

app.Run();

Quand AutoReplenishment est défini sur true , un minuteur interne réapprovisionne les
jetons tous les ReplenishmentPeriod. Quand il est défini sur false , l’application doit
appeler TryReplenish sur le limiteur.
Limiteur d’accès concurrentiel
Le limiteur d’accès concurrentiel limite le nombre de requêtes simultanées. Chaque
requête réduit la limite d’accès concurrentiel de un. Lorsqu’une requête se termine, la
limite est augmentée de un. Contrairement aux autres limiteurs de requêtes qui limitent
le nombre total de requêtes pour une période spécifiée, le limiteur d’accès concurrentiel
limite uniquement le nombre de requêtes simultanées et ne limite pas le nombre de
requêtes dans une période donnée.

Le code suivant utilise le limiteur d’accès concurrentiel :

C#

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

var concurrencyPolicy = "Concurrency";


var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOpti
ons);

builder.Services.AddRateLimiter(_ => _
.AddConcurrencyLimiter(policyName: concurrencyPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));

var app = builder.Build();

app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks &


0x11111).ToString("00000");

app.MapGet("/", async () =>


{
await Task.Delay(500);
return Results.Ok($"Concurrency Limiter {GetTicks()}");

}).RequireRateLimiting(concurrencyPolicy);

app.Run();

Créer des limiteurs chaînés


L’API CreateChained permet de passer plusieurs PartitionedRateLimiter qui sont
combinés en un seul PartitionedRateLimiter . Le limiteur combiné exécute tous les
limiteurs d’entrée dans l’ordre.

Le code suivant utilise CreateChained :

C#

using System.Globalization;
using System.Threading.RateLimiting;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRateLimiter(_ =>
{
_.OnRejected = (context, _) =>
{
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var
retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((int)
retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo);
}

context.HttpContext.Response.StatusCode =
StatusCodes.Status429TooManyRequests;
context.HttpContext.Response.WriteAsync("Too many requests. Please
try again later.");

return new ValueTask();


};
_.GlobalLimiter = PartitionedRateLimiter.CreateChained(
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userAgent =
httpContext.Request.Headers.UserAgent.ToString();

return RateLimitPartition.GetFixedWindowLimiter
(userAgent, _ =>
new FixedWindowRateLimiterOptions
{
AutoReplenishment = true,
PermitLimit = 4,
Window = TimeSpan.FromSeconds(2)
});
}),
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userAgent =
httpContext.Request.Headers.UserAgent.ToString();

return RateLimitPartition.GetFixedWindowLimiter
(userAgent, _ =>
new FixedWindowRateLimiterOptions
{
AutoReplenishment = true,
PermitLimit = 20,
Window = TimeSpan.FromSeconds(30)
});
}));
});

var app = builder.Build();


app.UseRateLimiter();

static string GetTicks() => (DateTime.Now.Ticks &


0x11111).ToString("00000");

app.MapGet("/", () => Results.Ok($"Hello {GetTicks()}"));

app.Run();

Pour plus d’informations, consultez le code source CreateChained

Attributs EnableRateLimiting et
DisableRateLimiting
Les attributs [EnableRateLimiting] et [DisableRateLimiting] peuvent être appliqués à un
contrôleur, à une méthode d’action ou à une page Razor. Pour les pages Razor, l’attribut
doit être appliqué à la page Razor et non aux gestionnaires de pages. Par exemple,
[EnableRateLimiting] ne peut pas être appliqué à OnGet , OnPost ou à tout autre
gestionnaire de page.

L’attribut [DisableRateLimiting] désactive la limitation du débit du contrôleur, de la


méthode d’action ou de la page Razor, quels que soient les limiteurs de débit nommés
ou les limites globales appliquées. Par exemple, considérez le code suivant qui appelle
RequireRateLimiting pour appliquer la limitation de débit fixedPolicy à tous les points
de terminaison de contrôleur :

C#

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyRateLimitOptions>(
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));

var myOptions = new MyRateLimitOptions();


builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOpti
ons);
var fixedPolicy = "fixed";

builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: fixedPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));

var slidingPolicy = "sliding";

builder.Services.AddRateLimiter(_ => _
.AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
{
options.PermitLimit = myOptions.SlidingPermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));

var app = builder.Build();


app.UseRateLimiter();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.MapRazorPages().RequireRateLimiting(slidingPolicy);
app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy);

app.Run();

Dans le code suivant, [DisableRateLimiting] désactive la limitation du débit et les


remplacements appliqués par [EnableRateLimiting("fixed")] au Home2Controller et
app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy) appelés dans

Program.cs :
C#

[EnableRateLimiting("fixed")]
public class Home2Controller : Controller
{
private readonly ILogger<Home2Controller> _logger;

public Home2Controller(ILogger<Home2Controller> logger)


{
_logger = logger;
}

public ActionResult Index()


{
return View();
}

[EnableRateLimiting("sliding")]
public ActionResult Privacy()
{
return View();
}

[DisableRateLimiting]
public ActionResult NoLimit()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None,


NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ??
HttpContext.TraceIdentifier });
}
}

Dans le code précédent, le [EnableRateLimiting("sliding")] n’est pas appliqué à la


méthode d’action Privacy , car Program.cs a appelé
app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy) .

Considérez le code suivant qui n’appelle pas RequireRateLimiting sur MapRazorPages ou


MapDefaultControllerRoute :

C#

using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.Configure<MyRateLimitOptions>(
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));

var myOptions = new MyRateLimitOptions();


builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOpti
ons);
var fixedPolicy = "fixed";

builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: fixedPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));

var slidingPolicy = "sliding";

builder.Services.AddRateLimiter(_ => _
.AddSlidingWindowLimiter(policyName: slidingPolicy, options =>
{
options.PermitLimit = myOptions.SlidingPermitLimit;
options.Window = TimeSpan.FromSeconds(myOptions.Window);
options.SegmentsPerWindow = myOptions.SegmentsPerWindow;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));

var app = builder.Build();

app.UseRateLimiter();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.MapRazorPages();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();
Examinons le contrôleur ci-dessous :

C#

[EnableRateLimiting("fixed")]
public class Home2Controller : Controller
{
private readonly ILogger<Home2Controller> _logger;

public Home2Controller(ILogger<Home2Controller> logger)


{
_logger = logger;
}

public ActionResult Index()


{
return View();
}

[EnableRateLimiting("sliding")]
public ActionResult Privacy()
{
return View();
}

[DisableRateLimiting]
public ActionResult NoLimit()
{
return View();
}

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None,


NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ??
HttpContext.TraceIdentifier });
}
}

Dans le contrôleur précédent :

Le limiteur de taux de stratégie "fixed" est appliqué à toutes les méthodes


d’action qui n’ont pas les attributs EnableRateLimiting et DisableRateLimiting .
Le limiteur de taux de stratégie "sliding" est appliqué à l’action Privacy .
La limitation du débit est désactivée sur la méthode d’action NoLimit .

Application d'attributs à des pages Razor


Pour les pages Razor, l’attribut doit être appliqué à la page Razor et non aux
gestionnaires de pages. Par exemple, [EnableRateLimiting] ne peut pas être appliqué à
OnGet , OnPost ou à tout autre gestionnaire de page.

L’attribut DisableRateLimiting désactive la limitation de débit sur une page Razor.


EnableRateLimiting est appliqué uniquement à une page Razor si

MapRazorPages().RequireRateLimiting(Policy) n’a pas été appelé.

Comparaison d’algorithmes de limiteur


Les limiteurs fixes, glissants et de jetons limitent tous le nombre maximal de requêtes
dans une période donnée. Le limiteur d’accès concurrentiel limite uniquement le
nombre de requêtes simultanées et ne limite pas le nombre de requêtes dans une
période donnée. Le coût d’un point de terminaison doit être pris en compte lors de la
sélection d’un limiteur. Le coût d’un point de terminaison inclut les ressources utilisées,
par exemple, le temps, l’accès aux données, le processeur et les E/S.

Exemples de limiteur de débit


Les exemples suivants ne sont pas destinés au code de production, mais sont des
exemples sur l’utilisation des limiteurs.

Limiteur avec OnRejected , RetryAfter et GlobalLimiter


L’exemple suivant :

Crée un rappel RateLimiterOptions.OnRejected qui est appelé lorsqu’une requête


dépasse la limite spécifiée. retryAfter peut être utilisé avec
TokenBucketRateLimiter , FixedWindowLimiter et SlidingWindowLimiter , car
ces algorithmes sont en mesure d’estimer quand d’autres autorisations seront
ajoutées. ConcurrencyLimiter ne dispose d’aucun moyen de calculer le moment où
les permis seront disponibles.

Ajoute les limiteurs suivants :


SampleRateLimiterPolicy qui implémente l'interface

IRateLimiterPolicy<TPartitionKey> . La classe SampleRateLimiterPolicy est

montrée plus bas dans cet article.


Une SlidingWindowLimiter :
Avec une partition pour chaque utilisateur authentifié.
Une partition partagée pour tous les utilisateurs anonymes.
GlobalLimiter appliqué à toutes les requêtes. Le limiteur global est exécuté en
premier, suivi du limiteur spécifique au point de terminaison, le cas échéant.
GlobalLimiter crée une partition pour chaque IPAddress.

C#

// Preceding code removed for brevity.


using System.Globalization;
using System.Net;
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebRateLimitAuth;
using WebRateLimitAuth.Data;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection") ??
throw new InvalidOperationException("Connection string
'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.Configure<MyRateLimitOptions>(
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var userPolicyName = "user";


var helloPolicy = "hello";
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOpti
ons);

builder.Services.AddRateLimiter(limiterOptions =>
{
limiterOptions.OnRejected = (context, cancellationToken) =>
{
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var
retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((int)
retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo);
}
context.HttpContext.Response.StatusCode =
StatusCodes.Status429TooManyRequests;
context.HttpContext.RequestServices.GetService<ILoggerFactory>()?
.CreateLogger("Microsoft.AspNetCore.RateLimitingMiddleware")
.LogWarning("OnRejected: {GetUserEndPoint}",
GetUserEndPoint(context.HttpContext));

return new ValueTask();


};

limiterOptions.AddPolicy<string, SampleRateLimiterPolicy>(helloPolicy);
limiterOptions.AddPolicy(userPolicyName, context =>
{
var username = "anonymous user";
if (context.User.Identity?.IsAuthenticated is true)
{
username = context.User.ToString()!;
}

return RateLimitPartition.GetSlidingWindowLimiter(username,
_ => new SlidingWindowRateLimiterOptions
{
PermitLimit = myOptions.PermitLimit,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = myOptions.QueueLimit,
Window = TimeSpan.FromSeconds(myOptions.Window),
SegmentsPerWindow = myOptions.SegmentsPerWindow
});

});

limiterOptions.GlobalLimiter =
PartitionedRateLimiter.Create<HttpContext, IPAddress>(context =>
{
IPAddress? remoteIpAddress = context.Connection.RemoteIpAddress;

if (!IPAddress.IsLoopback(remoteIpAddress!))
{
return RateLimitPartition.GetTokenBucketLimiter
(remoteIpAddress!, _ =>
new TokenBucketRateLimiterOptions
{
TokenLimit = myOptions.TokenLimit2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = myOptions.QueueLimit,
ReplenishmentPeriod =
TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
TokensPerPeriod = myOptions.TokensPerPeriod,
AutoReplenishment = myOptions.AutoReplenishment
});
}

return RateLimitPartition.GetNoLimiter(IPAddress.Loopback);
});
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();
app.UseRateLimiter();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages().RequireRateLimiting(userPolicyName);
app.MapDefaultControllerRoute();

static string GetUserEndPoint(HttpContext context) =>


$"User {context.User.Identity?.Name ?? "Anonymous"} endpoint:
{context.Request.Path}"
+ $" {context.Connection.RemoteIpAddress}";
static string GetTicks() => (DateTime.Now.Ticks &
0x11111).ToString("00000");

app.MapGet("/a", (HttpContext context) => $"{GetUserEndPoint(context)}


{GetTicks()}")
.RequireRateLimiting(userPolicyName);

app.MapGet("/b", (HttpContext context) => $"{GetUserEndPoint(context)}


{GetTicks()}")
.RequireRateLimiting(helloPolicy);

app.MapGet("/c", (HttpContext context) => $"{GetUserEndPoint(context)}


{GetTicks()}");

app.Run();

2 Avertissement

La création de partitions sur des adresses IP clientes rend l’application vulnérable


aux attaques par déni de service qui utilisent l’usurpation d’adresses IP source. Pour
plus d’informations, consultez Filtrage d’entrée réseau BCP 38 RFC 2827 : Contrer
les attaques par déni de service (DoS) qui utilisent l’usurpation d’adresses IP
Source .

Consultez le référentiel d’exemples pour connaître le fichier Program.cs complet .

La classe SampleRateLimiterPolicy

C#

using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.Extensions.Options;
using WebRateLimitAuth.Models;

namespace WebRateLimitAuth;

public class SampleRateLimiterPolicy : IRateLimiterPolicy<string>


{
private Func<OnRejectedContext, CancellationToken, ValueTask>?
_onRejected;
private readonly MyRateLimitOptions _options;

public SampleRateLimiterPolicy(ILogger<SampleRateLimiterPolicy> logger,


IOptions<MyRateLimitOptions> options)
{
_onRejected = (ctx, token) =>
{
ctx.HttpContext.Response.StatusCode =
StatusCodes.Status429TooManyRequests;
logger.LogWarning($"Request rejected by
{nameof(SampleRateLimiterPolicy)}");
return ValueTask.CompletedTask;
};
_options = options.Value;
}

public Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected


=> _onRejected;

public RateLimitPartition<string> GetPartition(HttpContext httpContext)


{
return RateLimitPartition.GetSlidingWindowLimiter(string.Empty,
_ => new SlidingWindowRateLimiterOptions
{
PermitLimit = _options.PermitLimit,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = _options.QueueLimit,
Window = TimeSpan.FromSeconds(_options.Window),
SegmentsPerWindow = _options.SegmentsPerWindow
});
}
}

Dans le code précédent, OnRejected utilise OnRejectedContext pour définir l’état de


réponse sur 429 Nombre de requêtes trop élevé . La valeur par défaut de l’état rejeté
est 503 Service indisponible .

Limiteur avec autorisation


L’exemple suivant utilise des JSjetons web ON (JWT) et crée une partition avec le jeton
d’accès JWT . Dans une application de production, le JWT est généralement fourni par
un serveur agissant en tant que service de jeton de sécurité (STS). Pour le
développement local, l’outil en ligne de commande user-jwts dotnet peut être utilisé
pour créer et gérer des JWT locaux spécifiques à l’application.

C#

using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Primitives;
using WebRateLimitAuth.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();
builder.Services.AddAuthentication("Bearer").AddJwtBearer();

var myOptions = new MyRateLimitOptions();


builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOpti
ons);
var jwtPolicyName = "jwt";

builder.Services.AddRateLimiter(limiterOptions =>
{
limiterOptions.RejectionStatusCode =
StatusCodes.Status429TooManyRequests;
limiterOptions.AddPolicy(policyName: jwtPolicyName, partitioner:
httpContext =>
{
var accessToken =
httpContext.Features.Get<IAuthenticateResultFeature>()?

.AuthenticateResult?.Properties?.GetTokenValue("access_token")?.ToString()
?? string.Empty;

if (!StringValues.IsNullOrEmpty(accessToken))
{
return RateLimitPartition.GetTokenBucketLimiter(accessToken, _
=>
new TokenBucketRateLimiterOptions
{
TokenLimit = myOptions.TokenLimit2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = myOptions.QueueLimit,
ReplenishmentPeriod =
TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
TokensPerPeriod = myOptions.TokensPerPeriod,
AutoReplenishment = myOptions.AutoReplenishment
});
}

return RateLimitPartition.GetTokenBucketLimiter("Anon", _ =>


new TokenBucketRateLimiterOptions
{
TokenLimit = myOptions.TokenLimit,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = myOptions.QueueLimit,
ReplenishmentPeriod =
TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
TokensPerPeriod = myOptions.TokensPerPeriod,
AutoReplenishment = true
});
});
});

var app = builder.Build();

app.UseAuthorization();
app.UseRateLimiter();

app.MapGet("/", () => "Hello, World!");

app.MapGet("/jwt", (HttpContext context) => $"Hello


{GetUserEndPointMethod(context)}")
.RequireRateLimiting(jwtPolicyName)
.RequireAuthorization();

app.MapPost("/post", (HttpContext context) => $"Hello


{GetUserEndPointMethod(context)}")
.RequireRateLimiting(jwtPolicyName)
.RequireAuthorization();

app.Run();

static string GetUserEndPointMethod(HttpContext context) =>


$"Hello {context.User.Identity?.Name ?? "Anonymous"} " +
$"Endpoint:{context.Request.Path} Method: {context.Request.Method}";

Limiteur avec ConcurrencyLimiter , TokenBucketRateLimiter


et autorisation
L’exemple suivant :
Ajoute un ConcurrencyLimiter avec un nom de stratégie "get" utilisé sur les pages
Razor.
Ajoute un TokenBucketRateLimiter avec une partition pour chaque utilisateur
autorisé et une partition pour tous les utilisateurs anonymes.
Définit RateLimiterOptions.RejectionStatusCode sur 429 Nombre de requêtes trop
élevé .

C#

var getPolicyName = "get";


var postPolicyName = "post";
var myOptions = new MyRateLimitOptions();
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit).Bind(myOpti
ons);

builder.Services.AddRateLimiter(_ => _
.AddConcurrencyLimiter(policyName: getPolicyName, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
})
.AddPolicy(policyName: postPolicyName, partitioner: httpContext =>
{
string userName = httpContext.User.Identity?.Name ?? string.Empty;

if (!StringValues.IsNullOrEmpty(userName))
{
return RateLimitPartition.GetTokenBucketLimiter(userName, _ =>
new TokenBucketRateLimiterOptions
{
TokenLimit = myOptions.TokenLimit2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = myOptions.QueueLimit,
ReplenishmentPeriod =
TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
TokensPerPeriod = myOptions.TokensPerPeriod,
AutoReplenishment = myOptions.AutoReplenishment
});
}

return RateLimitPartition.GetTokenBucketLimiter("Anon", _ =>


new TokenBucketRateLimiterOptions
{
TokenLimit = myOptions.TokenLimit,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = myOptions.QueueLimit,
ReplenishmentPeriod =
TimeSpan.FromSeconds(myOptions.ReplenishmentPeriod),
TokensPerPeriod = myOptions.TokensPerPeriod,
AutoReplenishment = true
});
}));

Consultez le référentiel d’exemples pour connaître le fichier Program.cs complet .

Test des points de terminaison avec limitation


de débit
Avant de déployer une application à l’aide de la limitation de débit en production,
effectuez un test de contrainte de l’application pour valider les limites de débit et les
options utilisées. Par exemple, créez un script JMeter avec un outil tel que
BlazeMeter ou Apache JMeter HTTP(S) Test Script Recorder et chargez le script dans
Test de charge Azure.

La création de partitions avec une entrée utilisateur rend l’application vulnérable aux
attaques par déni de service (DoS) . Par exemple, la création de partitions sur des
adresses IP clientes rend l’application vulnérable aux attaques par déni de service qui
utilisent l’usurpation d’adresses IP source. Pour plus d’informations, consultez Filtrage
d’entrée réseau BCP 38 RFC 2827 : Contrer les attaques par déni de service (DoS) qui
utilisent l’usurpation d’adresses IP Source .

Ressources supplémentaires
Middleware de limitation de débit par Maarten Balliauw
Limite de débit d’un gestionnaire HTTP dans .NET

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Intergiciel dans les applications API
minimales
Article • 30/11/2023

WebApplication ajoute automatiquement l’intergiciel suivant en fonction de certaines

conditions :

UseDeveloperExceptionPage est ajouté en premier lorsque HostingEnvironment


est "Development" .
UseRouting est ajouté ensuite si le code utilisateur n’a pas déjà appelé UseRouting
et s’il existe des points de terminaison configurés, par exemple app.MapGet .
UseEndpoints est ajouté à la fin du pipeline d’intergiciel si des points de
terminaison sont configurés.
UseAuthentication est ajouté immédiatement après UseRouting , si le code
utilisateur n’a pas déjà appelé UseAuthentication et si
IAuthenticationSchemeProvider peut être détecté dans le fournisseur de services.
IAuthenticationSchemeProvider est ajouté par défaut lors de l’utilisation de

AddAuthentication, et les services sont détectés à l’aide de


IServiceProviderIsService.
UseAuthorization est ajouté après, si le code utilisateur n’a pas déjà appelé
UseAuthorization et si IAuthorizationHandlerProvider peut être détecté dans le

fournisseur de services. IAuthorizationHandlerProvider est ajouté par défaut lors


de l’utilisation de AddAuthorization, et les services sont détectés à l’aide de
IServiceProviderIsService .

Les intergiciels et les points de terminaison configurés par l’utilisateur sont ajoutés
entre UseRouting et UseEndpoints .

Le code suivant est effectivement ce qu’un intergiciel automatique ajouté à l’application


produit :

C#

if (isDevelopment)
{
app.UseDeveloperExceptionPage();
}

app.UseRouting();

if (isAuthenticationConfigured)
{
app.UseAuthentication();
}

if (isAuthorizationConfigured)
{
app.UseAuthorization();
}

// user middleware/endpoints
app.CustomMiddleware(...);
app.MapGet("/", () => "hello world");
// end user middleware/endpoints

app.UseEndpoints(e => {});

Dans certains cas, la configuration de l’intergiciel par défaut n’est pas correcte pour
l’application et exige une modification. Par exemple, UseCors doit être appelé avant
UseAuthentication et UseAuthorization. L’application doit appeler UseAuthentication et
UseAuthorization , si UseCors est appelé :

C#

app.UseCors();
app.UseAuthentication();
app.UseAuthorization();

Si l’intergiciel doit être exécuté avant l’exécution de la correspondance d’itinéraire,


appeler UseRouting et placer l’intergiciel avant l’appel à UseRouting . UseEndpoints n’est
pas obligatoire dans ce cas, car il est automatiquement ajouté comme décrit
précédemment :

C#

app.Use((context, next) =>


{
return next(context);
});

app.UseRouting();

// other middleware and endpoints

Lors de l’ajout d’un intergiciel de terminal :

L’intergiciel doit être ajouté après UseEndpoints .


L’application doit appeler UseRouting et UseEndpoints pour que l’intergiciel de
terminal puisse être placé à l’emplacement approprié.
C#

app.UseRouting();

app.MapGet("/", () => "hello world");

app.UseEndpoints(e => {});

app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});

Un intergiciel de terminal est un intergiciel qui s’exécute si aucun point de terminaison


ne gère la requête.

Pour plus d’informations sur l’intergiciel antifalsification dans les API minimales,
consultez Empêcher les attaques XSRF/CSRF (falsification de requêtes intersites) dans
ASP.NET Core

Pour plus d’informations sur les intergiciels, consultez Intergiciel ASP.NET Core et la liste
des intergiciels intégrés qui peuvent être ajoutés aux applications.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tester les middlewares ASP.NET Core
Article • 12/01/2024

Par Chris Ross

Les intergiciels peuvent être testés isolément avec TestServer. Vous pouvez ainsi :

Instanciez un pipeline d’application contenant uniquement les composants que


vous devez tester.
Envoyez des demandes personnalisées pour vérifier le comportement de
l’intergiciel.

Avantages :

Les requêtes sont envoyées en mémoire au lieu d’être sérialisées sur le réseau.
Cela évite d’autres problèmes, tels que la gestion des ports et les certificats HTTPS.
Les exceptions dans l’intergiciel peuvent être transmises directement au test
appelant.
Il est possible de personnaliser les structures de données du serveur, telles que
HttpContext, directement dans le test.

Configurez TestServer
Dans le projet de test, créez un test :

Générez et démarrez un hôte qui utilise TestServer.

Ajoutez tous les services requis que l’intergiciel utilise.

Ajoutez une référence de package au projet pour le package NuGet


Microsoft.AspNetCore.TestHost .

Configurez le pipeline de traitement pour utiliser l’intergiciel pour le test.

C#

[Fact]
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
{
using var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddMyServices();
})
.Configure(app =>
{
app.UseMiddleware<MyMiddleware>();
});
})
.StartAsync();

...
}

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Envoyer des requêtes avec HttpClient


Envoyez une requête à l’aide de HttpClient :

C#

[Fact]
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
{
using var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddMyServices();
})
.Configure(app =>
{
app.UseMiddleware<MyMiddleware>();
});
})
.StartAsync();

var response = await host.GetTestClient().GetAsync("/");

...
}

Affirmez le résultat. Tout d’abord, faites une assertion à l’opposé du résultat attendu.
Une exécution initiale avec une assertion fausse positive confirme que le test échoue
lorsque l’intergiciel fonctionne correctement. Exécutez le test et confirmez que le test
échoue.

Dans l’exemple suivant, l’intergiciel doit retourner un code d’état 404 (Introuvable)
lorsque le point de terminaison racine est demandé. Effectuez la première série de tests
avec Assert.NotEqual( ... ); , qui doit échouer :

C#

[Fact]
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
{
using var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddMyServices();
})
.Configure(app =>
{
app.UseMiddleware<MyMiddleware>();
});
})
.StartAsync();

var response = await host.GetTestClient().GetAsync("/");

Assert.NotEqual(HttpStatusCode.NotFound, response.StatusCode);
}

Modifiez l’assertion pour tester l’intergiciel dans des conditions de fonctionnement


normales. Le test final utilise Assert.Equal( ... ); . Réexécutez le test pour confirmer
qu’il réussit.

C#
[Fact]
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
{
using var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddMyServices();
})
.Configure(app =>
{
app.UseMiddleware<MyMiddleware>();
});
})
.StartAsync();

var response = await host.GetTestClient().GetAsync("/");

Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

Envoyer des requêtes avec HttpContext


Une application de test peut également envoyer une requête à l’aide de
SendAsync(Action<HttpContext>, CancellationToken). Dans l’exemple suivant, plusieurs
vérifications sont effectuées lorsque https://example.com/A/Path/?and=query est traité
par l’intergiciel :

C#

[Fact]
public async Task TestMiddleware_ExpectedResponse()
{
using var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddMyServices();
})
.Configure(app =>
{
app.UseMiddleware<MyMiddleware>();
});
})
.StartAsync();

var server = host.GetTestServer();


server.BaseAddress = new Uri("https://example.com/A/Path/");

var context = await server.SendAsync(c =>


{
c.Request.Method = HttpMethods.Post;
c.Request.Path = "/and/file.txt";
c.Request.QueryString = new QueryString("?and=query");
});

Assert.True(context.RequestAborted.CanBeCanceled);
Assert.Equal(HttpProtocol.Http11, context.Request.Protocol);
Assert.Equal("POST", context.Request.Method);
Assert.Equal("https", context.Request.Scheme);
Assert.Equal("example.com", context.Request.Host.Value);
Assert.Equal("/A/Path", context.Request.PathBase.Value);
Assert.Equal("/and/file.txt", context.Request.Path.Value);
Assert.Equal("?and=query", context.Request.QueryString.Value);
Assert.NotNull(context.Request.Body);
Assert.NotNull(context.Request.Headers);
Assert.NotNull(context.Response.Headers);
Assert.NotNull(context.Response.Body);
Assert.Equal(404, context.Response.StatusCode);
Assert.Null(context.Features.Get<IHttpResponseFeature>().ReasonPhrase);
}

SendAsync permet la configuration directe d’un objet HttpContext plutôt que d’utiliser
les abstractions HttpClient. Utilisez SendAsync pour manipuler des structures
disponibles uniquement sur le serveur, telles que HttpContext.Items ou
HttpContext.Features.

Comme dans l’exemple précédent qui testait une réponse 404 - Introuvable, vérifiez
l’inverse pour chaque instruction Assert du test précédent. La vérification confirme que
le test échoue correctement lorsque l’intergiciel fonctionne normalement. Une fois que
vous avez confirmé que le test faux positif fonctionne, définissez les instructions finales
Assert pour les conditions et valeurs attendues du test. Réexécutez-le pour confirmer

que le test aboutit.

Ajouter des itinéraires de requête


Des itinéraires supplémentaires peuvent être ajoutés selon la configuration à l’aide du
test HttpClient :

C#
[Fact]
public async Task TestWithEndpoint_ExpectedResponse ()
{
using var host = await new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddRouting();
})
.Configure(app =>
{
app.UseRouting();
app.UseMiddleware<MyMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/hello", () =>
TypedResults.Text("Hello Tests"));
});
});
})
.StartAsync();

var client = host.GetTestClient();

var response = await client.GetAsync("/hello");

Assert.True(response.IsSuccessStatusCode);
var responseBody = await response.Content.ReadAsStringAsync();
Assert.Equal("Hello Tests", responseBody);

Des itinéraires supplémentaires peuvent également être ajoutés à l’aide de l’approche


server.SendAsync .

Limitations de TestServer
TestServer :

A été créé pour répliquer les comportements du serveur afin de tester l’intergiciel.
N’essaie pas de répliquer tous les comportements HttpClient.
Tente de donner au client l’accès à autant de contrôle que possible sur le serveur
et avec autant de visibilité que possible sur ce qui se passe sur le serveur. Par
exemple, il peut lever des exceptions qui ne sont normalement pas levées par
HttpClient pour communiquer directement l’état du serveur.

Ne définit pas certains en-têtes spécifiques au transport par défaut, car ceux-ci ne
sont généralement pas pertinents pour les intergiciels. Pour plus d'informations,
consultez la section suivante.
Ignore la position Stream passée via StreamContent. HttpClient envoie l’intégralité
du flux à partir de la position de départ, même lorsque le positionnement est
défini. Pour plus d’informations, consultez ce problème GitHub .

Les en-têtes Content-Length et Transfer-Encoding


TestServer ne définit pas d’en-têtes de requête ou de réponse liés au transport, tels que
Content-Length ou Transfer-Encoding . Les applications doivent éviter de dépendre
de ces en-têtes, car leur utilisation varie selon le client, le scénario et le protocole. Si
Content-Length et Transfer-Encoding sont nécessaires pour tester un scénario

spécifique, ils peuvent être spécifiés dans le test lors de la composition de


HttpRequestMessage ou HttpContext. Pour plus d’informations, consultez les problèmes
GitHub suivants :

dotnet/aspnetcore#21677
dotnet/aspnetcore#18463
dotnet/aspnetcore#13273

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Intergiciel de mise en cache des
réponses dans ASP.NET Core
Article • 30/11/2023

Par John Luo et Rick Anderson

Cet article explique comment configurer l’intergiciel de mise en cache de sortie dans
une application ASP.NET Core. L’intergiciel détermine quand les réponses peuvent être
mises en cache, stocke les réponses et traite les réponses du cache. Pour une
présentation de la mise en cache HTTP et de l’attribut [ResponseCache] , consultez Mise
en cache des réponses.

L’intergiciel de mise en cache des réponses :

Active la mise en cache des réponses du serveur en fonction des en-têtes de cache
HTTP . Implémente la sémantique de mise en cache HTTP standard. Caches basés
sur des en-têtes de cache HTTP comme les proxys.
N’est généralement pas bénéfique pour les applications d’interface utilisateur
telles que Razor Pages, car les navigateurs définissent habituellement des en-têtes
de requête qui empêchent la mise en cache. La mise en cache de sortie, disponible
dans ASP.NET Core 7.0 et versions ultérieures, bénéficie aux applications
d’interface utilisateur. Avec la mise en cache de sortie, la configuration détermine
ce qui doit être mis en cache indépendamment des en-têtes HTTP.
Peut être utile pour les demandes d’API GET ou HEAD publiques provenant de
clients pour lesquels les conditions de mise en cache sont remplies.

Pour tester la mise en cache des réponses, utilisez Fiddler , Postman ou un autre
outil qui peut définir explicitement des en-têtes de requête. La définition explicite d’en-
têtes est recommandée pour tester la mise en cache. Pour plus d’informations, consultez
la page Dépannage.

Configuration
Dans Program.cs , utilisez AddResponseCaching l’ajout des services d’intergiciel de mise
en cache de réponse pour la collection de services et configurez l’application pour
utiliser l’intergiciel avec la méthode d’extension UseResponseCaching.
UseResponseCaching ajoute l’intergiciel au pipeline de traitement des requêtes en

appelant :

C#
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddResponseCaching();

var app = builder.Build();

app.UseHttpsRedirection();

// UseCors must be called before UseResponseCaching


//app.UseCors();

app.UseResponseCaching();

2 Avertissement

UseCors doit être appelé avant UseResponseCaching lors de l’utilisation de


l’intergiciel CORS.

L’exemple d’application ajoute des en-têtes pour contrôler la mise en cache sur les
requêtes suivantes :

Cache-Control : met en cache les réponses pouvant être mises en cache pendant
une durée maximale de 10 secondes.
Vary : configure le middleware pour servir une réponse mise en cache
uniquement si l’en-tête Accept-Encoding des requêtes suivantes correspond à
celui de la demande d’origine.

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddResponseCaching();

var app = builder.Build();

app.UseHttpsRedirection();

// UseCors must be called before UseResponseCaching


//app.UseCors();

app.UseResponseCaching();
app.Use(async (context, next) =>
{
context.Response.GetTypedHeaders().CacheControl =
new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] =
new string[] { "Accept-Encoding" };

await next();
});

app.MapGet("/", () => DateTime.Now.Millisecond);

app.Run();

Les en-têtes précédents ne sont pas écrits dans la réponse et sont remplacés lorsqu’un
contrôleur, une action ou une page Razor :

A un attribut [ResponseCache]. Cela s’applique même si aucune propriété n’est


définie. Par exemple, l’omission de la propriété VaryByHeader entraîne la
suppression de l’en-tête correspondant de la réponse.

Le middleware de mise en cache de réponse met uniquement en cache les réponses du


serveur qui entraînent un code état 200 (OK). Toutes les autres réponses, y compris les
pages d’erreur, sont ignorées par l’intergiciel.

2 Avertissement

Les réponses avec du contenu pour les clients authentifiés doivent être marquées
comme ne pouvant pas être mises en cache pour empêcher l’intergiciel de stocker
et de traiter ces réponses. Pour plus d’informations sur la façon dont l’intergiciel
détermine si une réponse peut être mise en cache, consultez Conditions de mise
en cache .

Le code précédent ne retourne généralement pas de valeur mise en cache à un


navigateur. Utilisez Fiddler , Postman ou un autre outil qui peut définir explicitement
des en-têtes de requête et qui est préféré pour tester la mise en cache. Pour plus
d'informations, consultez résolution des problèmes dans cet article.

Options
Les options de mise en cache des réponses sont présentées dans le tableau suivant.
Option Description

MaximumBodySize La plus grande taille pouvant être mise en cache pour le corps de la
réponse, en octets. La valeur par défaut est 64 * 1024 * 1024 64 Mo.

SizeLimit Limite de taille de l’intergiciel du cache de réponse en octets. La valeur


par défaut est 100 * 1024 * 1024 100 Mo.

UseCaseSensitivePaths Détermine si les réponses sont mises en cache sur des chemins
respectant la casse. La valeur par défaut est false .

L’exemple suivant configure l’intergiciel pour :

Mettre en cache les réponses avec une taille de corps inférieure ou égale à 1 024
octets.
Stocker les réponses par chemin d’accès respectant la casse. Par exemple, /page1
et /Page1 sont stockés séparément.

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddResponseCaching(options =>
{
options.MaximumBodySize = 1024;
options.UseCaseSensitivePaths = true;
});

var app = builder.Build();

app.UseHttpsRedirection();

// UseCors must be called before UseResponseCaching


//app.UseCors();

app.UseResponseCaching();

app.Use(async (context, next) =>


{
context.Response.GetTypedHeaders().CacheControl =
new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
{
Public = true,
MaxAge = TimeSpan.FromSeconds(10)
};
context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] =
new string[] { "Accept-Encoding" };

await next(context);
});
app.MapGet("/", () => DateTime.Now.Millisecond);

app.Run();

VaryByQueryKeys
Lorsque vous utilisez MVC, des contrôleurs d’API web ou des modèles de page Razor,
l’attribut [ResponseCache] spécifie les paramètres nécessaires pour définir les en-têtes
appropriés pour la mise en cache des réponses. Le seul paramètre de l’attribut
[ResponseCache] qui nécessite strictement l’intergiciel est VaryByQueryKeys, qui ne

correspond pas à un en-tête HTTP réel. Pour plus d’informations, consultez Mise en
cache des réponses dans ASP.NET Core.

Lorsque vous n’utilisez pas l’attribut [ResponseCache] , la mise en cache des réponses
peut être modifiée avec VaryByQueryKeys . Utilisez directement le
ResponseCachingFeature à partir de HttpContext.Features :

C#

var responseCachingFeature =
context.HttpContext.Features.Get<IResponseCachingFeature>();

if (responseCachingFeature != null)
{
responseCachingFeature.VaryByQueryKeys = new[] { "MyKey" };
}

L’utilisation d’une valeur unique égale à * dans VaryByQueryKeys varie le cache par tous
les paramètres de requête.

En-têtes HTTP utilisés par l’intergiciel de mise


en cache de réponse.
Le tableau suivant fournit des informations sur les en-têtes HTTP qui affectent la mise en
cache des réponses.

En-tête Détails

Authorization La réponse n’est pas mise en cache si l’en-tête existe.

Cache-Control L’intergiciel prend uniquement en compte la mise en cache des réponses


marquées avec la directive de cache public . Contrôlez la mise en cache avec les
paramètres suivants :
En-tête Détails

max-age
max-stale†
min-fresh
must-revalidate
no-cache
no-store
only-if-cached
private
public
s-maxage
proxy-revalidate‡

†Si aucune limite n’est spécifiée sur max-stale , l’intergiciel n’effectue aucune
action.
‡ proxy-revalidate produit le même effet que must-revalidate .

Pour plus d’informations, consultez RFC 9111 : Directives de requête .

Pragma Un en-tête Pragma: no-cache dans la requête produit le même effet que Cache-
Control: no-cache . Cet en-tête est remplacé par les directives pertinentes dans
l’en-tête Cache-Control , le cas échéant. Considéré pour la compatibilité
descendante avec HTTP/1.0.

Set-Cookie La réponse n’est pas mise en cache si l’en-tête existe. Tout intergiciel dans le
pipeline de traitement des requêtes qui définit un ou plusieurs cookies empêche
l’intergiciel de mise en cache de réponse de mettre en cache la réponse (par
exemple, le fournisseur TempDatacookie basé sur).

Vary L’en-tête Vary est utilisé pour faire varier la réponse mise en cache par un autre
en-tête. Par exemple, mettez en cache les réponses par encodage en incluant
l’en-tête Vary: Accept-Encoding , qui met en cache les réponses pour les
demandes avec des en-têtes Accept-Encoding: gzip et Accept-Encoding:
text/plain séparément. Une réponse avec une valeur d’en-tête de * n’est
jamais stockée.

Expires Une réponse considérée comme obsolète par cet en-tête n’est ni stockée ni
récupérée, sauf si elle est remplacée par d’autres en-têtes Cache-Control .

If-None-Match La réponse complète est servie à partir du cache si la valeur n’est pas * et si le
ETag de la réponse ne correspond à aucune des valeurs fournies. Sinon, une
réponse 304 (non modifiée) est traitée.

If-Modified- Si l’en-tête If-None-Match n’est pas présent, une réponse complète est fournie à
Since partir du cache si la date de réponse mise en cache est plus récente que la
valeur fournie. Sinon, une réponse 304 - Non modifié est traitée.

Date Lors de la distribution à partir du cache, l’en-tête Date est défini par l’intergiciel
s’il n’a pas été fourni dans la réponse d’origine.
En-tête Détails

Content- Lors de la distribution à partir du cache, l’en-tête Content-Length est défini par
Length l’intergiciel s’il n’a pas été fourni dans la réponse d’origine.

Age L’en-tête Age envoyé dans la réponse d’origine est ignoré. L’intergiciel calcule
une nouvelle valeur lors de la distribution d’une réponse mise en cache.

La mise en cache respecte les directives de


requête de Cache-Control
L’intergiciel respecte les règles de RFC 9111 : Mise en cache HTTP (Section 5.2. Cache-
Control) . Les règles nécessitent un cache pour respecter un en-tête Cache-Control
valide envoyé par le client. Un client peut effectuer des requêtes avec une valeur d’en-
tête no-cache et forcer le serveur à générer une nouvelle réponse pour chaque requête.
Actuellement, il n’existe aucun contrôle de développeur sur ce comportement de mise
en cache lors de l’utilisation du middleware, car le middleware respecte la spécification
de mise en cache officielle.

Pour plus de contrôle sur le comportement de mise en cache, explorez d’autres


fonctionnalités de mise en cache de ASP.NET Core. Consultez les rubriques suivantes :

Mettre en cache en mémoire dans ASP.NET Core


Mise en cache distribuée dans ASP.NET Core
Tag Helper Cache dans ASP.NET Core MVC
Tag Helper Cache distribué dans ASP.NET Core

Résolution des problèmes


L’ intergiciel de mise en cache de réponse utilise IMemoryCache, qui a une capacité
limitée. Lorsque la capacité est dépassée, le cache de mémoire est compacté .

Si le comportement de mise en cache ne se déroule pas comme prévu, vérifiez que les
réponses peuvent être mises en cache et qu’elles peuvent être traitées à partir du cache.
Examinez les en-têtes entrants de la requête et les en-têtes sortants de la réponse.
Activez la journalisation pour faciliter le débogage.

Lors du test et de la résolution des problèmes de comportement de mise en cache, un


navigateur définit généralement des en-têtes de requête qui empêchent la mise en
cache. Par exemple, un navigateur peut définir l’en-tête Cache-Control sur no-cache ou
max-age=0 lors de l’actualisation d’une page. Fiddler , Postman et d’autres outils
peuvent définir explicitement des en-têtes de requête et sont préférés pour tester la
mise en cache.

Conditions de mise en cache


La requête doit donner lieu à une réponse du serveur avec un code de statut 200
(OK).
La méthode de requête doit être GET ou HEAD.
L’intergiciel de mise en cache de réponse doit être placé avant les intergiciels qui
nécessitent une mise en cache. Pour plus d’informations, consultez Intergiciel
(middleware) ASP.NET Core.
L’en-tête Authorization ne doit pas être présent.
Les paramètres d’en-tête Cache-Control doivent être valides et la réponse doit être
marquée public et non private .
L’en-tête Pragma: no-cache ne doit pas être présent si l’en-tête Cache-Control
n’est pas présent, car l’en-tête Cache-Control remplace l’en-tête Pragma lorsqu’il
est présent.
L’en-tête Set-Cookie ne doit pas être présent.
Les paramètres d’en-tête Vary doivent être valides et non égaux à * .
La valeur Content-Length d’en-tête (si définie) doit correspondre à la taille du
corps de la réponse.
IHttpSendFileFeature n’est pas utilisé.
La réponse ne doit pas être obsolète comme spécifié par l’en-tête Expires et les
directives de cache max-age et s-maxage .
La mise en mémoire tampon de réponse doit réussir. La taille de la réponse doit
être inférieure à laSizeLimit configurée ou par défaut . La taille de la réponse doit
être inférieure à la MaximumBodySizeconfigurée ou par défaut .
La réponse doit être mise en cache selon RFC 9111 : Mise en cache HTTP . Par
exemple, la directive no-store ne doit pas exister dans les champs d’en-tête de
requête ou de réponse. Pour plus d’informations , consultez RFC 9111 : Mise en
cache HTTP (Section 3 : Stockage des réponses dans les caches ).

7 Notes

Le système Antiforgery pour générer des jetons sécurisés pour empêcher les
attaques CSRF (falsification des requêtes intersites) définit les en-têtes Cache-
Control et Pragma sur no-cache afin que les réponses ne soient pas mises en cache.

Pour plus d’informations sur la désactivation des jetons Antiforgery pour les
éléments de formulaire HTML, consultez Empêcher les attaques par falsification de
requête intersites (XSRF/CSRF) dans ASP.NET Core.

Ressources supplémentaires
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Source GitHub pour IResponseCachingPolicyProvider
Source GitHub pour IResponseCachingPolicyProvider
Démarrage d’une application dans ASP.NET Core
Intergiciel (middleware) ASP.NET Core
Mettre en cache en mémoire dans ASP.NET Core
Mise en cache distribuée dans ASP.NET Core
Détecter les modifications avec des jetons de modification dans ASP.NET Core
Mise en cache des réponses dans ASP.NET Core
Tag Helper Cache dans ASP.NET Core MVC
Tag Helper Cache distribué dans ASP.NET Core

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Écrire un intergiciel (middleware)
ASP.NET Core personnalisé
Article • 30/11/2023

Par Fiyaz Hasan , Rick Anderson et Steve Smith

Un middleware est un logiciel qui est assemblé dans un pipeline d’application pour
gérer les requêtes et les réponses. Même si ASP.NET Core propose une large palette de
composants d’intergiciel (middleware) intégrés, dans certains scénarios, vous voudrez
certainement écrire un intergiciel personnalisé.

Cette rubrique explique comment écrire un intergiciel basé sur une convention. Pour une
approche qui utilise un typage fort et une activation par requête, consultez Activation
d’intergiciels en usine dans ASP.NET Core.

Classe d’intergiciel (middleware)


Les intergiciels sont généralement encapsulés dans une classe et exposés avec une
méthode d’extension. Prenez en compte l’intergiciel inclus suivant, qui définit la culture
de la requête actuelle à partir d’une chaîne de requête :

C#

using System.Globalization;

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.UseHttpsRedirection();

app.Use(async (context, next) =>


{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);

CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}

// Call the next delegate/middleware in the pipeline.


await next(context);
});

app.Run(async (context) =>


{
await context.Response.WriteAsync(
$"CurrentCulture.DisplayName:
{CultureInfo.CurrentCulture.DisplayName}");
});

app.Run();

L’intergiciel inclus mis en évidence ci-dessus est utilisé pour illustrer la création d’un
composant d’intergiciel en appelant Microsoft.AspNetCore.Builder.UseExtensions.Use. La
méthode d’extension Use précédente ajoute un délégué intergiciel défini comme inclus
au pipeline de requête de l’application.

Deux surcharges sont disponibles pour l’extension Use :

La première exige un HttpContext et un Func<Task> . Appelez le Func<Task> sans


aucun paramètre.
La seconde méthode exige un HttpContext et un RequestDelegate. Appelez le
RequestDelegate en passant le HttpContext .

Préfère utiliser la seconde surcharge alors qu’il enregistre deux allocations internes par
requête, qui sont nécessaires lors de l’utilisation de l’autre surcharge.

Testez l’intergiciel en transmettant la culture. Par exemple, demandez


https://localhost:5001/?culture=es-es .

Consultez Globalisation et localisation dans ASP.NET Core pour la prise en charge de la


localisation intégrée d’ASP.NET Core.

Le code suivant déplace le délégué de l’intergiciel dans une classe :

C#

using System.Globalization;

namespace Middleware.Example;

public class RequestCultureMiddleware


{
private readonly RequestDelegate _next;

public RequestCultureMiddleware(RequestDelegate next)


{
_next = next;
}

public async Task InvokeAsync(HttpContext context)


{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);

CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}

// Call the next delegate/middleware in the pipeline.


await _next(context);
}
}

La classe d’intergiciel (middleware) doit inclure :

Un constructeur public avec un paramètre de type RequestDelegate.


Une méthode publique nommée Invoke ou InvokeAsync . Cette méthode doit :
Retournez une Task .
Accepter un premier paramètre de type HttpContext.

Les paramètres supplémentaires pour le constructeur et Invoke / InvokeAsync sont


remplis par Injection de dépendance (DI).

En règle générale, une méthode d’extension est créée pour exposer l’intergiciel via
IApplicationBuilder :

C#

using System.Globalization;

namespace Middleware.Example;

public class RequestCultureMiddleware


{
private readonly RequestDelegate _next;

public RequestCultureMiddleware(RequestDelegate next)


{
_next = next;
}

public async Task InvokeAsync(HttpContext context)


{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);

CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}

// Call the next delegate/middleware in the pipeline.


await _next(context);
}
}

public static class RequestCultureMiddlewareExtensions


{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}

Le code suivant appelle l’intergiciel à partir de Program.cs :

C#

using Middleware.Example;
using System.Globalization;

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.UseHttpsRedirection();

app.UseRequestCulture();

app.Run(async (context) =>


{
await context.Response.WriteAsync(
$"CurrentCulture.DisplayName:
{CultureInfo.CurrentCulture.DisplayName}");
});

app.Run();

Dépendances de l’intergiciel (middleware)


L’intergiciel doit suivre le principe de dépendances explicites en exposant ses
dépendances dans son constructeur. L’intergiciel est construit une fois par durée de vie
d’application.

Les composants de middleware peuvent résoudre leurs dépendances à partir de


l’injection de dépendances à l’aide des paramètres du constructeur. UseMiddleware
peut également accepter des paramètres supplémentaires directement.
Dépendances de l’intergiciel (middleware) par
requête
L’intergiciel est construit au démarrage de l’application et a donc une durée de vie
d’application. Les services de durée de vie étendue utilisés par les constructeurs de
l’intergiciel ne sont pas partagés avec d’autres types injectés par des dépendances lors
de chaque requête. Si vous devez partager un service étendu entre votre intergiciel et
d’autres types, ajoutez ces services à la signature de la méthode InvokeAsync . La
méthode InvokeAsync peut accepter des paramètres supplémentaires qui sont
renseignés par injection de dépendances :

C#

namespace Middleware.Example;

public class MyCustomMiddleware


{
private readonly RequestDelegate _next;

public MyCustomMiddleware(RequestDelegate next)


{
_next = next;
}

// IMessageWriter is injected into InvokeAsync


public async Task InvokeAsync(HttpContext httpContext, IMessageWriter
svc)
{
svc.Write(DateTime.Now.Ticks.ToString());
await _next(httpContext);
}
}

public static class MyCustomMiddlewareExtensions


{
public static IApplicationBuilder UseMyCustomMiddleware(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyCustomMiddleware>();
}
}

Les options de durée de vie et d’inscription contiennent un exemple complet


d’intergiciel avec des services de durée de vie étendue.

Le code suivant est utilisé pour tester l’intergiciel précédent :

C#
using Middleware.Example;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMessageWriter, LoggingMessageWriter>();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseMyCustomMiddleware();

app.MapGet("/", () => "Hello World!");

app.Run();

Interface IMessageWriter et implémentation :

C#

namespace Middleware.Example;

public interface IMessageWriter


{
void Write(string message);
}

public class LoggingMessageWriter : IMessageWriter


{

private readonly ILogger<LoggingMessageWriter> _logger;

public LoggingMessageWriter(ILogger<LoggingMessageWriter> logger) =>


_logger = logger;

public void Write(string message) =>


_logger.LogInformation(message);
}

Ressources supplémentaires
Exemple de code utilisé dans cet article
Source UseExtensions sur GitHub
Les options de durée de vie et d’inscription contiennent un exemple complet de
middleware avec des services de durée de vie délimitée, temporaire et singleton.
PRÉSENTATION APPROFONDIE : COMMENT LE PIPELINE D’INTERGICIELS ASP.NET
CORE EST-IL CRÉÉ ?
Intergiciel (middleware) ASP.NET Core
Tester les middlewares ASP.NET Core
Migrer des gestionnaires et des modules HTTP vers des middlewares ASP.NET Core
Démarrage d’une application dans ASP.NET Core
Fonctionnalités de requête dans ASP.NET Core
Activation d’un intergiciel (middleware) basé sur une fabrique dans ASP.NET Core
Activation d’un intergiciel (middleware) avec un conteneur tiers dans ASP.NET Core

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Opérations de demande et de réponse
dans ASP.NET Core
Article • 30/11/2023

Par Justin Kotalik

Cet article explique comment lire à partir du corps de la demande et écrire dans le corps
de la réponse. Le code pour ces opérations peut être requis lors de l’écriture
d’intergiciels. En dehors de l’écriture d’intergiciels, le code personnalisé n’est
généralement pas requis, car les opérations sont gérées par MVC et Razor Pages.

Il existe deux abstractions pour les corps de la requête et de la réponse : Stream et Pipe.
Pour la lecture des requêtes, HttpRequest.Body est un Streamet HttpRequest.BodyReader
est un PipeReader. Pour l’écriture de réponses, HttpResponse.Body est un Streamet
HttpResponse.BodyWriter est un PipeWriter.

Les pipelines sont recommandés sur les flux. Les flux peuvent être plus faciles à utiliser
pour des opérations simples, mais les pipelines présentent un avantage de
performances et sont plus faciles à utiliser dans la plupart des scénarios. ASP.NET Core
commence à utiliser des pipelines au lieu de flux en interne. Voici quelques exemples :

FormReader

TextReader
TextWriter

HttpResponse.WriteAsync

Les flux ne sont pas supprimés de l’infrastructure. Les flux continuent à être utilisés dans
.NET et de nombreux types de flux n’ont d’équivalents en termes de canal, comme
FileStreams et ResponseCompression .

Exemples de flux
Supposons que votre objectif est de créer un intergiciel qui lit le corps de la requête
dans sa totalité comme une liste de chaînes, avec un fractionnement sur les nouvelles
lignes. Une implémentation simple de flux peut se présenter comme dans l’exemple
suivant :

2 Avertissement

Le code suivant :
Est utilisé pour illustrer les problèmes liés à l’utilisation d’un canal pour lire le
corps de la requête.
N’est pas destiné à être utilisé dans les applications de production.

C#

private async Task<List<string>> GetListOfStringsFromStream(Stream


requestBody)
{
// Build up the request body in a string builder.
StringBuilder builder = new StringBuilder();

// Rent a shared buffer to write the request body into.


byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);

while (true)
{
var bytesRemaining = await requestBody.ReadAsync(buffer, offset: 0,
buffer.Length);
if (bytesRemaining == 0)
{
break;
}

// Append the encoded string into the string builder.


var encodedString = Encoding.UTF8.GetString(buffer, 0,
bytesRemaining);
builder.Append(encodedString);
}

ArrayPool<byte>.Shared.Return(buffer);

var entireRequestBody = builder.ToString();

// Split on \n in the string.


return new List<string>(entireRequestBody.Split("\n"));
}

Si vous souhaitez voir les commentaires de code traduits dans une langue autre que
l’anglais, dites-le nous dans cette discussion GitHub .

Ce code fonctionne, mais il existe certains problèmes :

Avant d’ajouter à StringBuilder , l’exemple crée une autre chaîne ( encodedString )


qui est immédiatement rejetée. Ce processus se produit pour tous les octets dans
le flux, il en résulte une allocation de mémoire supplémentaire de la taille de la
totalité du corps de la demande.
L’exemple lit la chaîne entière avant de fractionner sur les nouvelles lignes. Il est
plus efficace de rechercher les nouvelles lignes dans le tableau d’octets.

Voici un exemple qui résout certains des problèmes précédents :

2 Avertissement

Le code suivant :

Est utilisé pour illustrer les solutions à certains problèmes dans le code
précédent sans résoudre tous les problèmes.
N’est pas destiné à être utilisé dans les applications de production.

C#

private async Task<List<string>>


GetListOfStringsFromStreamMoreEfficient(Stream requestBody)
{
StringBuilder builder = new StringBuilder();
byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
List<string> results = new List<string>();

while (true)
{
var bytesRemaining = await requestBody.ReadAsync(buffer, offset: 0,
buffer.Length);

if (bytesRemaining == 0)
{
results.Add(builder.ToString());
break;
}

// Instead of adding the entire buffer into the StringBuilder


// only add the remainder after the last \n in the array.
var prevIndex = 0;
int index;
while (true)
{
index = Array.IndexOf(buffer, (byte)'\n', prevIndex);
if (index == -1)
{
break;
}

var encodedString = Encoding.UTF8.GetString(buffer, prevIndex,


index - prevIndex);

if (builder.Length > 0)
{
// If there was a remainder in the string buffer, include it
in the next string.
results.Add(builder.Append(encodedString).ToString());
builder.Clear();
}
else
{
results.Add(encodedString);
}

// Skip past last \n


prevIndex = index + 1;
}

var remainingString = Encoding.UTF8.GetString(buffer, prevIndex,


bytesRemaining - prevIndex);
builder.Append(remainingString);
}

ArrayPool<byte>.Shared.Return(buffer);

return results;
}

Cet exemple précédent :

Ne met pas en mémoire tampon le corps entier de la demande dans un


StringBuilder , sauf s’il n’y a pas de caractère de nouvelle ligne.

N’appelle pas Split sur la chaîne.

Toutefois, il existe toujours quelques problèmes :

Si les caractères nouvelle ligne sont épars, une grande partie du corps de la
requête est mis en mémoire tampon dans la chaîne.
Le code continue de créer des chaînes ( remainingString ) et les ajoute à la
mémoire tampon de chaîne, ce qui entraîne une allocation supplémentaire.

Ces problèmes sont réparables, mais le code est de plus en plus compliqué avec peu
d’amélioration. Les pipelines permettent de résoudre ces problèmes avec un code peu
compliqué.

Pipelines
L’exemple suivant indique comment le même scénario peut être géré à l’aide d’un
PipeReader :

C#
private async Task<List<string>> GetListOfStringFromPipe(PipeReader reader)
{
List<string> results = new List<string>();

while (true)
{
ReadResult readResult = await reader.ReadAsync();
var buffer = readResult.Buffer;

SequencePosition? position = null;

do
{
// Look for a EOL in the buffer
position = buffer.PositionOf((byte)'\n');

if (position != null)
{
var readOnlySequence = buffer.Slice(0, position.Value);
AddStringToList(results, in readOnlySequence);

// Skip the line + the \n character (basically position)


buffer = buffer.Slice(buffer.GetPosition(1,
position.Value));
}
}
while (position != null);

if (readResult.IsCompleted && buffer.Length > 0)


{
AddStringToList(results, in buffer);
}

reader.AdvanceTo(buffer.Start, buffer.End);

// At this point, buffer will be updated to point one byte after the
last
// \n character.
if (readResult.IsCompleted)
{
break;
}
}

return results;
}

private static void AddStringToList(List<string> results, in


ReadOnlySequence<byte> readOnlySequence)
{
// Separate method because Span/ReadOnlySpan cannot be used in async
methods
ReadOnlySpan<byte> span = readOnlySequence.IsSingleSegment ?
readOnlySequence.First.Span : readOnlySequence.ToArray().AsSpan();
results.Add(Encoding.UTF8.GetString(span));
}

Cet exemple résout de nombreux problèmes trouvés dans les implémentations de flux :

Une mémoire tampon de chaîne est inutile, car le PipeReader gère des octets qui
n’ont pas été utilisés.
Les chaînes codées sont ajoutées directement à la liste des chaînes retournées.
À l’exception de l’appel ToArray , et de la mémoire utilisée par la chaîne, la création
de chaînes est libre d’allocation.

Adaptateurs
Les propriétés Body , BodyReader et BodyWriter sont disponibles pour HttpRequest et
HttpResponse . Lorsque vous définissez Body sur un autre flux, un nouvel ensemble

d’adaptateurs adapte automatiquement chaque type à l’autre. Si vous définissez


HttpRequest.Body sur un nouveau flux, HttpRequest.BodyReader est automatiquement

défini sur un nouveau PipeReader qui enveloppe HttpRequest.Body .

StartAsync
HttpResponse.StartAsync est utilisé pour indiquer que les en-têtes sont non modifiables

et pour exécuter des rappels OnStarting . Lors de l’utilisation de Kestrel en tant que
serveur, l’appel de StartAsync avant d’utiliser le PipeReader garantit que la mémoire
retournée par GetMemory appartiendra au Pipe interne de Kestrel au lieu d’une mémoire
tampon externe.

Ressources supplémentaires
System.IO.Pipelines dans .NET
Écrire un intergiciel (middleware) ASP.NET Core personnalisé

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus
documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Décompression de la requête dans
ASP.NET Core
Article • 30/11/2023

Par David Acker

Intergiciel de décompression de requête :

Permet aux points de terminaison d’API d’accepter les requêtes avec du contenu
compressé.
Utilise l’en-tête HTTP Content-Encoding pour identifier et décompresser
automatiquement les requêtes qui contiennent du contenu compressé.
Élimine la nécessité d’écrire du code pour gérer les requêtes compressées.

Lorsque la valeur d’en-tête Content-Encoding d’une requête correspond à l’un des


fournisseurs de décompression disponibles, l’intergiciel :

Utilise le fournisseur de correspondance pour encapsuler le HttpRequest.Body


dans un flux de décompression approprié.
Supprime l’en-tête Content-Encoding , indiquant que le corps de la requête n’est
plus compressé.

Les demandes qui n’incluent pas d’en-tête Content-Encoding sont ignorées par
l’intergiciel de décompression des requêtes.

Décompression :

Se produit lorsque le corps de la requête est lu. Autrement dit, la décompression


se produit au niveau du point de terminaison sur la liaison de données. Le corps de
la demande n’est pas compressé avec impatience.
Lorsque vous tentez de lire le corps de la demande compressée avec des données
compressées non valides pour le Content-Encoding spécifié, une exception est
levée. Brotli peut lever System.InvalidOperationException: Decoder ran into invalid
data. Deflate et GZip peuvent lever System.IO.InvalidDataException: The archive
entry was compressed using an unsupported compression method.

Si l’intergiciel rencontre une requête avec du contenu compressé mais ne peut pas la
décompresser, la demande est transmise au délégué suivant dans le pipeline. Par
exemple, une requête avec une valeur d’en-tête Content-Encoding non prise en charge
ou plusieurs valeurs d’en-tête Content-Encoding est transmise au délégué suivant dans
le pipeline.
Configuration
Le code suivant montre comment activer la décompression des requêtes pour les types
par défaut Content-Encoding :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRequestDecompression();

var app = builder.Build();

app.UseRequestDecompression();

app.MapPost("/", (HttpRequest request) => Results.Stream(request.Body));

app.Run();

Fournisseurs de décompression par défaut


Les valeurs d’en-tête Content-Encoding que l’intergiciel de décompression de requêtes
prend en charge par défaut sont répertoriées dans le tableau suivant :

Valeurs d’en-tête Content-Encoding Description

br Format de données compressées Brotli

deflate Format de données compressées DEFLATE

gzip Format de fichier Gzip

Fournisseurs de décompression personnalisée


Il est possible d’ajouter la prise en charge des encodages personnalisés en créant des
classes de fournisseurs de décompression personnalisée qui implémentent
IDecompressionProvider :

C#

public class CustomDecompressionProvider : IDecompressionProvider


{
public Stream GetDecompressionStream(Stream stream)
{
// Perform custom decompression logic here
return stream;
}
}

Les fournisseurs de décompression personnalisée sont inscrits avec


RequestDecompressionOptions et leurs valeurs d’en-tête Content-Encoding
correspondantes :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRequestDecompression(options =>
{
options.DecompressionProviders.Add("custom", new
CustomDecompressionProvider());
});

var app = builder.Build();

app.UseRequestDecompression();

app.MapPost("/", (HttpRequest request) => Results.Stream(request.Body));

app.Run();

Limites de la taille des demandes


Afin de se protéger contre les bombes zip ou les bombes de décompression :

La taille maximale du corps de la requête décompressée est limitée à la limite de


taille du corps de la demande appliquée par le point de terminaison ou le serveur.
Si le nombre d’octets lus à partir du flux de corps de requête décompressé
dépasse la limite, une exception InvalidOperationException est levée pour
empêcher la lecture d’octets supplémentaires à partir du flux.

Dans l’ordre de priorité, la taille maximale des requêtes pour un point de terminaison
est définie par :

1. IRequestSizeLimitMetadata.MaxRequestBodySize, comme
RequestSizeLimitAttribute ou DisableRequestSizeLimitAttribute pour les points de
terminaison MVC.
2. Limite de taille IHttpMaxRequestBodySizeFeature.MaxRequestBodySize de serveur
globale. MaxRequestBodySize peut être remplacé par requête avec
IHttpMaxRequestBodySizeFeature.MaxRequestBodySize, mais la limite configurée
par défaut pour l’implémentation du serveur web est définie par défaut.

Implémentation de serveur web Configuration MaxRequestBodySize

HTTP.sys HttpSysOptions.MaxRequestBodySize

IIS IISServerOptions.MaxRequestBodySize

Kestrel KestrelServerLimits.MaxRequestBodySize

2 Avertissement

La désactivation de la limite de taille du corps de la requête présente un risque de


sécurité en ce qui concerne la consommation non contrôlée des ressources, en
particulier si le corps de la demande est mis en mémoire tampon. Assurez-vous que
des protections sont en place pour atténuer le risque d’attaques par déni de
service (DoS).

Ressources supplémentaires
Intergiciel (middleware) ASP.NET Core
Réseau des développeurs Mozilla : encodage de contenu
Format de données compressées Brotli
Spécification du format de données compressées DEFLATE version 1.3
Spécification du format de fichier GZIP version 4.3

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Activation d’un intergiciel (middleware)
basé sur une fabrique dans ASP.NET
Core
Article • 30/11/2023

IMiddlewareFactory/IMiddleware est un point d’extensibilité pour l’activation


d’intergiciel qui offre les avantages suivants :

Activation par requête de client (injection de services délimités)


Typage fort du middleware

Les méthodes d’extension de UseMiddleware vérifient si le type inscrit d’un middleware


implémente IMiddleware. Si c’est le cas, l’instance de IMiddlewareFactory inscrite dans le
conteneur est utilisée pour résoudre l’implémentation de IMiddleware, au lieu de la
logique d’activation de middleware basée sur une convention. Le middleware est inscrit
comme service délimité ou temporaire dans le conteneur de service de l’application.

IMiddleware est activé par requête de client (connexion) : des services délimités peuvent
ainsi être injectés dans le constructeur du middleware.

IMiddleware
IMiddleware définit le middleware pour le pipeline des requêtes de l’application. La
méthode InvokeAsync(HttpContext, RequestDelegate) gère les requêtes et retourne un
élément Task qui représente l’exécution du middleware.

Middleware activé par convention :

C#

public class ConventionalMiddleware


{
private readonly RequestDelegate _next;

public ConventionalMiddleware(RequestDelegate next)


=> _next = next;

public async Task InvokeAsync(HttpContext context, SampleDbContext


dbContext)
{
var keyValue = context.Request.Query["key"];

if (!string.IsNullOrWhiteSpace(keyValue))
{
dbContext.Requests.Add(new Request("Conventional", keyValue));

await dbContext.SaveChangesAsync();
}

await _next(context);
}
}

Middleware activé par MiddlewareFactory :

C#

public class FactoryActivatedMiddleware : IMiddleware


{
private readonly SampleDbContext _dbContext;

public FactoryActivatedMiddleware(SampleDbContext dbContext)


=> _dbContext = dbContext;

public async Task InvokeAsync(HttpContext context, RequestDelegate next)


{
var keyValue = context.Request.Query["key"];

if (!string.IsNullOrWhiteSpace(keyValue))
{
_dbContext.Requests.Add(new Request("Factory", keyValue));

await _dbContext.SaveChangesAsync();
}

await next(context);
}
}

Des extensions sont créées pour les intergiciels :

C#

public static class MiddlewareExtensions


{
public static IApplicationBuilder UseConventionalMiddleware(
this IApplicationBuilder app)
=> app.UseMiddleware<ConventionalMiddleware>();

public static IApplicationBuilder UseFactoryActivatedMiddleware(


this IApplicationBuilder app)
=> app.UseMiddleware<FactoryActivatedMiddleware>();
}
Il n’est pas possible de passer des objets au middleware activé par fabrique avec
UseMiddleware :

C#

public static IApplicationBuilder UseFactoryActivatedMiddleware(


this IApplicationBuilder app, bool option)
{
// Passing 'option' as an argument throws a NotSupportedException at
runtime.
return app.UseMiddleware<FactoryActivatedMiddleware>(option);
}

Le middleware activé par fabrique est ajouté au conteneur intégré dans Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<SampleDbContext>
(options => options.UseInMemoryDatabase("SampleDb"));

builder.Services.AddTransient<FactoryActivatedMiddleware>();

Les deux intergiciels sont inscrits dans le pipeline de traitement des requêtes, également
dans Program.cs :

C#

var app = builder.Build();

app.UseConventionalMiddleware();
app.UseFactoryActivatedMiddleware();

IMiddlewareFactory
IMiddlewareFactory fournit des méthodes pour créer un middleware. L’implémentation
de la fabrique de middlewares est inscrite dans le conteneur comme service délimité.

L’implémentation par défaut de IMiddlewareFactory, MiddlewareFactory, se trouve dans


le package Microsoft.AspNetCore.Http .

Ressources supplémentaires
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Intergiciel (middleware) ASP.NET Core
Activation d’un intergiciel (middleware) avec un conteneur tiers dans ASP.NET Core

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Activation d’un intergiciel (middleware)
avec un conteneur tiers dans ASP.NET
Core
Article • 30/11/2023

Cet article montre comment utiliser IMiddlewareFactory et IMiddleware comme point


d’extensibilité pour l’activation d’un middleware avec un conteneur tiers. Pour une
présentation de IMiddlewareFactory et de IMiddleware , consultez Activation d’un
intergiciel basée sur une fabrique dans ASP.NET Core.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

L’exemple d’application montre l’activation d’un middleware par une implémentation de


IMiddlewareFactory , SimpleInjectorMiddlewareFactory . L’exemple utilise le conteneur

d’injection de dépendances Simple Injector .

L’implémentation du middleware de l’exemple enregistre la valeur fournie par un


paramètre de la chaîne de requête ( key ). Le middleware utilise un contexte de base de
données injecté (un service délimité) pour enregistrer la valeur de la chaîne de requête
dans une base de données en mémoire.

7 Notes

L’exemple d’application utilise Simple Injector seulement à des fins de


démonstration. Sa simple utilisation ne constitue pas une publicité pour ce produit.
Les approches de l’activation des middlewares décrites dans la documentation de
Simple Injector et les problèmes GitHub sont recommandées par les développeurs
de Simple Injector. Pour plus d’informations, consultez la documentation de
Simple Injector et le dépôt GitHub de Simple Injector .

IMiddlewareFactory
IMiddlewareFactory fournit des méthodes pour créer un middleware.

Dans l’exemple d’application, une fabrique d’intergiciels est implémentée pour créer une
instance de SimpleInjectorActivatedMiddleware . La fabrique de middleware utilise le
conteneur Simple Injector pour résoudre le middleware :

C#
public class SimpleInjectorMiddlewareFactory : IMiddlewareFactory
{
private readonly Container _container;

public SimpleInjectorMiddlewareFactory(Container container)


{
_container = container;
}

public IMiddleware Create(Type middlewareType)


{
return _container.GetInstance(middlewareType) as IMiddleware;
}

public void Release(IMiddleware middleware)


{
// The container is responsible for releasing resources.
}
}

IMiddleware
IMiddleware définit le middleware pour le pipeline des requêtes de l’application.

Intergiciel activé par une implémentation de IMiddlewareFactory


( Middleware/SimpleInjectorActivatedMiddleware.cs ) :

C#

public class SimpleInjectorActivatedMiddleware : IMiddleware


{
private readonly AppDbContext _db;

public SimpleInjectorActivatedMiddleware(AppDbContext db)


{
_db = db;
}

public async Task InvokeAsync(HttpContext context, RequestDelegate next)


{
var keyValue = context.Request.Query["key"];

if (!string.IsNullOrWhiteSpace(keyValue))
{
_db.Add(new Request()
{
DT = DateTime.UtcNow,
MiddlewareActivation =
"SimpleInjectorActivatedMiddleware",
Value = keyValue
});

await _db.SaveChangesAsync();
}

await next(context);
}
}

Une extension est créée pour l’intergiciel ( Middleware/MiddlewareExtensions.cs ) :

C#

public static class MiddlewareExtensions


{
public static IApplicationBuilder UseSimpleInjectorActivatedMiddleware(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<SimpleInjectorActivatedMiddleware>();
}
}

Startup.ConfigureServices doit effectuer plusieurs tâches :

Configurer le conteneur Simple Injector.


Inscrire la fabrique et le middleware.
Rendez le contexte de base de données de l’application disponible depuis le
conteneur Simple Injector.

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddRazorPages();

// Replace the default middleware factory with the


// SimpleInjectorMiddlewareFactory.
services.AddTransient<IMiddlewareFactory>(_ =>
{
return new SimpleInjectorMiddlewareFactory(_container);
});

// Wrap ASP.NET Core requests in a Simple Injector execution


// context.
services.UseSimpleInjectorAspNetRequestScoping(_container);

// Provide the database context from the Simple


// Injector container whenever it's requested from
// the default service container.
services.AddScoped<AppDbContext>(provider =>
_container.GetInstance<AppDbContext>());

_container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

_container.Register<AppDbContext>(() =>
{
var optionsBuilder = new DbContextOptionsBuilder<DbContext>();
optionsBuilder.UseInMemoryDatabase("InMemoryDb");
return new AppDbContext(optionsBuilder.Options);
}, Lifestyle.Scoped);

_container.Register<SimpleInjectorActivatedMiddleware>();

_container.Verify();
}

Le middleware est inscrit dans le pipeline de traitement des requêtes, dans


Startup.Configure :

C#

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)


{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}

app.UseSimpleInjectorActivatedMiddleware();

app.UseStaticFiles();
app.UseRouting();

app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}

Ressources supplémentaires
Middleware
Activation d’intergiciel (middleware) basée sur une fabrique
Dépôt GitHub de Simple Injector
Documentation de Simple Injector
6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
WebApplication et
WebApplicationBuilder dans les
applications API minimales
Article • 30/11/2023

WebApplication
Le code suivant est généré par un modèle ASP.NET Core :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Le code précédent peut être créé via dotnet new web sur la ligne de commande ou en
sélectionnant le modèle web Vide dans Visual Studio.

Le code suivant crée un WebApplication ( app ) sans créer explicitement de


WebApplicationBuilder :

C#

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run();

WebApplication.Create initialise une nouvelle instance de la classe WebApplication avec


les valeurs par défaut préconfigurées.

Utilisation des ports


Lorsqu’une application web est créée avec Visual Studio ou dotnet new , un fichier
Properties/launchSettings.json est créé et spécifie les ports auxquels l’application

répond. Dans les exemples de paramètres de port qui suivent, l’exécution de


l’application à partir de Visual Studio renvoie une boîte de dialogue d’erreur Unable to
connect to web server 'AppName' . Visual Studio retourne une erreur, car il attend le port

spécifié dans Properties/launchSettings.json , mais l’application utilise le port spécifié


par app.Run("http://localhost:3000") . Exécutez les exemples de modification de port
suivants à partir de la ligne de commande.

Les sections suivantes définissent le port auquel l’application répond.

C#

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World!");

app.Run("http://localhost:3000");

Dans le code précédent, l’application répond au port 3000 .

Plusieurs ports

Dans le code suivant, l’application répond aux ports 3000 et 4000 .

C#

var app = WebApplication.Create(args);

app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");

app.MapGet("/", () => "Hello World");

app.Run();

Définir le port à partir de la ligne de commande

La commande suivante permet à l’application de répondre au port 7777 :

CLI .NET

dotnet run --urls="https://localhost:7777"

Si le point de terminaison Kestrel est également configuré dans le fichier


appsettings.json , l’URL spécifiée par le fichier appsettings.json est utilisée. Pour plus

d’informations, consultez Configuration du point de terminaison Kestrel


Lire le port à partir de l’environnement
Le code suivant lit le port à partir de l’environnement :

C#

var app = WebApplication.Create(args);

var port = Environment.GetEnvironmentVariable("PORT") ?? "3000";

app.MapGet("/", () => "Hello World");

app.Run($"http://localhost:{port}");

La méthode recommandée pour définir le port à partir de l’environnement consiste à


utiliser la variable d’environnement ASPNETCORE_URLS , comme indiqué dans la section
suivante.

Définir les ports via la variable d’environnement


ASPNETCORE_URLS

La variable d’environnement ASPNETCORE_URLS est disponible pour définir le port :

ASPNETCORE_URLS=http://localhost:3000

ASPNETCORE_URLS prend en charge plusieurs URL :

ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000

Écouter sur toutes les interfaces


Les exemples suivants illustrent l’écoute sur toutes les interfaces

http://*:3000

C#

var app = WebApplication.Create(args);

app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");

app.Run();

http://+:3000

C#

var app = WebApplication.Create(args);

app.Urls.Add("http://+:3000");

app.MapGet("/", () => "Hello World");

app.Run();

http://0.0.0.0:3000

C#

var app = WebApplication.Create(args);

app.Urls.Add("http://0.0.0.0:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Écoutez toutes les interfaces à l’aide d’ASPNETCORE_URLS


Les exemples précédents peuvent utiliser ASPNETCORE_URLS

ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005

Écoutez toutes les interfaces à l’aide


d’ASPNETCORE_HTTPS_PORTS
Les exemples précédents peuvent utiliser ASPNETCORE_HTTPS_PORTS et
ASPNETCORE_HTTP_PORTS .
ASPNETCORE_HTTP_PORTS=3000;5005
ASPNETCORE_HTTPS_PORTS=5000

Pour plus d’informations, consultez Configurer des points de terminaison pour le serveur
web ASP.NET Core Kestrel

Spécifier HTTPS avec un certificat de développement


C#

var app = WebApplication.Create(args);

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Pour plus d’informations sur le certificat de développement, consultez Approuver le


certificat de développement HTTPS ASP.NET Core sur Windows et macOS.

Spécifier HTTPS à l’aide d’un certificat personnalisé


Les sections suivantes montrent comment spécifier le certificat personnalisé à l’aide du
fichier appsettings.json et via la configuration.

Spécifier le certificat personnalisé avec appsettings.json

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}

Spécifier le certificat personnalisé via la configuration

C#

var builder = WebApplication.CreateBuilder(args);

// Configure the cert and the key


builder.Configuration["Kestrel:Certificates:Default:Path"] = "cert.pem";
builder.Configuration["Kestrel:Certificates:Default:KeyPath"] = "key.pem";

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Utiliser les API de certificat

C#

using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
options.ConfigureHttpsDefaults(httpsOptions =>
{
var certPath = Path.Combine(builder.Environment.ContentRootPath,
"cert.pem");
var keyPath = Path.Combine(builder.Environment.ContentRootPath,
"key.pem");

httpsOptions.ServerCertificate =
X509Certificate2.CreateFromPemFile(certPath,
keyPath);
});
});

var app = builder.Build();

app.Urls.Add("https://localhost:3000");

app.MapGet("/", () => "Hello World");

app.Run();

Lire l’environnement
C#

var app = WebApplication.Create(args);

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/oops");
}

app.MapGet("/", () => "Hello World");


app.MapGet("/oops", () => "Oops! An error happened.");

app.Run();

Pour plus d’informations sur l’utilisation de l’environnement, consultez Utiliser plusieurs


environnements dans ASP.NET Core

Configuration
Le code suivant est lu à partir du système de configuration :

C#

var app = WebApplication.Create(args);

var message = app.Configuration["HelloKey"] ?? "Config failed!";

app.MapGet("/", () => message);

app.Run();

Pour plus d’informations, consultez Configuration dans ASP.NET Core

Journalisation
Le code suivant écrit un message dans le journal au démarrage de l’application :

C#

var app = WebApplication.Create(args);

app.Logger.LogInformation("The app started");

app.MapGet("/", () => "Hello World");

app.Run();

Pour plus d’informations, consultez Journalisation dans .NET Core et ASP.NET Core

Accéder au conteneur d’injection de dépendances (DI)


Le code suivant montre comment obtenir des services à partir du conteneur
d’authentification unique au démarrage de l’application :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();

var app = builder.Build();

app.MapControllers();

using (var scope = app.Services.CreateScope())


{
var sampleService =
scope.ServiceProvider.GetRequiredService<SampleService>();
sampleService.DoSomething();
}

app.Run();

Le code suivant montre comment accéder aux clés d’accès à partir du conteneur
d’injection de dépendances (DI) en utilisant l’attribut [FromKeyedServices] :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) =>


bigCache.Get("date"));

app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>


smallCache.Get("date"));

app.Run();

public interface ICache


{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache


{
public object Get(string key) => $"Resolving {key} from small cache.";
}

Pour obtenir plus d’informations sur le DI, consultez Injection de dépendances dans
ASP.NET Core.

WebApplicationBuilder
Cette section contient un exemple de code utilisant WebApplicationBuilder.

Modifier la racine du contenu, le nom de l’application et


l’environnement
Le code suivant définit la racine du contenu, le nom de l’application et l’environnement :

C#

var builder = WebApplication.CreateBuilder(new WebApplicationOptions


{
Args = args,
ApplicationName = typeof(Program).Assembly.FullName,
ContentRootPath = Directory.GetCurrentDirectory(),
EnvironmentName = Environments.Staging,
WebRootPath = "customwwwroot"
});

Console.WriteLine($"Application Name:
{builder.Environment.ApplicationName}");
Console.WriteLine($"Environment Name:
{builder.Environment.EnvironmentName}");
Console.WriteLine($"ContentRoot Path:
{builder.Environment.ContentRootPath}");
Console.WriteLine($"WebRootPath: {builder.Environment.WebRootPath}");

var app = builder.Build();

WebApplication.CreateBuilder initialise une nouvelle instance de la classe


WebApplicationBuilder avec les valeurs par défaut préconfigurées.

Pour plus d’informations, consultez Vue d’ensemble des principes de base d’ASP.NET
Core

Modifier la racine du contenu, le nom de l’application et


l’environnement avec des variables d’environnement ou la
ligne de commande
Le tableau suivant montre la variable d’environnement et l’argument de ligne de
commande utilisés pour modifier la racine du contenu, le nom de l’application et
l’environnement :

fonctionnalité Variable d’environnement Argument de ligne de


commande

Nom de l'application ASPNETCORE_APPLICATIONNAME --applicationName

Nom de ASPNETCORE_ENVIRONMENT --environment


l’environnement

Racine de contenu ASPNETCORE_CONTENTROOT --contentRoot

Ajouter des fournisseurs de configuration


L’exemple suivant ajoute le fournisseur de configuration INI :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddIniFile("appsettings.ini");

var app = builder.Build();


Pour plus d’informations, consultez Fournisseurs de configuration de fichiers dans
Configuration dans ASP.NET Core.

Configuration de lecture
Par défaut, WebApplicationBuilder lit la configuration à partir de plusieurs sources,
notamment :

appSettings.json et appSettings.{environment}.json

Variables d’environnement
Ligne de commande

Pour obtenir la liste complète des sources de configuration lues, consultez Configuration
par défaut dans Configuration dans ASP.NET Core

C#

var builder = WebApplication.CreateBuilder(args);

var message = builder.Configuration["HelloKey"] ?? "Hello";

var app = builder.Build();

app.MapGet("/", () => message);

app.Run();

Lire l’environnement
Le code suivant lit HelloKey à partir de la configuration et affiche la valeur au niveau du
point de terminaison / . Si la valeur de configuration est null, « Hello » est affecté à
message :

C#

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
Console.WriteLine($"Running in development.");
}

var app = builder.Build();

app.MapGet("/", () => "Hello World!");


app.Run();

Ajouter des fournisseurs de journalisation


C#

var builder = WebApplication.CreateBuilder(args);

// Configure JSON logging to the console.


builder.Logging.AddJsonConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello JSON console!");

app.Run();

Ajouter des services


C#

var builder = WebApplication.CreateBuilder(args);

// Add the memory cache services.


builder.Services.AddMemoryCache();

// Add a custom scoped service.


builder.Services.AddScoped<ITodoRepository, TodoRepository>();
var app = builder.Build();

Personnaliser IHostBuilder
Les méthodes d’extension existantes sur IHostBuilder sont accessibles à l’aide de la
propriété Host :

C#

var builder = WebApplication.CreateBuilder(args);

// Wait 30 seconds for graceful shutdown.


builder.Host.ConfigureHostOptions(o => o.ShutdownTimeout =
TimeSpan.FromSeconds(30));

var app = builder.Build();


app.MapGet("/", () => "Hello World!");

app.Run();

Personnaliser IWebHostBuilder
Les méthodes d’extension sur IWebHostBuilder sont accessibles à l’aide de la propriété
WebApplicationBuilder.WebHost.

C#

var builder = WebApplication.CreateBuilder(args);

// Change the HTTP server implemenation to be HTTP.sys based


builder.WebHost.UseHttpSys();

var app = builder.Build();

app.MapGet("/", () => "Hello HTTP.sys");

app.Run();

Modifier la racine web


Par défaut, la racine web est relative à la racine de contenu dans le dossier wwwroot . La
racine web est l’endroit où l’intergiciel de fichiers statiques recherche les fichiers
statiques. La racine web peut être modifiée avec WebHostOptions , la ligne de commande
ou avec la méthode UseWebRoot :

C#

var builder = WebApplication.CreateBuilder(new WebApplicationOptions


{
Args = args,
// Look for static files in webroot
WebRootPath = "webroot"
});

var app = builder.Build();

app.Run();

Conteneur d’injection de dépendances (DI) personnalisé


L’exemple suivant utilise Autofac :
C#

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

// Register services directly with Autofac here. Don't


// call builder.Populate(), that happens in AutofacServiceProviderFactory.
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
builder.RegisterModule(new MyApplicationModule()));

var app = builder.Build();

Ajouter un intergiciel
Tout intergiciel ASP.NET Core existant peut être configuré sur WebApplication :

C#

var app = WebApplication.Create(args);

// Setup the file server to serve static files.


app.UseFileServer();

app.MapGet("/", () => "Hello World!");

app.Run();

Pour plus d’informations, consultez Intergiciel (middleware) ASP.NET Core

Page d’exceptions du développeur


WebApplication.CreateBuilder initialise une nouvelle instance de la classe
WebApplicationBuilder avec les valeurs par défaut préconfigurées. La page d’exception
du développeur est activée dans les valeurs par défaut préconfigurées. Lorsque le code
suivant est exécuté dans l’environnement de développement, la navigation vers /
présente une page conviviale qui affiche l’exception.

C#

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an
exception.");
});

app.Run();

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Hôte générique .NET dans ASP.NET Core
Article • 30/11/2023

Cet article fournit des informations sur l’utilisation de l’hôte générique .NET dans
ASP.NET Core.

Les modèles ASP.NET Core créent un WebApplicationBuilder et une WebApplication, qui


offrent un moyen simplifié de configurer et d’exécuter des applications web sans classe
Startup . Pour plus d’informations sur WebApplicationBuilder et WebApplication ,

consultez Migrer d’ASP.NET Core 5.0 à 6.0.

Pour plus d’informations sur l’utilisation de l’hôte générique .NET dans les applications
console, consultez Hôte générique .NET.

Définition de l’hôte
Un hôte est un objet qui encapsule les ressources de l’application, telles que :

Injection de dépendances (DI)


Journalisation
Configuration
Implémentations de IHostedService

Lorsqu’un hôte démarre, il appelle IHostedService.StartAsync sur chaque


implémentation de IHostedService inscrite dans la collection de services hébergés du
conteneur de services. Dans une application web, l’une des implémentations de
IHostedService est un service web qui démarre une implémentation de serveur HTTP.

L’inclusion de toutes les ressources interdépendantes de l’application dans un seul objet


permet de contrôler le démarrage et l’arrêt approprié de l’application.

Configurer un hôte
L’hôte est généralement configuré, généré et exécuté par du code dans le Program.cs .
Le code suivant crée un hôte avec une implémentation IHostedService ajoutée au
conteneur de DI :

C#

await Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<SampleHostedService>();
})
.Build()
.RunAsync();

Pour une charge de travail HTTP, appelez ConfigureWebHostDefaults après


CreateDefaultBuilder :

C#

await Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build()
.RunAsync();

Paramètres du générateur par défaut


La méthode CreateDefaultBuilder :

Définit le chemin retourné par GetCurrentDirectory comme racine de contenu.


Charge la configuration de l’hôte à partir de :
Variables d’environnement comportant le préfixe DOTNET_ .
Arguments de ligne de commande
Charge la configuration de l’application à partir de :
appsettings.json .
appsettings.{Environment}.json .

Les secrets utilisateur quand l’application s’exécute dans l’environnement


Development
Variables d'environnement.
Arguments de ligne de commande
Ajoute les fournisseurs de journalisation suivants :
Console
Déboguer
EventSource
EventLog (uniquement en cas d’exécution sur Windows)
Active la validation de l’étendue et la validation de dépendances dans un
environnement de développement.

La méthode ConfigureWebHostDefaults :
Charge la configuration hôte à partir des variables d’environnement comportant le
préfixe ASPNETCORE_ .
Définit le serveur Kestrel comme serveur web et le configure en utilisant les
fournisseurs de configuration d’hébergement de l’application. Pour connaître les
options par défaut du serveur Kestrel, consultez Configurer les options pour le
serveur web ASP.NET Core Kestrel.
Ajoute l’intergiciel (middleware) Filtrage d’hôtes.
Ajoute l’intergiciel d’en-têtes transférés si ASPNETCORE_FORWARDEDHEADERS_ENABLED
est égal à true .
Permet l’intégration d’IIS. Pour connaître les options IIS par défaut, consultez la
Héberger ASP.NET Core sur Windows avec IIS.

Les sections Paramètres pour tous les types d’applications et Paramètres pour les
applications web figurant plus loin dans cet article montrent comment remplacer les
paramètres du générateur par défaut.

Services fournis par le framework


Les services suivants sont inscrits automatiquement :

IHostApplicationLifetime
IHostLifetime
IHostEnvironment / IWebHostEnvironment

Pour plus d’informations sur les services fournis par le framework, consultez Injection de
dépendances dans ASP.NET Core.

IHostApplicationLifetime
Injectez le service IHostApplicationLifetime (anciennement IApplicationLifetime ) dans
n’importe quelle classe pour gérer les tâches post-démarrage et d’arrêt approprié. Trois
propriétés de l’interface sont des jetons d’annulation utilisés pour inscrire les méthodes
du gestionnaire d’événements de démarrage et d’arrêt d’application. L’interface inclut
également une méthode StopApplication , qui permet aux applications de demander un
arrêt normal.

Lors de l’exécution d’un arrêt correct, l’hôte :

Déclenche les gestionnaires d’événements ApplicationStopping, ce qui permet à


l’application d’exécuter la logique avant le début du processus d’arrêt.
Arrête le serveur, ce qui désactive les nouvelles connexions. Le serveur attend que
les requêtes sur les connexions existantes se terminent, tant que le délai d’arrêt le
permet. Le serveur envoie l’en-tête de fermeture de connexion pour les requêtes
supplémentaires sur les connexions existantes.
Déclenche les gestionnaires d’événements ApplicationStopped, ce qui permet à
l’application d’exécuter la logique après l’arrêt de l’application.

L’exemple suivant est une implémentation de IHostedService qui inscrit les


gestionnaires d’événements IHostApplicationLifetime :

C#

public class HostApplicationLifetimeEventsHostedService : IHostedService


{
private readonly IHostApplicationLifetime _hostApplicationLifetime;

public HostApplicationLifetimeEventsHostedService(
IHostApplicationLifetime hostApplicationLifetime)
=> _hostApplicationLifetime = hostApplicationLifetime;

public Task StartAsync(CancellationToken cancellationToken)


{
_hostApplicationLifetime.ApplicationStarted.Register(OnStarted);
_hostApplicationLifetime.ApplicationStopping.Register(OnStopping);
_hostApplicationLifetime.ApplicationStopped.Register(OnStopped);

return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)


=> Task.CompletedTask;

private void OnStarted()


{
// ...
}

private void OnStopping()


{
// ...
}

private void OnStopped()


{
// ...
}
}

IHostLifetime
L’implémentation de IHostLifetime contrôle quand l’hôte démarre et quand il s’arrête. La
dernière implémentation inscrite est utilisée.

Microsoft.Extensions.Hosting.Internal.ConsoleLifetime est l’implémentation de


IHostLifetime par défaut. ConsoleLifetime :

écoute Ctrl + C /SIGINT (Windows), ⌘ + C (macOS) et appelle StopApplication


pour démarrer le processus d’arrêt.
Déverrouille les extensions telles que RunAsync et WaitForShutdownAsync.

IHostEnvironment
Injectez le service IHostEnvironment dans une classe pour obtenir des informations sur
les paramètres suivants :

ApplicationName
EnvironmentName
ContentRootPath

Les applications web implémentent l’interface IWebHostEnvironment , qui hérite de


IHostEnvironment et ajoute le WebRootPath.

Configuration de l’hôte
La configuration de l’hôte est utilisée pour les propriétés de l’implémentation de
IHostEnvironment.

La configuration de l’hôte est disponible à partir de HostBuilderContext.Configuration


dans ConfigureAppConfiguration. Après ConfigureAppConfiguration ,
HostBuilderContext.Configuration est remplacé par la configuration de l’application.

Pour ajouter la configuration d’hôte, appelez ConfigureHostConfiguration sur


IHostBuilder . ConfigureHostConfiguration peut être appelé plusieurs fois avec des

résultats additifs. L’hôte utilise l’option qui définit une valeur en dernier sur une clé
donnée.

Le fournisseur de variables d’environnement avec le préfixe DOTNET_ et les arguments de


ligne de commande sont inclus par CreateDefaultBuilder . Pour les applications web, le
fournisseur de variables d’environnement avec le préfixe ASPNETCORE_ est ajouté. Ce
préfixe est supprimé à la lecture des variables d’environnement. Par exemple, la valeur
de variable d’environnement de ASPNETCORE_ENVIRONMENT devient la valeur de
configuration d’hôte de la clé environment .

L’exemple suivant crée la configuration d’hôte :

C#

Host.CreateDefaultBuilder(args)
.ConfigureHostConfiguration(hostConfig =>
{
hostConfig.SetBasePath(Directory.GetCurrentDirectory());
hostConfig.AddJsonFile("hostsettings.json", optional: true);
hostConfig.AddEnvironmentVariables(prefix: "PREFIX_");
hostConfig.AddCommandLine(args);
});

la configuration d’une application ;


La configuration d’application est créée en appelant ConfigureAppConfiguration sur
IHostBuilder . ConfigureAppConfiguration peut être appelé plusieurs fois avec des

résultats additifs. L’application utilise l’option qui définit une valeur en dernier sur une
clé donnée.

La configuration créée par ConfigureAppConfiguration est disponible dans


HostBuilderContext.Configuration pour les opérations suivantes et en tant que service à
partir de l’injection de dépendances. La configuration d’hôte est également ajoutée à la
configuration d’application.

Pour plus d’informations, consultez Configuration dans ASP.NET Core.

Paramètres pour tous les types d’applications


Cette section liste les paramètres d’hôte qui s’appliquent aux charges de travail HTTP et
non-HTTP. Par défaut, les variables d’environnement utilisées pour configurer ces
paramètres peuvent avoir un préfixe DOTNET_ ou ASPNETCORE_ , qui apparaissent dans la
liste suivante des paramètres comme espace réservé {PREFIX_} . Pour plus
d’informations, consultez la section Paramètres du générateur par défaut et
Configuration : Variables d’environnement.

ApplicationName
La propriété IHostEnvironment.ApplicationName est définie à partir de la configuration
d’hôte pendant la construction de l’hôte.

Clé : applicationName
Type : string
Par défaut : nom de l’assembly contenant le point d’entrée de l’application.
La variable d’environnement: {PREFIX_}APPLICATIONNAME

Pour définir cette valeur, utilisez la variable d’environnement.

ContentRoot
La propriété IHostEnvironment.ContentRootPath détermine où l’hôte commence la
recherche des fichiers de contenu. Si le chemin est introuvable, l’hôte ne peut pas
démarrer.

Clé : contentRoot
Type : string
Par défaut : dossier où réside l’assembly de l’application.
La variable d’environnement: {PREFIX_}CONTENTROOT

Pour définir cette valeur, utilisez la variable d’environnement ou appelez UseContentRoot


sur IHostBuilder :

C#

Host.CreateDefaultBuilder(args)
.UseContentRoot("/path/to/content/root")
// ...

Pour plus d'informations, consultez les pages suivantes :

Notions de base : Racine du contenu


WebRoot

EnvironmentName
La valeur de la propriété IHostEnvironment.EnvironmentName peut être n’importe
quelle valeur. Les valeurs définies par le framework sont Development , Staging et
Production . Les valeurs ne respectent pas la casse.

Clé : environment
Type : string
Par défaut : Production
La variable d’environnement: {PREFIX_}ENVIRONMENT

Pour définir cette valeur, utilisez la variable d’environnement ou appelez UseEnvironment


sur IHostBuilder :

C#

Host.CreateDefaultBuilder(args)
.UseEnvironment("Development")
// ...

ShutdownTimeout
HostOptions.ShutdownTimeout définit le délai d’expiration pour StopAsync. La valeur
par défaut est de cinq secondes. Pendant la période du délai d’expiration, l’hôte :

Déclenche IHostApplicationLifetime.ApplicationStopping.
Tente d’arrêter les services hébergés, en journalisant les erreurs pour les services
qui échouent à s’arrêter.

Si la période du délai d’attente expire avant l’arrêt de tous les services hébergés, les
services actifs restants sont arrêtés quand l’application s’arrête. Les services s’arrêtent
même s’ils n’ont pas terminé les traitements. Si des services nécessitent plus de temps
pour s’arrêter, augmentez le délai d’attente.

Clé : shutdownTimeoutSeconds
Type : int
Valeur par défaut : 5 secondes
La variable d’environnement: {PREFIX_}SHUTDOWNTIMEOUTSECONDS

Pour définir cette valeur, utilisez la variable d’environnement ou configurez HostOptions .


L’exemple suivant définit un délai d’expiration de 20 secondes :

C#

Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.Configure<HostOptions>(options =>
{
options.ShutdownTimeout = TimeSpan.FromSeconds(20);
});
});
Désactiver le rechargement de la configuration de
l’application lors de la modification
Par défaut, appsettings.json et appsettings.{Environment}.json sont rechargés lorsque
le fichier change. Pour désactiver ce comportement de rechargement dans ASP.NET
Core 5.0 ou version ultérieure, définissez la clé hostBuilder:reloadConfigOnChange sur
false .

Clé : hostBuilder:reloadConfigOnChange
Type : bool ( true ou false )
Par défaut : true
Argument de ligne de commande : hostBuilder:reloadConfigOnChange
La variable d’environnement: {PREFIX_}hostBuilder:reloadConfigOnChange

2 Avertissement

Le séparateur deux-points ( : ) ne fonctionne pas avec les clés hiérarchiques des


variables d’environnement sur toutes les plateformes. Pour en savoir plus, voir
Variables d’environnement.

Paramètres pour les applications web


Certains paramètres d’hôte s’appliquent uniquement aux charges de travail HTTP. Par
défaut, les variables d’environnement utilisées pour configurer ces paramètres peuvent
avoir un préfixe DOTNET_ ou ASPNETCORE_ , qui apparaissent dans la liste suivante des
paramètres comme espace réservé {PREFIX_} .

Des méthodes d’extension sur IWebHostBuilder sont disponibles pour ces paramètres.
Les exemples de code qui montrent comment appeler les méthodes d’extension
supposent que webBuilder est une instance de IWebHostBuilder , comme dans l’exemple
suivant :

C#

Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
// ...
});
CaptureStartupErrors
Avec la valeur false , la survenue d’erreurs au démarrage entraîne la fermeture de l’hôte.
Avec la valeur true , l’hôte capture les exceptions levées au démarrage et tente de
démarrer le serveur.

Clé : captureStartupErrors
Type : bool ( true / 1 ou false / 0 )
Valeur par défaut : false , ou true si l’application s’exécute avec Kestrel derrière IIS.
La variable d’environnement: {PREFIX_}CAPTURESTARTUPERRORS

Pour définir cette valeur, utilisez la configuration ou appelez CaptureStartupErrors :

C#

webBuilder.CaptureStartupErrors(true);

DetailedErrors
En cas d’activation ou quand l’environnement est Development , l’application capture des
erreurs détaillées.

Clé : detailedErrors
Type : bool ( true / 1 ou false / 0 )
Par défaut : false
La variable d’environnement: {PREFIX_}DETAILEDERRORS

Pour définir cette valeur, utilisez la configuration ou appelez UseSetting :

C#

webBuilder.UseSetting(WebHostDefaults.DetailedErrorsKey, "true");

HostingStartupAssemblies
Chaîne délimitée par des points-virgules qui spécifie les assemblys d’hébergement à
charger au démarrage. La valeur de configuration par défaut est une chaîne vide, mais
les assemblys d’hébergement au démarrage incluent toujours l’assembly de
l’application. Quand des assemblys d’hébergement au démarrage sont fournis, ils sont
ajoutés à l’assembly de l’application et sont chargés lorsque l’application génère ses
services communs au démarrage.
Clé : hostingStartupAssemblies
Type : string
Valeur par défaut : une chaîne vide
La variable d’environnement: {PREFIX_}HOSTINGSTARTUPASSEMBLIES

Pour définir cette valeur, utilisez la configuration ou appelez UseSetting :

C#

webBuilder.UseSetting(
WebHostDefaults.HostingStartupAssembliesKey, "assembly1;assembly2");

HostingStartupExcludeAssemblies
Chaîne délimitée par des points-virgules qui spécifie les assemblys d’hébergement à
exclure au démarrage.

Clé : hostingStartupExcludeAssemblies
Type : string
Valeur par défaut : une chaîne vide
La variable d’environnement: {PREFIX_}HOSTINGSTARTUPEXCLUDEASSEMBLIES

Pour définir cette valeur, utilisez la configuration ou appelez UseSetting :

C#

webBuilder.UseSetting(
WebHostDefaults.HostingStartupExcludeAssembliesKey,
"assembly1;assembly2");

HTTPS_Port
Port de redirection HTTPS. Utilisé dans l’application de HTTPS.

Clé : https_port
Type : string
Par défaut : Aucune valeur par défaut n’est définie.
La variable d’environnement: {PREFIX_}HTTPS_PORT

Pour définir cette valeur, utilisez la configuration ou appelez UseSetting :

C#
webBuilder.UseSetting("https_port", "8080");

PreferHostingUrls
Indique si l’hôte doit écouter les URL configurées avec IWebHostBuilder au lieu
d’écouter celles configurées avec l’implémentation IServer .

Clé : preferHostingUrls
Type : bool ( true / 1 ou false / 0 )
Par défaut : true
La variable d’environnement: {PREFIX_}PREFERHOSTINGURLS

Pour définir cette valeur, utilisez la variable d’environnement ou appelez


PreferHostingUrls :

C#

webBuilder.PreferHostingUrls(true);

PreventHostingStartup
Empêche le chargement automatique des assemblys d’hébergement au démarrage, y
compris ceux configurés par l’assembly de l’application. Pour plus d’informations,
consultez Utiliser des assemblys d’hébergement au démarrage dans ASP.NET Core.

Clé : preventHostingStartup
Type : bool ( true / 1 ou false / 0 )
Par défaut : false
La variable d’environnement: {PREFIX_}PREVENTHOSTINGSTARTUP

Pour définir cette valeur, utilisez la variable d’environnement ou appelez UseSetting :

C#

webBuilder.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true");

StartupAssembly
Assembly où rechercher la classe Startup .
Clé : startupAssembly
Type : string
Valeur par défaut : l’assembly de l’application
La variable d’environnement: {PREFIX_}STARTUPASSEMBLY

Pour définir cette valeur, utilisez la variable d’environnement ou appelez UseStartup .


UseStartup peut prendre un nom d’assembly ( string ) ou un type ( TStartup ). Si

plusieurs méthodes UseStartup sont appelées, la dernière prévaut.

C#

webBuilder.UseStartup("StartupAssemblyName");

C#

webBuilder.UseStartup<Startup>();

SuppressStatusMessages
Lorsque cette option est activée, supprime les messages d’état de démarrage
d’hébergement.

Clé : suppressStatusMessages
Type : bool ( true / 1 ou false / 0 )
Par défaut : false
La variable d’environnement: {PREFIX_}SUPPRESSSTATUSMESSAGES

Pour définir cette valeur, utilisez la configuration ou appelez UseSetting :

C#

webBuilder.UseSetting(WebHostDefaults.SuppressStatusMessagesKey, "true");

URLs
Liste délimitée par des points-virgules d’adresses IP ou d’adresses d’hôte avec les ports
et protocoles sur lesquels le serveur doit écouter les requêtes. Par exemple,
http://localhost:123 Utilisez « * » pour indiquer que le serveur doit écouter les

requêtes sur toutes les adresses IP ou tous les noms d’hôte qui utilisent le port et le
protocole spécifiés (par exemple, http://*:5000 ). Le protocole ( http:// ou https:// )
doit être inclus avec chaque URL. Les formats pris en charge varient selon les serveurs.

Clé : urls
Type : string
Par défaut : http://localhost:5000 et https://localhost:5001
La variable d’environnement: {PREFIX_}URLS

Pour définir cette valeur, utilisez la variable d’environnement ou appelez UseUrls :

C#

webBuilder.UseUrls("http://*:5000;http://localhost:5001;https://hostname:500
2");

Kestrel a sa propre API de configuration de points de terminaison. Pour plus


d’informations, consultez Configurer des points de terminaison pour le serveur web
ASP.NET Core Kestrel.

WebRoot
La propriété IWebHostEnvironment.WebRootPath détermine le chemin d’accès relatif
aux ressources statiques de l’application. Si ce chemin n’existe pas, un fournisseur de
fichiers no-op est utilisé.

Clé : webroot
Type : string
Par défaut : la valeur par défaut est wwwroot . Le chemin d’accès à {racine du
contenu}/wwwroot doit exister.
La variable d’environnement: {PREFIX_}WEBROOT

Pour définir cette valeur, utilisez la variable d’environnement ou appelez UseWebRoot sur
IWebHostBuilder :

C#

webBuilder.UseWebRoot("public");

Pour plus d'informations, consultez les pages suivantes :

Principes de base : Racine web


ContentRoot
Gérer la durée de vie de l’hôte
Appelez des méthodes sur l’implémentation de IHost pour démarrer et arrêter
l’application. Ces méthodes affectent toutes les implémentations de IHostedService qui
sont inscrites dans le conteneur de service.

La différence entre les méthodes Run* et Start* est que les méthodes Run* attendent
que l’hôte se termine avant de retourner, tandis que les méthodes Start* retournent
immédiatement. Les méthodes Run* sont généralement utilisées dans les applications
console, tandis que les méthodes Start* sont généralement utilisées dans les services
de longue durée.

Exécuter
Run exécute l’application et bloque le thread appelant jusqu’à l’arrêt de l’hôte.

RunAsync
RunAsync exécute l’application et retourne un Task qui est effectué quand le jeton
d’annulation ou l’arrêt est déclenché.

RunConsoleAsync
RunConsoleAsync permet la prise en charge de la console, génère et démarre l’hôte et
attend Ctrl + C /SIGINT (Windows), ⌘ + C (macOS) ou SIGTERM pour l’arrêter.

Commencement
Start démarre l’hôte en mode synchrone.

StartAsync
StartAsync démarre l’hôte et retourne un Task qui est effectué quand le jeton
d’annulation ou l’arrêt est déclenché.

WaitForStartAsync est appelé au début de StartAsync , lequel attend qu’il soit fini avant
de continuer. Cette méthode permet de retarder le démarrage jusqu’à ce que celui-ci
soit signalé par un événement externe.

StopAsync
StopAsync tente d’arrêter l’hôte dans le délai d’attente fourni.

WaitForShutdown
WaitForShutdown bloque le thread appelant jusqu’à ce qu’un arrêt soit déclenché par
IHostLifetime, par exemple par le biais de Ctrl + C /SIGINT (Windows), ⌘ + C ou
SIGTERM.

WaitForShutdownAsync
WaitForShutdownAsync retourne une Task qui est effectuée quand l’arrêt est déclenché
par le biais du jeton fourni et appelle StopAsync.

Ressources supplémentaires
Tâches d’arrière-plan avec des services hébergés dans ASP.NET Core
Lien GitHub vers la source d’hôte générique

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Hôte web ASP.NET Core
Article • 30/11/2023

Les applications ASP.NET Core configurent et lancent un hôte. L’hôte est responsable de
la gestion du démarrage et de la durée de vie des applications. Au minimum, l’hôte
configure un serveur ainsi qu’un pipeline de traitement des requêtes. L’hôte peut aussi
configurer la journalisation, l’injection de dépendances et la configuration.

Cet article traite de l’hôte Web, qui reste disponible uniquement pour la compatibilité
descendante. Les modèles ASP.NET Core créent un WebApplicationBuilder et une
WebApplication, ce qui est recommandé pour les applications web. Pour plus
d’informations sur WebApplicationBuilder et WebApplication , consultez Migrer
d’ASP.NET Core 5.0 à 6.0

Configurer un hôte
Créez un hôte en utilisant une instance de IWebHostBuilder. Cette opération est
généralement effectuée au point d’entrée de l’application, à savoir la méthode Main
dans Program.cs . Une application standard appelle CreateDefaultBuilder pour lancer la
configuration d’un hôte :

C#

public class Program


{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>


WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}

Le code qui appelle CreateDefaultBuilder est dans une méthode nommée


CreateWebHostBuilder , qui le sépare du code dans Main qui appelle Run sur l’objet du

générateur. Cette séparation est requise si vous utilisez les outils Entity Framework Core.
Les outils s’attendent à trouver une méthode CreateWebHostBuilder qu’ils peuvent
appeler au moment du design pour configurer l’hôte sans exécuter l’application. Une
alternative consiste à implémenter IDesignTimeDbContextFactory . Pour plus
d’informations, consultez Création de DbContext au moment du design.
CreateDefaultBuilder effectue les tâches suivantes :

Configure le serveur Kestrel comme serveur web en utilisant les fournisseurs de


configuration d’hébergement de l’application. Pour connaître les options par
défaut du serveur Kestrel, consultez Configurer les options pour le serveur web
ASP.NET Core Kestrel.
Définit le chemin retourné par Directory.GetCurrentDirectory comme racine de
contenu.
Charge la configuration de l’hôte à partir de :
Variables d’environnement comportant le préfixe ASPNETCORE_ (par exemple,
ASPNETCORE_ENVIRONMENT ).

Arguments de ligne de commande


Charge la configuration de l’application dans l’ordre suivant à partir des éléments
ci-après :
appsettings.json .

appsettings.{Environment}.json .

Les secrets utilisateur quand l’application s’exécute dans l’environnement


Development à l’aide de l’assembly d’entrée

Variables d'environnement.
Arguments de ligne de commande
Configure la journalisation des sorties de la console et du débogage. La
journalisation inclut les règles de filtrage de journal qui sont spécifiées dans une
section de configuration de la journalisation dans un fichier appsettings.json ou
appsettings.{Environment}.json .

Lors de l’exécution derrière IIS avec le module ASP.NET Core,


CreateDefaultBuilder permet l’intégration IIS, qui configure l’adresse de base et le

port de l’application. L’intégration IIS configure également l’application pour la


capture des erreurs de démarrage. Pour connaître les options IIS par défaut,
consultez la Héberger ASP.NET Core sur Windows avec IIS.
Définit ServiceProviderOptions.ValidateScopes sur true si l’environnement de
l’application est Développement. Pour plus d’informations, consultez Validation de
l’étendue.

La configuration définie par CreateDefaultBuilder peut être remplacée et enrichie par


ConfigureAppConfiguration, ConfigureLogging et les autres méthodes et les méthodes
d’extension de IWebHostBuilder. En voici quelques exemples :

ConfigureAppConfiguration permet de spécifier une configuration IConfiguration


supplémentaire pour l’application. L’appel ConfigureAppConfiguration suivant
ajoute un délégué pour inclure la configuration de l’application dans le fichier
appsettings.xml . ConfigureAppConfiguration peut être appelé plusieurs fois. Notez
que cette configuration ne s’applique pas à l’hôte (par exemple, les URL de serveur
ou l’environnement). Voir la section Valeurs de configuration de l’hôte.

C#

WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddXmlFile("appsettings.xml", optional: true,
reloadOnChange: true);
})
...

L’appel ConfigureLogging suivant ajoute un délégué pour configurer le niveau de


journalisation minimal (SetMinimumLevel) sur LogLevel.Warning. Ce paramètre
remplace les paramètres de appsettings.Development.json ( LogLevel.Debug ) et de
appsettings.Production.json ( LogLevel.Error ) configurés par
CreateDefaultBuilder . ConfigureLogging peut être appelé plusieurs fois.

C#

WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Warning);
})
...

L’appel suivant à ConfigureKestrel remplace la valeur par défaut de


Limits.MaxRequestBodySize (30 000 000 octets) établie lors de la configuration de
Kestrel par CreateDefaultBuilder :

C#

WebHost.CreateDefaultBuilder(args)
.ConfigureKestrel((context, options) =>
{
options.Limits.MaxRequestBodySize = 20000000;
});

La racine de contenu détermine l’emplacement où l’hôte recherche les fichiers de


contenu, tels que les fichiers de vue MVC. Quand l’application est démarrée à partir du
dossier racine du projet, ce dossier est utilisé comme racine de contenu. Il s’agit du
dossier par défaut utilisé dans Visual Studio et les modèles dotnet new.
Pour plus d’informations sur la configuration d’une application, consultez Configuration
dans ASP.NET Core.

7 Notes

Au lieu d’utiliser la méthode statique CreateDefaultBuilder , vous pouvez aussi


créer un hôte à partir de WebHostBuilder. Cette approche est prise en charge dans
ASP.NET Core 2.x.

Lors de la configuration d’un hôte, les méthodes Configure et ConfigureServices


peuvent être fournies. Si une classe Startup est spécifiée, elle doit définir une méthode
Configure . Pour plus d’informations, consultez Start-up de l’application dans ASP.NET

Core. Les appels multiples à ConfigureServices s’ajoutent les uns aux autres. Les appels
multiples à Configure ou UseStartup sur WebHostBuilder remplacent les paramètres
précédents.

Valeurs de configuration de l’hôte


WebHostBuilder s’appuie sur les approches suivantes pour définir les valeurs de
configuration de l’hôte :

Configuration du générateur de l’hôte, qui inclut des variables d’environnement au


format ASPNETCORE_{configurationKey} . Par exemple, ASPNETCORE_ENVIRONMENT
Extensions comme UseContentRoot et UseConfiguration (consultez la section
Remplacer la configuration).
UseSetting et la clé associée. Quand une valeur est définie avec UseSetting , elle
est définie au format chaîne indépendamment du type.

L’hôte utilise l’option qui définit une valeur en dernier. Pour plus d’informations,
consultez Remplacer une configuration dans la section suivante.

Clé d’application (Nom)


La propriété IWebHostEnvironment.ApplicationName est définie automatiquement quand
UseStartup ou Configure est appelé pendant la construction de l’hôte. La valeur affectée
correspond au nom de l’assembly contenant le point d’entrée de l’application. Pour
définir la valeur explicitement, utilisez WebHostDefaults.ApplicationKey :

Clé : applicationName
Type : string
Par défaut : nom de l’assembly contenant le point d’entrée de l’application.
Définition avec : UseSetting
La variable d’environnement: ASPNETCORE_APPLICATIONNAME

C#

WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.ApplicationKey, "CustomApplicationName")

Capture des erreurs de démarrage


Ce paramètre contrôle la capture des erreurs de démarrage.

Clé : captureStartupErrors
Type : bool ( true ou 1 )
Valeur par défaut : false , ou true si l’application s’exécute avec Kestrel derrière IIS.
Définition avec : CaptureStartupErrors
La variable d’environnement: ASPNETCORE_CAPTURESTARTUPERRORS

Avec la valeur false , la survenue d’erreurs au démarrage entraîne la fermeture de l’hôte.


Avec la valeur true , l’hôte capture les exceptions levées au démarrage et tente de
démarrer le serveur.

C#

WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(true)

Racine de contenu
Ce paramètre détermine l’emplacement où ASP.NET Core commence la recherche des
fichiers de contenu.

Clé : contentRoot
Type : string
Valeur par défaut : dossier où réside l’assembly de l’application.
Définition avec : UseContentRoot
La variable d’environnement: ASPNETCORE_CONTENTROOT

La racine de contenu est également utilisée comme chemin de base pour la racine web.
Si le chemin de la racine de contenu est introuvable, l’hôte ne peut pas démarrer.
C#

WebHost.CreateDefaultBuilder(args)
.UseContentRoot("c:\\<content-root>")

Pour en savoir plus, consultez :

Notions de base : Racine du contenu


Racine Web

Erreurs détaillées
Détermine si les erreurs détaillées doivent être capturées.

Clé : detailedErrors
Type : bool ( true ou 1 )
Valeur par défaut : false
Définition avec : UseSetting
La variable d’environnement: ASPNETCORE_DETAILEDERRORS

Quand cette fonctionnalité est activée (ou que l’environnement est défini sur
Development ), l’application capture les exceptions détaillées.

C#

WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")

Environment
Définit l’environnement de l’application.

Clé : environment
Type : string
Valeur par défaut : Production
Définition avec : UseEnvironment
La variable d’environnement: ASPNETCORE_ENVIRONMENT

L’environnement peut être défini à n’importe quelle valeur. Les valeurs définies par le
framework sont Development , Staging et Production . Les valeurs ne respectent pas la
casse. Par défaut, l’environnement est fourni par la variable d’environnement
ASPNETCORE_ENVIRONMENT . Si vous utilisez Visual Studio , les variables d’environnement
peuvent être définies dans le fichier launchSettings.json . Pour plus d’informations,
consultez Utiliser plusieurs environnements dans ASP.NET Core.

C#

WebHost.CreateDefaultBuilder(args)
.UseEnvironment(EnvironmentName.Development)

Assemblys d’hébergement au démarrage


Définit les assemblys d’hébergement au démarrage de l’application.

Clé : hostingStartupAssemblies
Type : string
Valeur par défaut : une chaîne vide
Définition avec : UseSetting
La variable d’environnement: ASPNETCORE_HOSTINGSTARTUPASSEMBLIES

Chaîne délimitée par des points-virgules qui spécifie les assemblys d’hébergement à
charger au démarrage.

La valeur de configuration par défaut est une chaîne vide, mais les assemblys
d’hébergement au démarrage incluent toujours l’assembly de l’application. Quand des
assemblys d’hébergement au démarrage sont fournis, ils sont ajoutés à l’assembly de
l’application et sont chargés lorsque l’application génère ses services communs au
démarrage.

C#

WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey,
"assembly1;assembly2")

Port HTTPS
Définissez le port de redirection HTTPS. Utilisé dans l’application de HTTPS.

Clé : https_port
Type : string
Par défaut : Aucune valeur par défaut n’est définie.
Définition avec : UseSetting
La variable d’environnement: ASPNETCORE_HTTPS_PORT
C#

WebHost.CreateDefaultBuilder(args)
.UseSetting("https_port", "8080")

Assemblys d’hébergement à exclure au démarrage


Chaîne délimitée par des points-virgules qui spécifie les assemblys d’hébergement à
exclure au démarrage.

Clé : hostingStartupExcludeAssemblies
Type : string
Valeur par défaut : une chaîne vide
Définition avec : UseSetting
La variable d’environnement: ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES

C#

WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey,
"assembly1;assembly2")

URL d’hébergement préférées


Indique si l’hôte doit écouter les URL configurées avec WebHostBuilder au lieu d’écouter
celles configurées avec l’implémentation IServer .

Clé : preferHostingUrls
Type : bool ( true ou 1 )
Valeur par défaut : true
Définition avec : PreferHostingUrls
La variable d’environnement: ASPNETCORE_PREFERHOSTINGURLS

C#

WebHost.CreateDefaultBuilder(args)
.PreferHostingUrls(false)

Bloquer les assemblys d’hébergement au démarrage


Empêche le chargement automatique des assemblys d’hébergement au démarrage, y
compris ceux configurés par l’assembly de l’application. Pour plus d’informations,
consultez Utiliser des assemblys d’hébergement au démarrage dans ASP.NET Core.

Clé : preventHostingStartup
Type : bool ( true ou 1 )
Valeur par défaut : false
Définition avec : UseSetting
La variable d’environnement: ASPNETCORE_PREVENTHOSTINGSTARTUP

C#

WebHost.CreateDefaultBuilder(args)
.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true")

URL du serveur
Indique les adresses IP ou les adresses d’hôte avec les ports et protocoles sur lesquels le
serveur doit écouter les requêtes.

Clé : urls
Type : string
Par défaut :http://localhost:5000
Définition avec : UseUrls
La variable d’environnement: ASPNETCORE_URLS

Liste de préfixes d’URL séparés par des points-virgules (;) correspondant aux URL
auxquelles le serveur doit répondre. Par exemple, http://localhost:123 Utilisez « * »
pour indiquer que le serveur doit écouter les requêtes sur toutes les adresses IP ou tous
les noms d’hôte qui utilisent le port et le protocole spécifiés (par exemple,
http://*:5000 ). Le protocole ( http:// ou https:// ) doit être inclus avec chaque URL.

Les formats pris en charge varient selon les serveurs.

C#

WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000;http://localhost:5001;https://hostname:5002")

Kestrel a sa propre API de configuration de points de terminaison. Pour plus


d’informations, consultez Configurer des points de terminaison pour le serveur web
ASP.NET Core Kestrel.
Délai d’arrêt
Spécifie le délai d’attente avant l’arrêt de l’hôte web.

Clé : shutdownTimeoutSeconds
Type : int
Valeur par défaut : 5
Définition avec : UseShutdownTimeout
La variable d’environnement: ASPNETCORE_SHUTDOWNTIMEOUTSECONDS

La clé accepte une valeur int avec UseSetting (par exemple,


.UseSetting(WebHostDefaults.ShutdownTimeoutKey, "10") ), mais la méthode d’extension

UseShutdownTimeout prend une valeur TimeSpan.

Pendant la période du délai d’attente, l’hébergement :

Déclenche IApplicationLifetime.ApplicationStopping.
Tente d’arrêter les services hébergés, en journalisant les erreurs pour les services
qui échouent à s’arrêter.

Si la période du délai d’attente expire avant l’arrêt de tous les services hébergés, les
services actifs restants sont arrêtés quand l’application s’arrête. Les services s’arrêtent
même s’ils n’ont pas terminé les traitements. Si des services nécessitent plus de temps
pour s’arrêter, augmentez le délai d’attente.

C#

WebHost.CreateDefaultBuilder(args)
.UseShutdownTimeout(TimeSpan.FromSeconds(10))

Assembly de démarrage
Détermine l’assembly à rechercher pour la classe Startup .

Clé : startupAssembly
Type : string
Valeur par défaut : l’assembly de l’application
Définition avec : UseStartup
La variable d’environnement: ASPNETCORE_STARTUPASSEMBLY

L’assembly peut être référencé par nom ( string ) ou type ( TStartup ). Si plusieurs
méthodes UseStartup sont appelées, la dernière prévaut.
C#

WebHost.CreateDefaultBuilder(args)
.UseStartup("StartupAssemblyName")

C#

WebHost.CreateDefaultBuilder(args)
.UseStartup<TStartup>()

Racine web
Définit le chemin relatif des ressources statiques de l’application.

Clé : webroot
Type : string
Par défaut : la valeur par défaut est wwwroot . Le chemin d’accès à {racine du
contenu}/wwwroot doit exister. Si ce chemin n’existe pas, un fournisseur de fichiers no-
op est utilisé.
Définition avec : UseWebRoot
La variable d’environnement: ASPNETCORE_WEBROOT

C#

WebHost.CreateDefaultBuilder(args)
.UseWebRoot("public")

Pour en savoir plus, consultez :

Principes de base : Racine web


Racine de contenu

Remplacer la configuration
Utilisez Configuration pour configurer l’hôte web. Dans l’exemple suivant, la
configuration de l’hôte est facultativement spécifiée dans un fichier hostsettings.json .
Une configuration chargée à partir du fichier hostsettings.json peut être remplacée
par des arguments de ligne de commande. La configuration intégrée (dans config ) est
utilisée pour configurer l’hôte avec UseConfiguration. La configuration IWebHostBuilder
est ajoutée à la configuration de l’application, mais l’inverse n’est pas vrai ;
ConfigureAppConfiguration n’a pas d’incidence sur la configuration IWebHostBuilder .
La configuration fournie par UseUrls est d’abord remplacée par la configuration du
fichier hostsettings.json et ensuite par la configuration des arguments de ligne de
commande :

C#

public class Program


{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args)


{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("hostsettings.json", optional: true)
.AddCommandLine(args)
.Build();

return WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000")
.UseConfiguration(config)
.Configure(app =>
{
app.Run(context =>
context.Response.WriteAsync("Hello, World!"));
});
}
}

hostsettings.json :

JSON

{
urls: "http://*:5005"
}

7 Notes

UseConfiguration copie seulement les clés de la configuration IConfiguration


fournie vers la configuration du générateur d’hôte. Par conséquent, le fait de définir
reloadOnChange: true pour les fichiers de paramètres XML, JSON et INI n’a aucun

effet.
Pour spécifier l’exécution de l’hôte sur une URL particulière, vous pouvez passer la valeur
souhaitée à partir d’une invite de commandes lors de l’exécution de dotnet run.
L’argument de ligne de commande se substitue à la valeur urls fournie par le fichier
hostsettings.json et le serveur écoute le port 8080 :

CLI .NET

dotnet run --urls "http://*:8080"

Gérer l’hôte
Exécuter

La méthode Run démarre l’application web et bloque le thread appelant jusqu’à l’arrêt
de l’hôte :

C#

host.Run();

Démarrer

Appelez la méthode Start pour exécuter l’hôte en mode non bloquant :

C#

using (host)
{
host.Start();
Console.ReadLine();
}

Si une liste d’URL est passée à la méthode Start , l’hôte écoute les URL spécifiées :

C#

var urls = new List<string>()


{
"http://*:5000",
"http://localhost:5001"
};

var host = new WebHostBuilder()


.UseKestrel()
.UseStartup<Startup>()
.Start(urls.ToArray());

using (host)
{
Console.ReadLine();
}

L’application peut initialiser et démarrer un nouvel hôte ayant les valeurs par défaut
préconfigurées de CreateDefaultBuilder en utilisant une méthode d’usage statique. Ces
méthodes démarrent le serveur sans sortie de console et, avec WaitForShutdown, elles
attendent un arrêt (Ctrl-C/SIGINT ou SIGTERM) :

Start(RequestDelegate app)

Commencez par un RequestDelegate :

C#

using (var host = WebHost.Start(app => app.Response.WriteAsync("Hello,


World!")))
{
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();
}

Effectuez une requête dans le navigateur sur http://localhost:5000 pour recevoir la


réponse « Hello World! » WaitForShutdown se bloque jusqu’à ce qu’un arrêt (Ctrl-
C/SIGINT ou SIGTERM) soit émis. L’application affiche le message Console.WriteLine et
attend que l’utilisateur appuie sur une touche pour s’arrêter.

Start(string url, RequestDelegate app)

Commencez par une URL et RequestDelegate :

C#

using (var host = WebHost.Start("http://localhost:8080", app =>


app.Response.WriteAsync("Hello, World!")))
{
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();
}

Produit le même résultat que Start(RequestDelegate app), sauf que l’application répond
sur http://localhost:8080 .
Start(Action<IRouteBuilder> routeBuilder)

Utilisez une instance de IRouteBuilder (Microsoft.AspNetCore.Routing ) pour utiliser le


middleware de routage :

C#

using (var host = WebHost.Start(router => router


.MapGet("hello/{name}", (req, res, data) =>
res.WriteAsync($"Hello, {data.Values["name"]}!"))
.MapGet("buenosdias/{name}", (req, res, data) =>
res.WriteAsync($"Buenos dias, {data.Values["name"]}!"))
.MapGet("throw/{message?}", (req, res, data) =>
throw new Exception((string)data.Values["message"] ?? "Uh oh!"))
.MapGet("{greeting}/{name}", (req, res, data) =>
res.WriteAsync($"{data.Values["greeting"]},
{data.Values["name"]}!"))
.MapGet("", (req, res, data) => res.WriteAsync("Hello, World!"))))
{
Console.WriteLine("Use Ctrl-C to shutdown the host...");
host.WaitForShutdown();
}

Utilisez les requêtes de navigateur suivantes avec l’exemple :

Requête Response

http://localhost:5000/hello/Martin Hello, Martin!

http://localhost:5000/buenosdias/Catrina Buenos dias, Catrina!

http://localhost:5000/throw/ooops! Lève une exception avec la chaîne « ooops! »

http://localhost:5000/throw Lève une exception avec la chaîne « Uh oh! »

http://localhost:5000/Sante/Kevin Sante, Kevin!

http://localhost:5000 Hello World!

WaitForShutdown bloque la requête jusqu’à l’émission d’une commande d’arrêt (Ctrl-

C/SIGINT ou SIGTERM). L’application affiche le message Console.WriteLine et attend


que l’utilisateur appuie sur une touche pour s’arrêter.

Start(string url, Action<IRouteBuilder> routeBuilder)

Utilisez une URL et une instance de IRouteBuilder :

C#
using (var host = WebHost.Start("http://localhost:8080", router => router
.MapGet("hello/{name}", (req, res, data) =>
res.WriteAsync($"Hello, {data.Values["name"]}!"))
.MapGet("buenosdias/{name}", (req, res, data) =>
res.WriteAsync($"Buenos dias, {data.Values["name"]}!"))
.MapGet("throw/{message?}", (req, res, data) =>
throw new Exception((string)data.Values["message"] ?? "Uh oh!"))
.MapGet("{greeting}/{name}", (req, res, data) =>
res.WriteAsync($"{data.Values["greeting"]},
{data.Values["name"]}!"))
.MapGet("", (req, res, data) => res.WriteAsync("Hello, World!"))))
{
Console.WriteLine("Use Ctrl-C to shut down the host...");
host.WaitForShutdown();
}

Produit le même résultat que Start(Action<IRouteBuilder> routeBuilder), sauf que


l’application répond sur http://localhost:8080 .

StartWith(Action<IApplicationBuilder> app)

Fournissez un délégué pour configurer un IApplicationBuilder :

C#

using (var host = WebHost.StartWith(app =>


app.Use(next =>
{
return async context =>
{
await context.Response.WriteAsync("Hello World!");
};
})))
{
Console.WriteLine("Use Ctrl-C to shut down the host...");
host.WaitForShutdown();
}

Effectuez une requête dans le navigateur sur http://localhost:5000 pour recevoir la


réponse « Hello World! » WaitForShutdown se bloque jusqu’à ce qu’un arrêt (Ctrl-
C/SIGINT ou SIGTERM) soit émis. L’application affiche le message Console.WriteLine et
attend que l’utilisateur appuie sur une touche pour s’arrêter.

StartWith(string url, Action<IApplicationBuilder> app)

Fournissez une URL et un délégué pour configurer un IApplicationBuilder :

C#
using (var host = WebHost.StartWith("http://localhost:8080", app =>
app.Use(next =>
{
return async context =>
{
await context.Response.WriteAsync("Hello World!");
};
})))
{
Console.WriteLine("Use Ctrl-C to shut down the host...");
host.WaitForShutdown();
}

Produit le même résultat que StartWith(Action<IApplicationBuilder> app), sauf que


l’application répond sur http://localhost:8080 .

Interface IWebHostEnvironment
L’interface IWebHostEnvironment fournit des informations sur l’environnement
d’hébergement web de l’application. Utilisez l’injection de constructeur pour obtenir
l’interface IWebHostEnvironment afin d’utiliser ses propriétés et méthodes d’extension :

C#

public class CustomFileReader


{
private readonly IWebHostEnvironment _env;

public CustomFileReader(IWebHostEnvironment env)


{
_env = env;
}

public string ReadFile(string filePath)


{
var fileProvider = _env.WebRootFileProvider;
// Process the file here
}
}

Vous pouvez utiliser une approche basée sur une convention pour configurer
l’application au démarrage en fonction de l’environnement. Vous pouvez également
injecter IWebHostEnvironment dans le constructeur Startup pour l’utiliser dans
ConfigureServices :

C#
public class Startup
{
public Startup(IWebHostEnvironment env)
{
HostingEnvironment = env;
}

public IWebHostEnvironment HostingEnvironment { get; }

public void ConfigureServices(IServiceCollection services)


{
if (HostingEnvironment.IsDevelopment())
{
// Development configuration
}
else
{
// Staging/Production configuration
}

var contentRootPath = HostingEnvironment.ContentRootPath;


}
}

7 Notes

En plus de la méthode d’extension IsDevelopment , IWebHostEnvironment fournit les


méthodes IsStaging , IsProduction et IsEnvironment(string environmentName) .
Pour plus d’informations, consultez Utiliser plusieurs environnements dans
ASP.NET Core.

Le service IWebHostEnvironment peut également être injecté directement dans la


méthode Configure pour configurer le pipeline de traitement :

C#

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)


{
if (env.IsDevelopment())
{
// In Development, use the Developer Exception Page
app.UseDeveloperExceptionPage();
}
else
{
// In Staging/Production, route exceptions to /error
app.UseExceptionHandler("/error");
}
var contentRootPath = env.ContentRootPath;
}

IWebHostEnvironment peut être injecté dans la méthode Invoke lors de la création d’un

intergiciel (middleware) personnalisé :

C#

public async Task Invoke(HttpContext context, IWebHostEnvironment env)


{
if (env.IsDevelopment())
{
// Configure middleware for Development
}
else
{
// Configure middleware for Staging/Production
}

var contentRootPath = env.ContentRootPath;


}

Interface IHostApplicationLifetime
IHostApplicationLifetime s’utilise pour les opérations post-démarrage et arrêt. Trois

propriétés de l’interface sont des jetons d’annulation utilisés pour inscrire les méthodes
Action qui définissent les événements de démarrage et d’arrêt.

Jeton d’annulation Déclenché quand...

ApplicationStarted L’hôte a complètement démarré.

ApplicationStopped L’hôte effectue un arrêt approprié. Toutes les requêtes doivent être
traitées. L’arrêt est bloqué tant que cet événement n’est pas terminé.

ApplicationStopping L’hôte effectue un arrêt approprié. Des requêtes peuvent encore être en
cours de traitement. L’arrêt est bloqué tant que cet événement n’est pas
terminé.

C#

public class Startup


{
public void Configure(IApplicationBuilder app, IHostApplicationLifetime
appLifetime)
{
appLifetime.ApplicationStarted.Register(OnStarted);
appLifetime.ApplicationStopping.Register(OnStopping);
appLifetime.ApplicationStopped.Register(OnStopped);

Console.CancelKeyPress += (sender, eventArgs) =>


{
appLifetime.StopApplication();
// Don't terminate the process immediately, wait for the Main
thread to exit gracefully.
eventArgs.Cancel = true;
};
}

private void OnStarted()


{
// Perform post-startup activities here
}

private void OnStopping()


{
// Perform on-stopping activities here
}

private void OnStopped()


{
// Perform post-stopped activities here
}
}

StopApplication demande l’arrêt de l’application. La classe suivante utilise

StopApplication pour arrêter normalement une application lors de l’appel de la

méthode Shutdown de la classe :

C#

public class MyClass


{
private readonly IHostApplicationLifetime _appLifetime;

public MyClass(IHostApplicationLifetime appLifetime)


{
_appLifetime = appLifetime;
}

public void Shutdown()


{
_appLifetime.StopApplication();
}
}
Validation de l’étendue
CreateDefaultBuilder définit ServiceProviderOptions.ValidateScopes sur true si
l’environnement de l’application est Développement.

Quand ValidateScopes est défini sur true , le fournisseur de services par défaut vérifie
que :

Les services délimités ne sont pas résolus directement ou indirectement à partir du


fournisseur de services racine.
Les services Scoped ne sont pas directement ou indirectement injectés dans des
singletons.

Le fournisseur de services racine est créé quand BuildServiceProvider est appelé. La


durée de vie du fournisseur de services racine correspond à la durée de vie de
l’application/du serveur quand le fournisseur démarre avec l’application et qu’il est
supprimé quand l’application s’arrête.

Les services Scoped sont supprimés par le conteneur qui les a créés. Si un service
Scoped est créé dans le conteneur racine, la durée de vie du service est promue en
singleton, car elle est supprimée par le conteneur racine seulement quand
l’application/le serveur est arrêté. La validation des étendues du service permet de
traiter ces situations quand BuildServiceProvider est appelé.

Pour toujours valider les étendues, notamment dans l’environnement de production,


configurez ServiceProviderOptions avec UseDefaultServiceProvider sur le créateur
d’hôte :

C#

WebHost.CreateDefaultBuilder(args)
.UseDefaultServiceProvider((context, options) => {
options.ValidateScopes = true;
})

Ressources supplémentaires
Héberger ASP.NET Core sur Windows avec IIS
Héberger ASP.NET Core sur Linux avec Nginx
Héberger ASP.NET Core sur Linux avec Apache
Héberger ASP.NET Core dans un service Windows
6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Configuration dans ASP.NET Core
Article • 30/11/2023

Par Rick Anderson et Kirk Larkin

La configuration de l’application dans ASP.NET Core est effectuée à l’aide d’un ou de


plusieurs fournisseurs de configuration. Les fournisseurs de configuration lisent les
données de configuration de paires clé-valeur à l’aide de diverses sources de
configuration :

Fichiers de paramètres, comme appsettings.json


Variables d'environnement
Azure Key Vault
Azure App Configuration
Arguments de ligne de commande
Fournisseurs personnalisés, installés ou créés
Fichiers de répertoire
Objets .NET en mémoire

Cet article fournit des informations sur la configuration dans ASP.NET Core. Pour plus
d’informations sur l’utilisation de la configuration dans des applications de console,
consultez Configuration .NET.

Configuration de l’application et de l’hôte


Les applications ASP.NET Core configurent et lancent un hôte. L’hôte est responsable de
la gestion du démarrage et de la durée de vie des applications. Les modèles ASP.NET
Core créent un WebApplicationBuilder qui contient l’hôte. Une configuration peut être
effectuée à la fois dans les fournisseurs de configuration de l’hôte et de l’application.
Toutefois, seule la configuration nécessaire à l’hôte doit généralement être effectuée
dans la configuration de l’hôte.

La configuration de l’application représente la priorité la plus élevée. Elle est détaillée


dans la section suivante. La configuration de l’hôte suit la configuration de l’application.
Elle est décrite dans cet article.

Sources de configuration de l’application par défaut


Les applications web ASP.NET Core créées avec dotnet new ou Visual Studio génèrent le
code suivant :
C#

var builder = WebApplication.CreateBuilder(args);

WebApplication.CreateBuilder initialise une nouvelle instance de la classe


WebApplicationBuilder avec les valeurs par défaut préconfigurées. Le
WebApplicationBuilder ( builder ) initialisé fournit la configuration de l’application par

défaut dans l’ordre suivant, de la priorité la plus élevée à la plus basse :

1. Arguments de ligne de commande avec le fournisseur de configuration de ligne de


commande.
2. Variables d’environnement sans préfixe avec le fournisseur de configuration des
variables d’environnement.
3. Les secrets utilisateur quand l’application s’exécute dans l’environnement
Development

4. appsettings.{Environment}.json avec le fournisseur de configuration JSON. Par


exemple : appsettings.Production.json et appsettings.Development.json .
5. appSettings.JSON avec le fournisseur de configuration JSON.
6. Solution de secours à la configuration de l’hôte décrite dans la section suivante.

Sources de configuration de l’hôte par défaut


La liste suivante contient les sources de configuration de l’hôte par défaut de la priorité
la plus élevée à la plus basse pour WebApplicationBuilder :

1. Arguments de ligne de commande avec le fournisseur de configuration de ligne de


commande
2. Variables d’environnement avec le préfixe DOTNET_ avec le fournisseur de
configuration des variables d’environnement.
3. Variables d’environnement avec le préfixe ASPNETCORE_ avec le fournisseur de
configuration des variables d’environnement.

Pour l’hôte générique .NET et l’hôte web, les sources de configuration de l’hôte par
défaut de la priorité la plus élevée à la priorité la plus basse sont les suivantes :

1. Variables d’environnement avec le préfixe ASPNETCORE_ avec le fournisseur de


configuration des variables d’environnement.
2. Arguments de ligne de commande avec le fournisseur de configuration de ligne de
commande
3. Variables d’environnement avec le préfixe DOTNET_ avec le fournisseur de
configuration des variables d’environnement.
Quand une valeur de configuration est définie dans la configuration de l’hôte et de
l’application, la configuration de l’application est utilisée.

Variables de l’hôte
Les variables suivantes sont verrouillées au début lors de l’initialisation des générateurs
d’hôtes. Elles ne peuvent pas être affectées via la configuration de l’application :

Nom de l’application
Nom de l’environnement, par exemple Development , Production et Staging
Racine de contenu
Racine web
Indique s’il faut effectuer une analyse à la recherche d’assemblys d’hébergement
au démarrage et spécifie les assemblys à analyser.
Les variables lues par le code de l’application et de la bibliothèque à partir de
HostBuilderContext.Configuration dans les rappels de
IHostBuilder.ConfigureAppConfiguration.

Tous les autres paramètres d’hôte sont lus à partir de la configuration de l’application et
non de la configuration de l’hôte.

URLS est l’un des nombreux paramètres courants de l’hôte qui n’est pas un paramètre

d’amorçage. Comme tous les autres paramètres d’hôte ne figurant pas dans la liste
précédente, URLS est lu ultérieurement à partir de la configuration de l’application. La
configuration de l’hôte est une solution de secours pour la configuration de
l’application. La configuration de l’hôte peut donc être utilisée pour définir URLS , mais
elle sera remplacée par toute source de configuration dans la configuration de
l’application, comme appsettings.json .

Pour plus d’informations, consultez Modifier la racine du contenu, le nom de


l’application et l’environnement et Modifier la racine du contenu, le nom de l’application
et l’environnement à l’aide de variables d’environnement ou de la ligne de commande

Les sections suivantes de cet article font référence à la configuration de l’application.

Fournisseurs de configuration de l’application


Le code suivant affiche les fournisseurs de configuration activés dans l’ordre dans lequel
ils ont été ajoutés :

C#
public class Index2Model : PageModel
{
private IConfigurationRoot ConfigRoot;

public Index2Model(IConfiguration configRoot)


{
ConfigRoot = (IConfigurationRoot)configRoot;
}

public ContentResult OnGet()


{
string str = "";
foreach (var provider in ConfigRoot.Providers.ToList())
{
str += provider.ToString() + "\n";
}

return Content(str);
}
}

La liste des sources de configuration par défaut classées de la priorité la plus élevée à la
plus basse précédente affiche les fournisseurs dans l’ordre inverse de leur ajout à
l’application générée par le modèle. Par exemple, le fournisseur de configuration JSON
est ajouté avant le fournisseur de configuration de ligne de commande.

Les fournisseurs de configuration ajoutés ultérieurement ont une priorité plus élevée. Ils
remplacent les paramètres de clé précédents. Par exemple, si MyKey est défini dans
appsettings.json et dans l’environnement, la valeur d’environnement est utilisée. À

l’aide des fournisseurs de configuration par défaut, le fournisseur de configuration de


ligne de commande remplace les valeurs de tous les autres fournisseurs.

Pour plus d’informations sur CreateBuilder , consultez Paramètres du générateur par


défaut.

appsettings.json

Examinons le fichier appsettings.json suivant :

JSON

{
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
},
"MyKey": "My appsettings.json Value",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

Le code suivant de l’exemple de téléchargement affiche certains des paramètres de


configuration précédents :

C#

public class TestModel : PageModel


{
// requires using Microsoft.Extensions.Configuration;
private readonly IConfiguration Configuration;

public TestModel(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var myKeyValue = Configuration["MyKey"];
var title = Configuration["Position:Title"];
var name = Configuration["Position:Name"];
var defaultLogLevel = Configuration["Logging:LogLevel:Default"];

return Content($"MyKey value: {myKeyValue} \n" +


$"Title: {title} \n" +
$"Name: {name} \n" +
$"Default Log Level: {defaultLogLevel}");
}
}

Le JsonConfigurationProvider par défaut charge la configuration dans l’ordre suivant :

1. appsettings.json
2. appsettings.{Environment}.json : par exemple les fichiers
appsettings.Production.json et appsettings.Development.json . La version de

l’environnement du fichier est chargée en fonction de


IHostingEnvironment.EnvironmentName. Pour plus d’informations, consultez
Utiliser plusieurs environnements dans ASP.NET Core.
Les valeurs appsettings.{Environment}.json remplacent les clés dans appsettings.json .
Par exemple, par défaut :

En cours de développement, la configuration appsettings.Development.json


remplace les valeurs trouvées dans appsettings.json .
En production, la configuration appsettings.Production.json remplace les valeurs
trouvées dans appsettings.json . Par exemple, lors du déploiement de l’application
sur Azure.

Si une valeur de configuration doit être garantie, consultez GetValue. L’exemple


précédent lit uniquement les chaînes. Il ne prend pas en charge de valeur par défaut.

À l’aide de la configuration par défaut , les fichiers appsettings.json et appsettings.


{Environment}.json sont activés avec reloadOnChange: true . Les modifications
apportées aux fichiers appsettings.json et appsettings.{Environment}.json après le
démarrage de l’application sont lues par le fournisseur de configuration JSON.

Commentaires dans appsettings.json


Les commentaires dans les fichiers appsettings.json et appsettings.
{Environment}.json sont pris en charge à l’aide des commentaires de style JavaScript ou

C#.

Lier des données de configuration hiérarchiques à l’aide


du modèle d’options
La meilleure méthode pour lire les valeurs de configuration associées consiste à utiliser
le modèle d’options. Par exemple, pour lire les valeurs de configuration suivantes :

JSON

"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}

Créez la classe PositionOptions suivante :

C#

public class PositionOptions


{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}

Classe d’options :

elle doit être non abstraite avec un constructeur public sans paramètre.
Toutes les propriétés publiques en lecture/écriture du type sont liées.
Les champs ne sont pas liés. Dans le code précédent, Position n’est pas lié. Le
champ Position est utilisé pour qu’il ne soit pas nécessaire de coder la chaîne
"Position" en dur dans l’application lors de la liaison de la classe à un fournisseur

de configuration.

Le code suivant :

Appelle ConfigurationBinder.Bind pour lier la classe PositionOptions à la section


Position .

Affiche les données de configuration Position .

C#

public class Test22Model : PageModel


{
private readonly IConfiguration Configuration;

public Test22Model(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var positionOptions = new PositionOptions();

Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

return Content($"Title: {positionOptions.Title} \n" +


$"Name: {positionOptions.Name}");
}
}

Dans le code précédent, par défaut, les modifications apportées au fichier de


configuration JSON après le démarrage de l’application sont lues.

ConfigurationBinder.Get<T> lie et retourne le type spécifié. Il peut être plus pratique


d’utiliser ConfigurationBinder.Get<T> que ConfigurationBinder.Bind . Le code suivant
illustre la classe ConfigurationBinder.Get<T> avec la classe PositionOptions :

C#

public class Test21Model : PageModel


{
private readonly IConfiguration Configuration;
public PositionOptions? positionOptions { get; private set; }

public Test21Model(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
positionOptions = Configuration.GetSection(PositionOptions.Position)
.Get<PositionOptions>
();

return Content($"Title: {positionOptions.Title} \n" +


$"Name: {positionOptions.Name}");
}
}

Dans le code précédent, par défaut, les modifications apportées au fichier de


configuration JSON après le démarrage de l’application sont lues.

Une autre approche lors de l’utilisation du modèle d’options consiste à lier la section
Position et à l’ajouter au conteneur de service d’injection de dépendances. Dans le

code suivant, PositionOptions est ajouté au conteneur de service avec Configure et lié à
la configuration :

C#

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

À l’aide du code précédent, le code suivant lit les options de position :


C#

public class Test2Model : PageModel


{
private readonly PositionOptions _options;

public Test2Model(IOptions<PositionOptions> options)


{
_options = options.Value;
}

public ContentResult OnGet()


{
return Content($"Title: {_options.Title} \n" +
$"Name: {_options.Name}");
}
}

Dans le code précédent, les modifications apportées au fichier de configuration JSON


après le démarrage de l’application ne sont pas lues. Pour lire les modifications après
que l’application a démarré, utilisez IOptionsSnapshot.

À l’aide de la configuration par défaut , les fichiers appsettings.json et appsettings.


{Environment}.json sont activés avec reloadOnChange: true . Les modifications
apportées aux fichiers appsettings.json et appsettings.{Environment}.json après le
démarrage de l’application sont lues par le fournisseur de configuration JSON.

Consultez Fournisseur de configurationJSON dans ce document pour plus


d’informations sur l’ajout de fichiers de configuration JSON.

Combinaison de la collection de services


Examinons les éléments suivants permettant d’inscrire les services et de configurer les
options :

C#

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
builder.Configuration.GetSection(ColorOptions.Color));
builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Les groupes d’inscriptions associés peuvent être déplacés vers une méthode d’extension
pour inscrire les services. Les services de configuration sont par exemple ajoutés à la
classe suivante :

C#

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
public static class MyConfigServiceCollectionExtensions
{
public static IServiceCollection AddConfig(
this IServiceCollection services, IConfiguration config)
{
services.Configure<PositionOptions>(
config.GetSection(PositionOptions.Position));
services.Configure<ColorOptions>(
config.GetSection(ColorOptions.Color));

return services;
}

public static IServiceCollection AddMyDependencyGroup(


this IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyDependency2, MyDependency2>();

return services;
}
}
}

Les services qui restent sont inscrits dans une classe similaire. Le code suivant utilise les
nouvelles méthodes d’extension pour inscrire les services :

C#

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);


builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Remarque : chaque méthode d’extension services.Add{GROUP_NAME} ajoute des services


et les configure éventuellement. Par exemple, AddControllersWithViews ajoute les
services nécessaires à MVC avec vues et AddRazorPages ajoute les services nécessaires
aux pages Razor.

Sécurité et secrets d’utilisateur


Instructions relatives aux données de configuration :

Ne stockez jamais des mots de passe ou d’autres données sensibles dans le code
du fournisseur de configuration ou dans les fichiers de configuration en texte clair.
Vous pouvez utiliser l’outil Secret Manager pour stocker des secrets lors du
développement.
N’utilisez aucun secret de production dans les environnements de développement
ou de test.
Spécifiez les secrets en dehors du projet afin qu’ils ne puissent pas être validés par
inadvertance dans un référentiel de code source.

Par défaut, la source de configuration des secrets de l’utilisateur est inscrite après les
sources de configuration JSON. Par conséquent, les clés des secrets utilisateur sont
prioritaires sur les clés dans appsettings.json et appsettings.{Environment}.json .

Pour plus d’informations sur le stockage des mots de passe ou d’autres données
sensibles :

Utiliser plusieurs environnements dans ASP.NET Core


Stockage sécurisé des secrets d’application en développement dans ASP.NET
Core : comprend des conseils sur l’utilisation de variables d’environnement pour
stocker les données sensibles. L’outil Secret Manager utilise le fournisseur de
configuration de fichier pour stocker les secrets utilisateur dans un fichier JSON sur
le système local.

Azure Key Vault stocke en toute sécurité des secrets d’application pour les
applications ASP.NET Core. Pour plus d’informations, consultez Fournisseur de
configuration Azure Key Vault dans ASP.NET Core.
Variables d’environnement sans préfixe
Les variables d’environnement sans préfixe sont des variables d’environnement autres
que celles avec le préfixe ASPNETCORE_ ou DOTNET_ . Par exemple, l’ensemble de modèles
d’application web ASP.NET Core "ASPNETCORE_ENVIRONMENT": "Development" dans
launchSettings.json . Pour plus d’informations sur les variables d’environnement

ASPNETCORE_ et DOTNET_ , consultez :

Liste des sources de configuration par défaut classées de la priorité est la plus
élevée à la plus basse, notamment les variables d’environnement sans préfixe, avec
le préfixe ASPNETCORE_ et avec le préfixe DOTNETCORE_ .
DOTNET_ variables d’environnement utilisées en dehors de
Microsoft.Extensions.Hosting.

À l’aide de la configuration par défaut, EnvironmentVariablesConfigurationProvider


charge la configuration à partir de paires clé-valeur d’environnement après avoir lu
appsettings.json , appsettings.{Environment}.json et les secrets de l’utilisateur. Par

conséquent, les valeurs de clés lues à partir de l’environnement remplacent les valeurs
lues à partir de appsettings.json , appsettings.{Environment}.json et les secrets de
l’utilisateur.

Le séparateur : ne fonctionne pas avec les clés hiérarchiques des variables


d’environnement sur toutes les plateformes. Le double trait de soulignement __ est :

Pris en charge par toutes les plateformes. Par exemple, le séparateur : n’est pas
pris en charge par Bash , mais __ est pris en charge.
Remplacé automatiquement par :

Les commandes suivantes set permettent de :

Définir les clés d’environnement et les valeurs de l’exemple précédent sur


Windows.
Testez les paramètres lors de l’utilisation de l’exemple de téléchargement . La
commande dotnet run doit être exécutée dans le répertoire du projet.

CLI .NET

set MyKey="My key from Environment"


set Position__Title=Environment_Editor
set Position__Name=Environment_Rick
dotnet run

Paramètres d’environnement précédents :


Définis uniquement dans les processus lancés à partir de la fenêtre de commande
dans laquelle ils ont été définis.
Ne seront pas lus par les navigateurs lancés avec Visual Studio.

Les commandes setx suivantes peuvent être utilisées pour définir les clés et les valeurs
d’environnement sur Windows. Contrairement à set , les paramètres setx sont
persistants. /M définit la variable dans l’environnement système. Si le commutateur /M
n’est pas utilisé, une variable d’environnement utilisateur est définie.

Console

setx MyKey "My key from setx Environment" /M


setx Position__Title Environment_Editor /M
setx Position__Name Environment_Rick /M

Pour vérifier que les commandes précédentes remplacent appsettings.json et


appsettings.{Environment}.json :

Avec Visual Studio : quittez et redémarrez Visual Studio.


Avec l’interface CLI : démarrez une nouvelle fenêtre de commande et entrez dotnet
run .

Appelez AddEnvironmentVariables avec une chaîne pour spécifier un préfixe pour les
variables d’environnement :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Configuration.AddEnvironmentVariables(prefix: "MyCustomPrefix_");

var app = builder.Build();

Dans le code précédent :

builder.Configuration.AddEnvironmentVariables(prefix: "MyCustomPrefix_") est

ajouté après les fournisseurs de configuration par défaut. Pour obtenir un exemple
de classement des fournisseurs de configuration, consultez Fournisseur de
configuration JSON.
Les variables d’environnement définies avec le préfixe MyCustomPrefix_ remplacent
les fournisseurs de configuration par défaut. Il s’agit notamment des variables
d’environnement sans le préfixe.
Le préfixe est supprimé quand les paires clé-valeur de la configuration sont lues.

Les commandes suivantes testent le préfixe personnalisé :

CLI .NET

set MyCustomPrefix_MyKey="My key with MyCustomPrefix_ Environment"


set MyCustomPrefix_Position__Title=Editor_with_customPrefix
set MyCustomPrefix_Position__Name=Environment_Rick_cp
dotnet run

La configuration par défaut charge les variables d’environnement et les arguments de


ligne de commande précédés du préfixe DOTNET_ et ASPNETCORE_ . Les préfixes DOTNET_
et ASPNETCORE_ sont utilisés par ASP.NET Core pour la configuration de l’hôte et de
l’application, mais pas pour la configuration de l’utilisateur. Pour plus d’informations sur
la configuration de l’hôte et de l’application, consultez Hôte générique .NET.

Dans Azure App Service , sélectionnez Nouveau paramètre d’application dans la page
Paramètres > Configuration. Les paramètres d’application Azure App Service sont :

Chiffrés au repos et transmis sur un canal chiffré.


Exposés en tant que variables d’environnement.

Pour plus d’informations, consultez Azure Apps : remplacer la configuration de


l’application à l’aide du portail Azure.

Consultez Préfixes des chaînes de connexion pour plus d’informations sur les chaînes de
connexion de base de données Azure.

Nommage des variables d’environnement


Les noms de variable d’environnement reflètent la structure d’un fichier
appsettings.json . Chaque élément de la hiérarchie est séparé par un trait de

soulignement double (solution préférable) ou par deux-points. Quand la structure de


l’élément comprend un tableau, l’index du tableau doit être traité comme un nom
d’élément supplémentaire dans ce chemin d’accès. Examinons le fichier
appsettings.json suivant et les valeurs équivalentes représentées sous forme de

variables d’environnement.

appsettings.json

JSON
{
"SmtpServer": "smtp.example.com",
"Logging": [
{
"Name": "ToEmail",
"Level": "Critical",
"Args": {
"FromAddress": "MySystem@example.com",
"ToAddress": "SRE@example.com"
}
},
{
"Name": "ToConsole",
"Level": "Information"
}
]
}

Variables d’environnement

Console

setx SmtpServer smtp.example.com


setx Logging__0__Name ToEmail
setx Logging__0__Level Critical
setx Logging__0__Args__FromAddress MySystem@example.com
setx Logging__0__Args__ToAddress SRE@example.com
setx Logging__1__Name ToConsole
setx Logging__1__Level Information

Variables d’environnement définies dans le fichier


launchSettings.json généré
Les variables d’environnement définies dans launchSettings.json remplacent celles qui
sont définies dans l’environnement système. Par exemple, les modèles web ASP.NET
Core génèrent un fichier launchSettings.json qui définit la configuration du point de
terminaison sur :

JSON

"applicationUrl": "https://localhost:5001;http://localhost:5000"

Configurer applicationUrl permet de définir la variable d’environnement


ASPNETCORE_URLS et de remplacer les valeurs définies dans l’environnement.
Échappement des variables d’environnement sur Linux
Sur Linux, la valeur des variables d’environnement d’URL doit être placée dans une
séquence d’échappement pour que systemd puisse l’analyser. Utiliser l’outil Linux
systemd-escape qui interrompt http:--localhost:5001

Invite de commandes Windows

groot@terminus:~$ systemd-escape http://localhost:5001


http:--localhost:5001

Afficher les variables d’environnement


Le code suivant affiche les variables d’environnement et les valeurs au démarrage de
l’application, ce qui peut être utile lors du débogage des paramètres d’environnement :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

foreach (var c in builder.Configuration.AsEnumerable())


{
Console.WriteLine(c.Key + " = " + c.Value);
}

Ligne de commande
À l’aide de la configuration par défaut, CommandLineConfigurationProvider charge la
configuration à partir de paires clé-valeur d’argument de ligne de commande après les
sources de configuration suivantes :

Fichiers appsettings.json et appsettings.{Environment}.json .


Secrets de l’application dans l’environnement de développement.
Variables d'environnement.

Par défaut, les valeurs de configuration définies sur la ligne de commande remplacent
les valeurs de configuration définies avec tous les autres fournisseurs de configuration.

Arguments de ligne de commande


La commande suivante définit des clés et des valeurs à l’aide de = :
CLI .NET

dotnet run MyKey="Using =" Position:Title=Cmd Position:Name=Cmd_Rick

La commande suivante définit des clés et des valeurs à l’aide de / :

CLI .NET

dotnet run /MyKey "Using /" /Position:Title=Cmd /Position:Name=Cmd_Rick

La commande suivante définit des clés et des valeurs à l’aide de -- :

CLI .NET

dotnet run --MyKey "Using --" --Position:Title=Cmd --Position:Name=Cmd_Rick

Valeur de clé :

Elle doit suivre = ou la clé doit avoir un préfixe -- ou / quand la valeur suit un
espace.
N’est pas obligatoire si l’option = est utilisée. Par exemple, MySetting=

Dans la même commande, ne mélangez pas des paires clé-valeur de l’argument de ligne
de commande qui utilisent = avec des paires clé-valeur qui utilisent un espace.

Correspondances de commutateur
Les correspondances de commutateur permettent une logique de remplacement des
noms de clé. Fournit un dictionnaire des remplacements de commutateur à la méthode
AddCommandLine.

Quand le dictionnaire de correspondances de commutateur est utilisé, il est vérifié afin


de déterminer s’il contient une clé correspondant à celle fournie par un argument de
ligne de commande. Si la clé de ligne de commande est trouvée dans le dictionnaire, la
valeur du dictionnaire est retournée pour définir la paire clé-valeur dans la configuration
de l’application. Une correspondance de commutateur est nécessaire pour chaque clé
de ligne de commande préfixée avec un tiret unique ( - ).

Règles des clés du dictionnaire de correspondances de commutateur :

Les commutateurs doivent commencer par - ou -- .


Le dictionnaire de correspondances de commutateur ne doit pas contenir de clés
en double.
Pour utiliser un dictionnaire de correspondances de commutateur, transmettez-le dans
l’appel à AddCommandLine :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var switchMappings = new Dictionary<string, string>()


{
{ "-k1", "key1" },
{ "-k2", "key2" },
{ "--alt3", "key3" },
{ "--alt4", "key4" },
{ "--alt5", "key5" },
{ "--alt6", "key6" },
};

builder.Configuration.AddCommandLine(args, switchMappings);

var app = builder.Build();

Exécutez la commande suivante pour tester le remplacement de la clé :

CLI .NET

dotnet run -k1 value1 -k2 value2 --alt3=value2 /alt4=value3 --alt5 value5
/alt6 value6

Le code suivant montre les valeurs de clé pour les clés remplacées :

C#

public class Test3Model : PageModel


{
private readonly IConfiguration Config;

public Test3Model(IConfiguration configuration)


{
Config = configuration;
}

public ContentResult OnGet()


{
return Content(
$"Key1: '{Config["Key1"]}'\n" +
$"Key2: '{Config["Key2"]}'\n" +
$"Key3: '{Config["Key3"]}'\n" +
$"Key4: '{Config["Key4"]}'\n" +
$"Key5: '{Config["Key5"]}'\n" +
$"Key6: '{Config["Key6"]}'");
}
}

Pour les applications qui utilisent des mappages de commutateurs, l’appel à


CreateDefaultBuilder ne doit pas passer d’arguments. L’appel AddCommandLine de la

méthode CreateDefaultBuilder n’inclut pas de commutateurs mappés. Il n’existe aucun


moyen de transmettre le dictionnaire de mappage de commutateur à
CreateDefaultBuilder . La solution ne consiste pas à transmettre les arguments à

CreateDefaultBuilder , mais à permettre à la méthode AddCommandLine de la méthode


ConfigurationBuilder de traiter à la fois les arguments et le dictionnaire de

correspondance de commutateur.

Définir les arguments d’environnement et de


ligne de commande avec Visual Studio
Les arguments d’environnement et de ligne de commande peuvent être définis dans
Visual Studio à partir de la boîte de dialogue Profils de lancement :

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet et


sélectionnez Propriétés.
Sélectionnez l’onglet Débogage > Général et sélectionnez Ouvrir l’IU Profils de
lancement de débogage.

Données de configuration hiérarchiques


L’API de configuration lit les données de configuration hiérarchiques en aplanissant les
données hiérarchiques à l’aide d’un délimiteur dans les clés de configuration.

L’exemple de téléchargement contient le fichier appsettings.json suivant :

JSON

{
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
},
"MyKey": "My appsettings.json Value",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

Le code suivant de l’exemple de téléchargement affiche certains des paramètres de


configuration :

C#

public class TestModel : PageModel


{
// requires using Microsoft.Extensions.Configuration;
private readonly IConfiguration Configuration;

public TestModel(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var myKeyValue = Configuration["MyKey"];
var title = Configuration["Position:Title"];
var name = Configuration["Position:Name"];
var defaultLogLevel = Configuration["Logging:LogLevel:Default"];

return Content($"MyKey value: {myKeyValue} \n" +


$"Title: {title} \n" +
$"Name: {name} \n" +
$"Default Log Level: {defaultLogLevel}");
}
}

La meilleure méthode pour lire les données de configuration hiérarchiques consiste à


utiliser le modèle d’options. Pour plus d’informations, consultez Lier des données de
configuration hiérarchiques à l’aide du modèle d’options dans ce document.

Les méthodes GetSection et GetChildren sont disponibles pour isoler les sections et les
enfants d’une section dans les données de configuration. Ces méthodes sont décrites
plus loin dans GetSection, GetChildren et Exists.

Clés et valeurs de configuration


Clés de configuration :

Ne respectent pas la casse. Par exemple, ConnectionString et connectionstring


sont traités en tant que clés équivalentes.
Si une clé et une valeur sont définies dans plus d’un fournisseur de configuration,
la valeur du dernier fournisseur ajouté est utilisée. Pour plus d’informations,
consultez Configuration par défaut.
Clés hiérarchiques
Dans l’API Configuration, un séparateur sous forme de signe deux-points ( : )
fonctionne sur toutes les plateformes.
Dans les variables d’environnement, un séparateur sous forme de signe deux-
points peut ne pas fonctionner sur toutes les plateformes. Un trait de
soulignement double __ est pris en charge par toutes les plateformes. Il est
automatiquement transformé en signe deux-points : .
Dans Azure Key Vault, les clés hiérarchiques utilisent -- comme séparateur. Le
fournisseur de configuration Azure Key Vault remplace automatiquement --
par : quand les secrets sont chargés dans la configuration de l’application.
ConfigurationBinder prend en charge la liaison de tableaux à des objets à l’aide
d’index de tableau dans les clés de configuration. La liaison de tableau est décrite
dans la section Lier un tableau à une classe.

Valeurs de configuration :

Les valeurs sont des chaînes.


Les valeurs NULL ne peuvent pas être stockées dans la configuration ou liées à des
objets.

Fournisseurs de configuration
Le tableau suivant présente les fournisseurs de configuration disponibles pour les
applications ASP.NET Core.

Fournisseur Fournit la configuration à partir de

Fournisseur de configuration Azure Key Vault Azure Key Vault

Fournisseur de configuration Azure App Azure App Configuration

Fournisseur de configuration de ligne de commande Paramètres de ligne de commande

Fournisseur de configuration personnalisé Source personnalisée

Fournisseur de configuration de variables Variables d'environnement


Fournisseur Fournit la configuration à partir de

d’environnement

Fournisseur de configuration de fichier Fichiers INI, JSON et XML

Fournisseur de configuration clé par fichier Fichiers de répertoire

Fournisseur de configuration de mémoire Collections en mémoire

Secrets utilisateur Fichier dans le répertoire de profil


utilisateur

Les sources de configuration sont lues dans l’ordre où leurs fournisseurs de


configuration sont spécifiés. Organisez les fournisseurs de configuration dans le code en
fonction des priorités pour les sources de configuration sous-jacentes nécessaires pour
l’application.

Une séquence type des fournisseurs de configuration est la suivante :

1. appsettings.json
2. appsettings.{Environment}.json
3. Secrets utilisateur
4. Variables d’environnement avec le fournisseur de configuration des variables
d’environnement.
5. Arguments de ligne de commande avec le fournisseur de configuration de ligne de
commande.

Une pratique courante consiste à ajouter le fournisseur de configuration de ligne de


commande en dernier dans une série de fournisseurs pour permettre aux arguments de
ligne de commande de remplacer la configuration définie par les autres fournisseurs.

La séquence de fournisseurs précédente est utilisée dans la configuration par défaut.

Préfixes des chaînes de connexion


L’API de configuration présente des règles de traitement spéciales pour quatre variables
d’environnement de chaîne de connexion. Ces chaînes de connexion sont impliquées
dans la configuration des chaînes de connexion Azure pour l’environnement de
l’application. Les variables d’environnement avec les préfixes indiqués dans le tableau
sont chargées dans l’application avec la configuration par défaut ou si aucun préfixe
n’est fourni à AddEnvironmentVariables .
Préfixe de la chaîne de connexion Fournisseur

CUSTOMCONNSTR_ Fournisseur personnalisé

MYSQLCONNSTR_ MySQL

SQLAZURECONNSTR_ Azure SQL Database

SQLCONNSTR_ SQL Server

Quand une variable d’environnement est découverte et chargée dans la configuration


avec l’un des quatre préfixes indiqués dans le tableau :

La clé de configuration est créée en supprimant le préfixe de la variable


d’environnement et en ajoutant une section de clé de configuration
( ConnectionStrings ).
Une nouvelle paire clé-valeur de configuration est créée qui représente le
fournisseur de connexion de base de données (à l’exception de CUSTOMCONNSTR_ ,
qui ne possède aucun fournisseur indiqué).

Clé de variable Clé de configuration Entrée de configuration de


d’environnement convertie fournisseur

CUSTOMCONNSTR_{KEY} ConnectionStrings:{KEY} Entrée de configuration non créée.

MYSQLCONNSTR_{KEY} ConnectionStrings:{KEY} Clé : ConnectionStrings:


{KEY}_ProviderName :
Valeur: MySql.Data.MySqlClient

SQLAZURECONNSTR_{KEY} ConnectionStrings:{KEY} Clé : ConnectionStrings:


{KEY}_ProviderName :
Valeur: System.Data.SqlClient

SQLCONNSTR_{KEY} ConnectionStrings:{KEY} Clé : ConnectionStrings:


{KEY}_ProviderName :
Valeur: System.Data.SqlClient

Fournisseur de configuration de fichier


FileConfigurationProvider est la classe de base pour charger la configuration à partir du
système de fichiers. Les fournisseurs de configuration suivants dérivent de
FileConfigurationProvider :

Fournisseur de configuration INI


Fournisseur de configuration JSON
Fournisseur de configuration XML

Fournisseur de configuration INI


IniConfigurationProvider charge la configuration à partir des paires clé-valeur du fichier
INI lors de l’exécution.

Le code suivant ajoute plusieurs fournisseurs de configuration :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
.AddIniFile("MyIniConfig.ini", optional: true, reloadOnChange: true)
.AddIniFile($"MyIniConfig.{builder.Environment.EnvironmentName}.ini",
optional: true, reloadOnChange: true);

builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);

builder.Services.AddRazorPages();

var app = builder.Build();

Dans le code précédent, les paramètres des fichiers MyIniConfig.ini et MyIniConfig.


{Environment}.ini sont remplacés par les paramètres dans :

Fournisseur de configuration de variables d’environnement


Fournisseur de configuration de ligne de commande.

L’exemple de téléchargement contient le fichier MyIniConfig.ini suivant :

ini

MyKey="MyIniConfig.ini Value"

[Position]
Title="My INI Config title"
Name="My INI Config name"

[Logging:LogLevel]
Default=Information
Microsoft=Warning

Le code suivant de l’exemple de téléchargement affiche certains des paramètres de


configuration précédents :
C#

public class TestModel : PageModel


{
// requires using Microsoft.Extensions.Configuration;
private readonly IConfiguration Configuration;

public TestModel(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var myKeyValue = Configuration["MyKey"];
var title = Configuration["Position:Title"];
var name = Configuration["Position:Name"];
var defaultLogLevel = Configuration["Logging:LogLevel:Default"];

return Content($"MyKey value: {myKeyValue} \n" +


$"Title: {title} \n" +
$"Name: {name} \n" +
$"Default Log Level: {defaultLogLevel}");
}
}

Fournisseur de configuration JSON


JsonConfigurationProvider charge la configuration à partir des paires clé-valeur du
fichier JSON.

Les surcharges permettent de spécifier :

Si le fichier est facultatif.


Si la configuration est rechargée quand le fichier est modifié.

Prenez le code suivant :

C#

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddJsonFile("MyConfig.json",
optional: true,
reloadOnChange: true);

builder.Services.AddRazorPages();
var app = builder.Build();

Le code précédent :

Configure le fournisseur de configuration JSON pour charger le fichier


MyConfig.json avec les options suivantes :
optional: true : le fichier est facultatif.

reloadOnChange: true : le fichier est rechargé quand des modifications sont

enregistrées.
Lit les fournisseurs de configuration par défaut avant le fichier MyConfig.json . Les
paramètres dans le fichier MyConfig.json remplacent les paramètres par défaut
dans les fournisseurs de configuration, notamment le fournisseur de configuration
des variables d’environnement et le fournisseur de configuration de ligne de
commande.

En règle générale, vous ne souhaitez pas qu’un fichier JSON personnalisé écrase les
valeurs définies dans le fournisseur de configuration des variables d’environnement et le
fournisseur de configuration de ligne de commande.

Fournisseur de configuration XML


XmlConfigurationProvider charge la configuration à partir des paires clé-valeur du
fichier XML lors de l’exécution.

Le code suivant ajoute plusieurs fournisseurs de configuration :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
.AddXmlFile("MyXMLFile.xml", optional: true, reloadOnChange: true)
.AddXmlFile($"MyXMLFile.{builder.Environment.EnvironmentName}.xml",
optional: true, reloadOnChange: true);

builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);

builder.Services.AddRazorPages();

var app = builder.Build();

Dans le code précédent, les paramètres des fichiers MyXMLFile.xml et MyXMLFile.


{Environment}.xml sont remplacés par les paramètres dans :
Fournisseur de configuration de variables d’environnement
Fournisseur de configuration de ligne de commande.

L’exemple de téléchargement contient le fichier MyXMLFile.xml suivant :

XML

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<MyKey>MyXMLFile Value</MyKey>
<Position>
<Title>Title from MyXMLFile</Title>
<Name>Name from MyXMLFile</Name>
</Position>
<Logging>
<LogLevel>
<Default>Information</Default>
<Microsoft>Warning</Microsoft>
</LogLevel>
</Logging>
</configuration>

Le code suivant de l’exemple de téléchargement affiche certains des paramètres de


configuration précédents :

C#

public class TestModel : PageModel


{
// requires using Microsoft.Extensions.Configuration;
private readonly IConfiguration Configuration;

public TestModel(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var myKeyValue = Configuration["MyKey"];
var title = Configuration["Position:Title"];
var name = Configuration["Position:Name"];
var defaultLogLevel = Configuration["Logging:LogLevel:Default"];

return Content($"MyKey value: {myKeyValue} \n" +


$"Title: {title} \n" +
$"Name: {name} \n" +
$"Default Log Level: {defaultLogLevel}");
}
}

Les éléments répétitifs qui utilisent le même nom d’élément fonctionnent si l’attribut
name est utilisé pour distinguer les éléments :

XML

<?xml version="1.0" encoding="UTF-8"?>


<configuration>
<section name="section0">
<key name="key0">value 00</key>
<key name="key1">value 01</key>
</section>
<section name="section1">
<key name="key0">value 10</key>
<key name="key1">value 11</key>
</section>
</configuration>

Le code suivant lit le fichier de configuration précédent et affiche les clés et les valeurs :

C#

public class IndexModel : PageModel


{
private readonly IConfiguration Configuration;

public IndexModel(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var key00 = "section:section0:key:key0";
var key01 = "section:section0:key:key1";
var key10 = "section:section1:key:key0";
var key11 = "section:section1:key:key1";

var val00 = Configuration[key00];


var val01 = Configuration[key01];
var val10 = Configuration[key10];
var val11 = Configuration[key11];

return Content($"{key00} value: {val00} \n" +


$"{key01} value: {val01} \n" +
$"{key10} value: {val10} \n" +
$"{key10} value: {val11} \n"
);
}
}

Les attributs peuvent être utilisés pour fournir des valeurs :

XML

<?xml version="1.0" encoding="UTF-8"?>


<configuration>
<key attribute="value" />
<section>
<key attribute="value" />
</section>
</configuration>

Le fichier de configuration précédent charge les clés suivantes avec value :

key:attribute
section:key:attribute

Fournisseur de configuration clé par fichier


Le KeyPerFileConfigurationProvider utilise les fichiers d’un répertoire en tant que paires
clé-valeur de configuration. La clé est le nom de fichier. La valeur contient le contenu du
fichier. Le fournisseur de configuration Clé par fichier est utilisé dans les scénarios
d’hébergement de Docker.

Pour activer la configuration clé par fichier, appelez la méthode d’extension


AddKeyPerFile sur une instance de ConfigurationBuilder. Le directoryPath aux fichiers
doit être un chemin d’accès absolu.

Les surcharges permettent de spécifier :

Un délégué Action<KeyPerFileConfigurationSource> qui configure la source.


Si le répertoire est facultatif et le chemin d’accès au répertoire.

Le double trait de soulignement ( __ ) est utilisé comme un délimiteur de clé de


configuration dans les noms de fichiers. Par exemple, le nom de fichier
Logging__LogLevel__System génère la clé de configuration Logging:LogLevel:System .

Appelez ConfigureAppConfiguration lors de la création de l’hôte pour spécifier la


configuration de l’application :

C#
.ConfigureAppConfiguration((hostingContext, config) =>
{
var path = Path.Combine(
Directory.GetCurrentDirectory(), "path/to/files");
config.AddKeyPerFile(directoryPath: path, optional: true);
})

Fournisseur de configuration de mémoire


Le MemoryConfigurationProvider utilise une collection en mémoire en tant que paires
clé-valeur de configuration.

Le code suivant ajoute une collection en mémoire au système de configuration :

C#

var builder = WebApplication.CreateBuilder(args);

var Dict = new Dictionary<string, string>


{
{"MyKey", "Dictionary MyKey Value"},
{"Position:Title", "Dictionary_Title"},
{"Position:Name", "Dictionary_Name" },
{"Logging:LogLevel:Default", "Warning"}
};

builder.Configuration.AddInMemoryCollection(Dict);
builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);

builder.Services.AddRazorPages();

var app = builder.Build();

Le code suivant de l’exemple de téléchargement affiche les paramètres de


configuration précédents :

C#

public class TestModel : PageModel


{
// requires using Microsoft.Extensions.Configuration;
private readonly IConfiguration Configuration;

public TestModel(IConfiguration configuration)


{
Configuration = configuration;
}
public ContentResult OnGet()
{
var myKeyValue = Configuration["MyKey"];
var title = Configuration["Position:Title"];
var name = Configuration["Position:Name"];
var defaultLogLevel = Configuration["Logging:LogLevel:Default"];

return Content($"MyKey value: {myKeyValue} \n" +


$"Title: {title} \n" +
$"Name: {name} \n" +
$"Default Log Level: {defaultLogLevel}");
}
}

Dans le code précédent, config.AddInMemoryCollection(Dict) est ajouté après les


fournisseurs de configuration par défaut. Pour obtenir un exemple de classement des
fournisseurs de configuration, consultez Fournisseur de configuration JSON.

Consultez Lier un tableau pour un autre exemple utilisant MemoryConfigurationProvider .

Configuration du point de terminaison Kestrel


Kestrel la configuration d’un point de terminaison spécifique remplace toutes les
configurations de point de terminaison entre serveurs. Les configurations de point de
terminaison entre serveurs comprennent notamment :

UseUrls
--urls sur la ligne de commande

Variable d’environnement ASPNETCORE_URLS

Examinons le fichier appsettings.json utilisé dans une application web ASP.NET Core :

JSON

{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://localhost:9999"
}
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

Quand le balisage en surbrillance précédent est utilisé dans une application web
ASP.NET Core et que l’application est lancée sur la ligne de commande avec la
configuration de point de terminaison entre serveurs suivante :

dotnet run --urls="https://localhost:7777"

Kestrel crée une liaison vers le point de terminaison configuré spécifiquement pour
Kestrel dans le fichier appsettings.json ( https://localhost:9999 ) et non
https://localhost:7777 .

Examinons le point de terminaison spécifique Kestrel configuré comme une variable


d’environnement :

set Kestrel__Endpoints__Https__Url=https://localhost:8888

Dans la variable d’environnement précédente, Https est le nom du point de terminaison


spécifique Kestrel. Le fichier appsettings.json précédent définit également un point de
terminaison spécifique Kestrel nommé Https . Par défaut, les variables d’environnement
utilisant le fournisseur de configuration des variables d’environnement sont lues après
appsettings.{Environment}.json . Par conséquent, la variable d’environnement

précédente est utilisée pour le point de terminaison Https .

GetValue
ConfigurationBinder.GetValue extrait une valeur unique de la configuration avec une clé
spécifiée et la convertit selon le type spécifié :

C#

public class TestNumModel : PageModel


{
private readonly IConfiguration Configuration;

public TestNumModel(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var number = Configuration.GetValue<int>("NumberKey", 99);
return Content($"{number}");
}
}

Dans le code précédent, si NumberKey est introuvable dans la configuration, la valeur par
défaut de 99 est utilisée.

GetSection, GetChildren et Exists


Pour les exemples suivants, utilisez le fichier MySubsection.json suivant :

JSON

{
"section0": {
"key0": "value00",
"key1": "value01"
},
"section1": {
"key0": "value10",
"key1": "value11"
},
"section2": {
"subsection0": {
"key0": "value200",
"key1": "value201"
},
"subsection1": {
"key0": "value210",
"key1": "value211"
}
}
}

Le code suivant ajoute MySubsection.json aux fournisseurs de configuration :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
.AddJsonFile("MySubsection.json",
optional: true,
reloadOnChange: true);

builder.Services.AddRazorPages();
var app = builder.Build();

GetSection
IConfiguration.GetSection retourne une sous-section de la configuration avec la clé de
sous-section spécifiée.

Le code suivant retourne les valeurs pour section1 :

C#

public class TestSectionModel : PageModel


{
private readonly IConfiguration Config;

public TestSectionModel(IConfiguration configuration)


{
Config = configuration.GetSection("section1");
}

public ContentResult OnGet()


{
return Content(
$"section1:key0: '{Config["key0"]}'\n" +
$"section1:key1: '{Config["key1"]}'");
}
}

Le code suivant retourne les valeurs pour section2:subsection0 :

C#

public class TestSection2Model : PageModel


{
private readonly IConfiguration Config;

public TestSection2Model(IConfiguration configuration)


{
Config = configuration.GetSection("section2:subsection0");
}

public ContentResult OnGet()


{
return Content(
$"section2:subsection0:key0 '{Config["key0"]}'\n" +
$"section2:subsection0:key1:'{Config["key1"]}'");
}
}
GetSection ne retourne jamais null . Si aucune section correspondante n’est trouvée,

une valeur IConfigurationSection vide est retournée.

Quand GetSection retourne une section correspondante, Value n’est pas rempli. Key et
Path sont retournés quand la section existe.

GetChildren et Exists
Le code suivant appelle IConfiguration.GetChildren et retourne les valeurs pour
section2:subsection0 :

C#

public class TestSection4Model : PageModel


{
private readonly IConfiguration Config;

public TestSection4Model(IConfiguration configuration)


{
Config = configuration;
}

public ContentResult OnGet()


{
string s = "";
var selection = Config.GetSection("section2");
if (!selection.Exists())
{
throw new Exception("section2 does not exist.");
}
var children = selection.GetChildren();

foreach (var subSection in children)


{
int i = 0;
var key1 = subSection.Key + ":key" + i++.ToString();
var key2 = subSection.Key + ":key" + i.ToString();
s += key1 + " value: " + selection[key1] + "\n";
s += key2 + " value: " + selection[key2] + "\n";
}
return Content(s);
}
}

Le code précédent appelle ConfigurationExtensions.Exists pour vérifier que la section


existe :

Lier un tableau
ConfigurationBinder.Bind prend en charge la liaison de tableaux à des objets à l’aide
d’index de tableau dans les clés de configuration. Tout format de tableau qui expose un
segment de clé numérique est capable d’effectuer la liaison de tableau avec un tableau
de classes POCO .

Examinons MyArray.json dans l’exemple de téléchargement :

JSON

{
"array": {
"entries": {
"0": "value00",
"1": "value10",
"2": "value20",
"4": "value40",
"5": "value50"
}
}
}

Le code suivant ajoute MyArray.json aux fournisseurs de configuration :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Configuration
.AddJsonFile("MyArray.json",
optional: true,
reloadOnChange: true);

builder.Services.AddRazorPages();

var app = builder.Build();

Le code suivant lit la configuration et affiche les valeurs :

C#

public class ArrayModel : PageModel


{
private readonly IConfiguration Config;
public ArrayExample? _array { get; private set; }

public ArrayModel(IConfiguration config)


{
Config = config;
}
public ContentResult OnGet()
{
_array = Config.GetSection("array").Get<ArrayExample>();
if (_array == null)
{
throw new ArgumentNullException(nameof(_array));
}
string s = String.Empty;

for (int j = 0; j < _array.Entries.Length; j++)


{
s += $"Index: {j} Value: {_array.Entries[j]} \n";
}

return Content(s);
}
}

C#

public class ArrayExample


{
public string[]? Entries { get; set; }
}

Le code précédent retourne la sortie suivante :

text

Index: 0 Value: value00


Index: 1 Value: value10
Index: 2 Value: value20
Index: 3 Value: value40
Index: 4 Value: value50

Dans la sortie précédente, l’index 3 a la valeur value40 , ce qui correspond à "4":


"value40", dans MyArray.json . Les index de tableau lié sont contenus et ne sont pas liés

à l’index de clé de configuration. Le binder de configuration n’est pas en mesure de lier


des valeurs null, ni de créer des entrées null dans des objets liés.

Fournisseur de configuration personnalisé


L’exemple d’application montre comment créer un fournisseur de configuration de base
qui lit les paires clé-valeur de configuration à partir d’une base de données à l’aide
d’Entity Framework (EF).
Le fournisseur présente les caractéristiques suivantes :

La base de données en mémoire EF est utilisée à des fins de démonstration. Pour


utiliser une base de données qui nécessite une chaîne de connexion, implémentez
un autre ConfigurationBuilder pour fournir la chaîne de connexion à partir d’un
autre fournisseur de configuration.
Le fournisseur lit une table de base de données dans la configuration au
démarrage. Le fournisseur n’interroge pas la base de données par clé.
Le rechargement en cas de changement n’est pas implémenté, par conséquent, la
mise à jour la base de données après le démarrage de l’application n’a aucun effet
sur la configuration de l’application.

Définissez une entité EFConfigurationValue pour le stockage des valeurs de


configuration dans la base de données.

Models/EFConfigurationValue.cs :

C#

public class EFConfigurationValue


{
public string Id { get; set; } = String.Empty;
public string Value { get; set; } = String.Empty;
}

Ajoutez un EFConfigurationContext pour stocker les valeurs configurées et y accéder.

EFConfigurationProvider/EFConfigurationContext.cs :

C#

public class EFConfigurationContext : DbContext


{
public EFConfigurationContext(DbContextOptions<EFConfigurationContext>
options) : base(options)
{
}

public DbSet<EFConfigurationValue> Values => Set<EFConfigurationValue>


();
}

Créez une classe qui implémente IConfigurationSource.

EFConfigurationProvider/EFConfigurationSource.cs :
C#

public class EFConfigurationSource : IConfigurationSource


{
private readonly Action<DbContextOptionsBuilder> _optionsAction;

public EFConfigurationSource(Action<DbContextOptionsBuilder>
optionsAction) => _optionsAction = optionsAction;

public IConfigurationProvider Build(IConfigurationBuilder builder) =>


new EFConfigurationProvider(_optionsAction);
}

Créez le fournisseur de configuration personnalisé en héritant de ConfigurationProvider.


Le fournisseur de configuration initialise la base de données quand elle est vide. Étant
donné que les clés de configuration ne respectent pas la casse, le dictionnaire utilisé
pour initialiser la base de données est créé avec le comparateur ne respectant pas la
casse (StringComparer.OrdinalIgnoreCase).

EFConfigurationProvider/EFConfigurationProvider.cs :

C#

public class EFConfigurationProvider : ConfigurationProvider


{
public EFConfigurationProvider(Action<DbContextOptionsBuilder>
optionsAction)
{
OptionsAction = optionsAction;
}

Action<DbContextOptionsBuilder> OptionsAction { get; }

public override void Load()


{
var builder = new DbContextOptionsBuilder<EFConfigurationContext>();

OptionsAction(builder);

using (var dbContext = new EFConfigurationContext(builder.Options))


{
if (dbContext == null || dbContext.Values == null)
{
throw new Exception("Null DB context");
}
dbContext.Database.EnsureCreated();

Data = !dbContext.Values.Any()
? CreateAndSaveDefaultValues(dbContext)
: dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
}
}

private static IDictionary<string, string> CreateAndSaveDefaultValues(


EFConfigurationContext dbContext)
{
// Quotes (c)2005 Universal Pictures: Serenity
// https://www.uphe.com/movies/serenity-2005
var configValues =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "quote1", "I aim to misbehave." },
{ "quote2", "I swallowed a bug." },
{ "quote3", "You can't stop the signal, Mal." }
};

if (dbContext == null || dbContext.Values == null)


{
throw new Exception("Null DB context");
}

dbContext.Values.AddRange(configValues
.Select(kvp => new EFConfigurationValue
{
Id = kvp.Key,
Value = kvp.Value
})
.ToArray());

dbContext.SaveChanges();

return configValues;
}
}

Une méthode d’extension AddEFConfiguration permet d’ajouter la source de


configuration à un ConfigurationBuilder .

Extensions/EntityFrameworkExtensions.cs :

C#

public static class EntityFrameworkExtensions


{
public static IConfigurationBuilder AddEFConfiguration(
this IConfigurationBuilder builder,
Action<DbContextOptionsBuilder> optionsAction)
{
return builder.Add(new EFConfigurationSource(optionsAction));
}
}
Le code suivant montre comment utiliser le EFConfigurationProvider personnalisé dans
Program.cs :

C#

//using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddEFConfiguration(
opt => opt.UseInMemoryDatabase("InMemoryDb"));

var app = builder.Build();

app.Run();

Configuration de l’accès avec injection de


dépendances
La configuration peut être injectée dans des services à l’aide de l’injection de
dépendances en résolvant le service IConfiguration :

C#

public class Service


{
private readonly IConfiguration _config;

public Service(IConfiguration config) =>


_config = config;

public void DoSomething()


{
var configSettingValue = _config["ConfigSetting"];

// ...
}
}

Pour plus d’informations sur l’accès aux valeurs à l’aide de IConfiguration , consultez
GetValue et GetSection, GetChildren et Exists dans cet article.

Configuration de l’accès dans les pages Razor


Le code suivant affiche les données de configuration dans une page Razor :
CSHTML

@page
@model Test5Model
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

Dans le code suivant, MyOptions est ajouté au conteneur de service avec Configure et lié
à la configuration :

C#

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

Le balisage suivant utilise la directive @injectRazor pour résoudre et afficher les valeurs
des options :

CSHTML

@page
@model SampleApp.Pages.Test3Model
@using Microsoft.Extensions.Options
@using SampleApp.Models
@inject IOptions<MyOptions> optionsAccessor

<p><b>Option1:</b> @optionsAccessor.Value.Option1</p>
<p><b>Option2:</b> @optionsAccessor.Value.Option2</p>

Accéder à la configuration dans un fichier de


vue MVC
Le code suivant affiche les données de configuration dans une vue MVC :
CSHTML

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Configuration value for 'MyKey': @Configuration["MyKey"]

Configuration de l’accès dans Program.cs


Le code suivant accède à la configuration dans le fichier Program.cs .

C#

var builder = WebApplication.CreateBuilder(args);

var key1 = builder.Configuration.GetValue<string>("KeyOne");

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

var key2 = app.Configuration.GetValue<int>("KeyTwo");


var key3 = app.Configuration.GetValue<bool>("KeyThree");

app.Logger.LogInformation("KeyOne: {KeyOne}", key1);


app.Logger.LogInformation("KeyTwo: {KeyTwo}", key2);
app.Logger.LogInformation("KeyThree: {KeyThree}", key3);

app.Run();

Dans appsettings.json pour l’exemple précédent :

JSON

{
...
"KeyOne": "Key One Value",
"KeyTwo": 1999,
"KeyThree": true
}

Configurer des options avec un délégué


Les options configurées dans un délégué remplacent les valeurs définies dans les
fournisseurs de configuration.
Dans le code suivant, un service IConfigureOptions<TOptions> est ajouté au conteneur
de services. Il utilise un délégué pour configurer les valeurs pour MyOptions :

C#

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(myOptions =>
{
myOptions.Option1 = "Value configured in delegate";
myOptions.Option2 = 500;
});

var app = builder.Build();

Le code suivant affiche les valeurs d’options :

C#

public class Test2Model : PageModel


{
private readonly IOptions<MyOptions> _optionsDelegate;

public Test2Model(IOptions<MyOptions> optionsDelegate )


{
_optionsDelegate = optionsDelegate;
}

public ContentResult OnGet()


{
return Content($"Option1: {_optionsDelegate.Value.Option1} \n" +
$"Option2: {_optionsDelegate.Value.Option2}");
}
}

Dans l’exemple précédent, les valeurs de Option1 et Option2 sont spécifiées dans
appsettings.json , puis remplacées par le délégué configuré.

Configuration de l’hôte ou configuration de


l’application
Avant que l’application ne soit configurée et démarrée, un hôte est configuré et lancé.
L’hôte est responsable de la gestion du démarrage et de la durée de vie des
applications. L’application et l’hôte sont configurés à l’aide des fournisseurs de
configuration décrits dans cette rubrique. Les paires clé-valeur de la configuration de
l’hôte sont également incluses dans la configuration de l’application. Pour plus
d’informations sur l’utilisation des fournisseurs de configuration lors de la génération de
l’hôte et sur l’impact des sources de configuration sur la configuration de l’hôte,
consultez Vue d’ensemble des notions de base d’ASP.NET Core.

Configuration de l’hôte par défaut


Pour plus de détails sur la configuration par défaut lors de l’utilisation de l’hôte Web,
consultez la version ASP.NET Core 2.2. de cette rubrique.

La configuration de l’hôte est fournie à partir des éléments suivants :


Variables d’environnement avec le préfixe DOTNET_ (par exemple
DOTNET_ENVIRONMENT ) avec le fournisseur de configuration des variables

d’environnement. Le préfixe ( DOTNET_ ) est supprimé lorsque les paires clé-valeur


de la configuration sont chargées.
Arguments de ligne de commande avec le fournisseur de configuration de ligne
de commande.
La configuration par défaut de l’hôte Web est établie ( ConfigureWebHostDefaults ) :
Kestrel est utilisé comme serveur web et configuré à l’aide des fournisseurs de
configuration de l’application.
Ajoutez l’intergiciel de filtrage d’hôtes.
Ajoutez l’intergiciel d’en-têtes transférés si la variable d'environnement
ASPNETCORE_FORWARDEDHEADERS_ENABLED est définie sur true .

Activez l’intégration d’IIS.

Autre configuration
Cette rubrique se rapporte uniquement à la configuration des applications. D’autres
aspects concernant l’exécution et l’hébergement des applications ASP.NET Core sont
configurés à l’aide des fichiers de configuration qui ne sont pas abordés dans cette
rubrique :

launch.json / launchSettings.json sont des fichiers de configuration d’outils pour

l’environnement de développement, décrits ci-après :


Dans Utiliser plusieurs environnements dans ASP.NET Core.
Dans l’ensemble de la documentation où les fichiers sont utilisés pour
configurer des applications ASP.NET Core dans des scénarios de
développement.
web.config est un fichier de configuration de serveur décrit dans les rubriques

suivantes :
Héberger ASP.NET Core sur Windows avec IIS
Module ASP.NET Core (ANCM) pour IIS

Les variables d’environnement définies dans launchSettings.json remplacent celles qui


sont définies dans l’environnement système.

Pour plus d’informations sur la migration de la configuration d’application à partir de


versions antérieures d’ASP.NET, consultez Effectuer une mise à jour d’ASP.NET vers
ASP.NET Core.

Ajouter la configuration à partir d’un assembly


externe
Une implémentation de IHostingStartup permet d’ajouter des améliorations à une
application au démarrage à partir d’un assembly externe, en dehors de la classe Startup
de l’application. Pour plus d’informations, consultez Utiliser des assemblys
d’hébergement au démarrage dans ASP.NET Core.

Générateur de source de liaison de


configuration
Le générateur de source de liaison de configuration fournit une configuration AOT et
conviviale. Pour plus d’informations, consultez le générateur de source de liaison de
configuration.

Ressources supplémentaires
Code source de configuration
Code source de WebApplicationBuilder
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Modèle d’options dans ASP.NET Core
C ASP.NET Core Blazor

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub
La source de ce contenu se ASP.NET Core is an open source
trouve sur GitHub, où vous project. Select a link to provide
pouvez également créer et feedback:
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Modèle d’options dans ASP.NET Core
Article • 30/11/2023

Par Rick Anderson .

Le modèle d’options utilise des classes pour fournir un accès fortement typé aux
groupes de paramètres associés. Quand les paramètres de configuration sont isolés par
scénario dans des classes distinctes, l’application est conforme à deux principes
d’ingénierie logicielle importants :

Encapsulation :
Les classes qui dépendent de paramètres de configuration dépendent
uniquement de ceux qu’elles utilisent.
Séparation des responsabilités :
Les paramètres des différentes parties de l’application ne sont ni dépendants ni
associés les uns aux autres.

Ces options fournissent également un mécanisme de validation des données de


configuration. Pour plus d'informations, reportez-vous à la section Validation des
options.

Cet article fournit des informations sur le modèle d’options dans ASP.NET Core. Pour
plus d’informations sur l’utilisation du modèle d’options dans les applications console,
consultez Modèle d’options dans .NET.

Lier une configuration hiérarchique


La meilleure méthode pour lire les valeurs de configuration associées consiste à utiliser
le modèle d’options. Par exemple, pour lire les valeurs de configuration suivantes :

JSON

"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}

Créez la classe PositionOptions suivante :

C#

public class PositionOptions


{
public const string Position = "Position";

public string Title { get; set; } = String.Empty;


public string Name { get; set; } = String.Empty;
}

Classe d’options :

Doit être non abstrait.


Possède des propriétés publiques en lecture-écriture du type qui ont des éléments
correspondants dans la configuration sont liées.
Possède ses propriétés en lecture-écriture liées aux entrées correspondantes dans
la configuration.
N’a pas de champs liés. Dans le code précédent, Position n’est pas lié. Le champ
Position est utilisé pour qu’il ne soit pas nécessaire de coder la chaîne "Position"

en dur dans l’application lors de la liaison de la classe à un fournisseur de


configuration.

Le code suivant :

Appelle ConfigurationBinder.Bind pour lier la classe PositionOptions à la section


Position .

Affiche les données de configuration Position .

C#

public class Test22Model : PageModel


{
private readonly IConfiguration Configuration;

public Test22Model(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var positionOptions = new PositionOptions();

Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

return Content($"Title: {positionOptions.Title} \n" +


$"Name: {positionOptions.Name}");
}
}
Dans le code précédent, par défaut, les modifications apportées au fichier de
configuration JSON après le démarrage de l’application sont lues.

ConfigurationBinder.Get<T> lie et retourne le type spécifié. Il peut être plus pratique


d’utiliser ConfigurationBinder.Get<T> que ConfigurationBinder.Bind . Le code suivant
illustre la classe ConfigurationBinder.Get<T> avec la classe PositionOptions :

C#

public class Test21Model : PageModel


{
private readonly IConfiguration Configuration;
public PositionOptions? positionOptions { get; private set; }

public Test21Model(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
positionOptions = Configuration.GetSection(PositionOptions.Position)
.Get<PositionOptions>
();

return Content($"Title: {positionOptions.Title} \n" +


$"Name: {positionOptions.Name}");
}
}

Dans le code précédent, par défaut, les modifications apportées au fichier de


configuration JSON après le démarrage de l’application sont lues.

Bind permet également la création d’une classe abstraite. Prenez le code suivant, qui
utilise la classe abstraite SomethingWithAName :

C#

namespace ConfigSample.Options;

public abstract class SomethingWithAName


{
public abstract string? Name { get; set; }
}

public class NameTitleOptions(int age) : SomethingWithAName


{
public const string NameTitle = "NameTitle";

public override string? Name { get; set; }


public string Title { get; set; } = string.Empty;

public int Age { get; set; } = age;


}

Le code suivant affiche les valeurs de configuration NameTitleOptions :

C#

public class Test33Model : PageModel


{
private readonly IConfiguration Configuration;

public Test33Model(IConfiguration configuration)


{
Configuration = configuration;
}

public ContentResult OnGet()


{
var nameTitleOptions = new NameTitleOptions(22);

Configuration.GetSection(NameTitleOptions.NameTitle).Bind(nameTitleOptions);

return Content($"Title: {nameTitleOptions.Title} \n" +


$"Name: {nameTitleOptions.Name} \n" +
$"Age: {nameTitleOptions.Age}"
);
}
}

Les appels à Bind sont moins stricts que les appels à Get<> :

Bind permet la concrétisation d’une abstraction.

Get<> doit créer une instance elle-même.

Le modèle d’options
Une autre approche lors de l’utilisation du modèle d’options consiste à lier la section
Position et à l’ajouter au conteneur de service d’injection de dépendances. Dans le

code suivant, PositionOptions est ajouté au conteneur de service avec Configure et lié à
la configuration :

C#

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

À l’aide du code précédent, le code suivant lit les options de position :

C#

public class Test2Model : PageModel


{
private readonly PositionOptions _options;

public Test2Model(IOptions<PositionOptions> options)


{
_options = options.Value;
}

public ContentResult OnGet()


{
return Content($"Title: {_options.Title} \n" +
$"Name: {_options.Name}");
}
}

Dans le code précédent, les modifications apportées au fichier de configuration JSON


après le démarrage de l’application ne sont pas lues. Pour lire les modifications après
que l’application a démarré, utilisez IOptionsSnapshot.

Interfaces d’options
IOptions<TOptions>:

Ne prend pas en charge :


La lecture des données de configuration après le démarrage de l’application.
Options nommées
Est inscrit en tant que Singleton et peut être injecté dans n’importe quelle durée
de vie d’un service.

IOptionsSnapshot<TOptions>:

Est utile dans les scénarios où des options doivent être recalculées à chaque
requête. Pour plus d’informations, consultez utiliser IOptionsSnapshot pour lire des
données mises à jour.
Est inscrit en tant que Délimité et ne peut donc pas être injecté dans un service
Singleton.
Prend en charge les options nommées

IOptionsMonitor<TOptions>:

Permet de récupérer des options et de gérer les notifications d’options pour les
instances TOptions .
Est inscrit en tant que Singleton et peut être injecté dans n’importe quelle durée
de vie d’un service.
Prend en charge :
Notifications de modifications
options nommées
Configuration rechargeable
Invalidation sélective des options (IOptionsMonitorCache<TOptions>)

Les scénarios de post-configuration permettent de paramétrer ou de modifier les


options après chaque configuration de IConfigureOptions<TOptions>.

IOptionsFactory<TOptions> est chargée de créer les instances d’options. Elle dispose


d’une seule méthode Create. L’implémentation par défaut prend toutes les
IConfigureOptions<TOptions> et IPostConfigureOptions<TOptions> inscrites et exécute
toutes les configurations, puis les post-configurations. Elle fait la distinction entre
IConfigureNamedOptions<TOptions> et IConfigureOptions<TOptions> et n’appelle que
l’interface appropriée.

IOptionsMonitorCache<TOptions> est utilisée par IOptionsMonitor<TOptions> pour


mettre en cache les instances TOptions . IOptionsMonitorCache<TOptions> invalide les
instances des options dans le moniteur afin que la valeur soit recalculée (TryRemove).
Les valeurs peuvent aussi être introduites manuellement avec TryAdd. La méthode Clear
est utilisée quand toutes les instances nommées doivent être recréées à la demande.

Utiliser IOptionsSnapshot pour lire des


données mises à jour
Utilisation de IOptionsSnapshot<TOptions>:

Les options sont calculées une fois par requête quand le système y accède et sont
mises en cache pour toute la durée de vie de la requête.
Peut entraîner une pénalité de performances importante, car il s’agit d’un service
délimité et est recalculé par requête. Pour plus d’informations, consultez ce
problème GitHub et Améliorer les performances de la liaison de configuration .
Les modifications apportées à la configuration sont lues après le démarrage de
l’application lors de l’utilisation de fournisseurs de configuration qui prennent en
charge la lecture des valeurs de configuration mises à jour.

La différence entre IOptionsMonitor et IOptionsSnapshot est que :

IOptionsMonitor est un service Singleton qui récupère les valeurs d’option

actuelles à tout instant, ce qui est particulièrement utile dans les dépendances
Singleton.
IOptionsSnapshot est un service délimité et fournit un instantané des options au

moment où l’objet IOptionsSnapshot<T> est construit. Les instantanés d’options


sont conçus pour être utilisés avec des dépendances temporaires et étendues.

Le code suivant utilise IOptionsSnapshot<TOptions>.

C#

public class TestSnapModel : PageModel


{
private readonly MyOptions _snapshotOptions;

public TestSnapModel(IOptionsSnapshot<MyOptions>
snapshotOptionsAccessor)
{
_snapshotOptions = snapshotOptionsAccessor.Value;
}

public ContentResult OnGet()


{
return Content($"Option1: {_snapshotOptions.Option1} \n" +
$"Option2: {_snapshotOptions.Option2}");
}
}

Le code suivant inscrit une instance de configuration qui MyOptions se lie à :

C#

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();

Dans le code précédent, les modifications apportées au fichier de configuration JSON


après le démarrage de l’application sont lues.

IOptionsMonitor
Le code suivant inscrit une instance de configuration à laquelle MyOptions se lie.

C#

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

L'exemple suivant utilise IOptionsMonitor<TOptions> :

C#

public class TestMonitorModel : PageModel


{
private readonly IOptionsMonitor<MyOptions> _optionsDelegate;

public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )


{
_optionsDelegate = optionsDelegate;
}

public ContentResult OnGet()


{
return Content($"Option1: {_optionsDelegate.CurrentValue.Option1}
\n" +
$"Option2: {_optionsDelegate.CurrentValue.Option2}");
}
}

Dans le code précédent, par défaut, les modifications apportées au fichier de


configuration JSON après le démarrage de l’application sont lues.
Prise en charge des options nommées à l’aide
de IConfigureNamedOptions
Options nommées :

Sont utiles lorsque plusieurs sections de configuration se lient aux mêmes


propriétés.
Respectent la casse.

Examinons le fichier appsettings.json suivant :

JSON

{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}

Au lieu de créer deux classes à lier TopItem:Month et TopItem:Year , la classe suivante est
utilisée pour chaque section :

C#

public class TopItemSettings


{
public const string Month = "Month";
public const string Year = "Year";

public string Name { get; set; } = string.Empty;


public string Model { get; set; } = string.Empty;
}

Le code ci-dessous configure les options nommées :

C#

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddRazorPages();

builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));

var app = builder.Build();

Le code suivant affiche les options nommées :

C#

public class TestNOModel : PageModel


{
private readonly TopItemSettings _monthTopItem;
private readonly TopItemSettings _yearTopItem;

public TestNOModel(IOptionsSnapshot<TopItemSettings>
namedOptionsAccessor)
{
_monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
_yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
}

public ContentResult OnGet()


{
return Content($"Month:Name {_monthTopItem.Name} \n" +
$"Month:Model {_monthTopItem.Model} \n\n" +
$"Year:Name {_yearTopItem.Name} \n" +
$"Year:Model {_yearTopItem.Model} \n" );
}
}

Toutes les options sont des instances nommées. Les instances


IConfigureOptions<TOptions> sont traitées comme ciblant l’instance
Options.DefaultName , qui est string.Empty . En outre,

IConfigureNamedOptions<TOptions> implémente IConfigureOptions<TOptions>.


L’implémentation par défaut de IOptionsFactory<TOptions> possède une logique qui
utilise chaque élément de manière appropriée. L’option nommée null est utilisée pour
cibler toutes les instances nommées au lieu d’une instance nommée spécifique.
ConfigureAll et PostConfigureAll utilisent cette convention.

API OptionsBuilder
OptionsBuilder<TOptions> permet de configurer des instances TOptions .
OptionsBuilder simplifie la création d’options nommées. En effet, il est le seul

paramètre de l’appel AddOptions<TOptions>(string optionsName) initial et n’apparaît pas


dans les appels ultérieurs. La validation des options et les surcharges ConfigureOptions
qui acceptent des dépendances de service sont uniquement disponibles avec
OptionsBuilder .

OptionsBuilder est utilisé dans la section Validation des options.

Pour plus d’informations sur l’ajout d’un dépôt personnalisé, consultez Utiliser
AddOptions pour configurer un dépôt personnalisé.

Utiliser les services d’injection de dépendances


(DI) pour configurer des options
Les service sont accessibles à partir de l’injection de dépendances pendant la
configuration des options de deux manières différentes :

Passez un délégué de configuration à Configure sur OptionsBuilder<TOptions>.


OptionsBuilder<TOptions> fournit des surcharges de Configure qui permettent

d’utiliser jusqu’à cinq services pour configurer des options :

C#

builder.Services.AddOptions<MyOptions>("optionalName")
.Configure<Service1, Service2, Service3, Service4, Service5>(
(o, s, s2, s3, s4, s5) =>
o.Property = DoSomethingWith(s, s2, s3, s4, s5));

Créer un type qui implémente IConfigureOptions<TOptions> ou


IConfigureNamedOptions<TOptions> et inscrit le type en tant que service.

Nous vous recommandons de transmettre un délégué de configuration à Configure, car


il est plus complexe de créer un service. La création d’un type équivaut à ce que fait
l’infrastructure lors de l’appel de Configure. L’appel de Configure a pour effet d’inscrire
une instance générique temporaire de IConfigureNamedOptions<TOptions>, dont l’un
des constructeurs accepte les types de service génériques spécifiés.

Validation des options


La validation des options permet de valider les valeurs d’option.
Examinons le fichier appsettings.json suivant :

JSON

{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}

La classe suivante est utilisé pour lier à la section de configuration "MyConfig" et


applique quelques règles DataAnnotations :

C#

public class MyConfigOptions


{
public const string MyConfig = "MyConfig";

[RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
public string Key1 { get; set; }
[Range(0, 1000,
ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public int Key2 { get; set; }
public int Key3 { get; set; }
}

Le code suivant :

Appelle AddOptions pour obtenir un OptionsBuilder<TOptions> qui lie à la classe


MyConfigOptions .

Appelle ValidateDataAnnotations pour activer la validation à l’aide de


DataAnnotations .

C#

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()

.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations();
var app = builder.Build();

La méthode d'extension ValidateDataAnnotations est définie dans le package NuGet


Microsoft.Extensions.Options.DataAnnotations . Pour les applications web qui utilisent
le kit de développement logiciel (SDK) Microsoft.NET.Sdk.Web , ce package est référencé
implicitement à partir de l’infrastructure partagée.

Le code suivant affiche les valeurs de configuration ou les erreurs de validation :

C#

public class HomeController : Controller


{
private readonly ILogger<HomeController> _logger;
private readonly IOptions<MyConfigOptions> _config;

public HomeController(IOptions<MyConfigOptions> config,


ILogger<HomeController> logger)
{
_config = config;
_logger = logger;

try
{
var configValue = _config.Value;

}
catch (OptionsValidationException ex)
{
foreach (var failure in ex.Failures)
{
_logger.LogError(failure);
}
}
}

public ContentResult Index()


{
string msg;
try
{
msg = $"Key1: {_config.Value.Key1} \n" +
$"Key2: {_config.Value.Key2} \n" +
$"Key3: {_config.Value.Key3}";
}
catch (OptionsValidationException optValEx)
{
return Content(optValEx.Message);
}
return Content(msg);
}

Le code suivant applique une règle de validation plus complexe à l’aide d’un délégué :

C#

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()

.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.Validate(config =>
{
if (config.Key2 != 0)
{
return config.Key3 > config.Key2;
}

return true;
}, "Key3 must be > than Key2."); // Failure message.

var app = builder.Build();

IValidateOptions<TOptions> et IValidatableObject

La classe suivante implémente IValidateOptions<TOptions> :

C#

public class MyConfigValidation : IValidateOptions<MyConfigOptions>


{
public MyConfigOptions _config { get; private set; }

public MyConfigValidation(IConfiguration config)


{
_config = config.GetSection(MyConfigOptions.MyConfig)
.Get<MyConfigOptions>();
}

public ValidateOptionsResult Validate(string name, MyConfigOptions


options)
{
string? vor = null;
var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
var match = rx.Match(options.Key1!);

if (string.IsNullOrEmpty(match.Value))
{
vor = $"{options.Key1} doesn't match RegEx \n";
}

if ( options.Key2 < 0 || options.Key2 > 1000)


{
vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
}

if (_config.Key2 != default)
{
if(_config.Key3 <= _config.Key2)
{
vor += "Key3 must be > than Key2.";
}
}

if (vor != null)
{
return ValidateOptionsResult.Fail(vor);
}

return ValidateOptionsResult.Success;
}
}

IValidateOptions permet de déplacer le code de validation hors de Program.cs et dans

une classe.

À l’aide du code précédent, la validation est activée dans Program.cs avec le code
suivant :

C#

using Microsoft.Extensions.Options;
using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.Configure<MyConfigOptions>
(builder.Configuration.GetSection(
MyConfigOptions.MyConfig));

builder.Services.AddSingleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>();
var app = builder.Build();

La validation des options prend également en charge IValidatableObject. Pour effectuer


une validation au niveau de la classe d’une classe dans la classe elle-même :

Implémentez l’interface IValidatableObject et sa méthode Validate dans la classe.


Appelez ValidateDataAnnotations dans Program.cs .

ValidateOnStart

La validation des options s’exécute la première fois qu’une implémentation


IOptions<TOptions>, IOptionsSnapshot<TOptions> ou IOptionsMonitor<TOptions> est
créée. Pour exécuter la validation des options rapidement, appelez ValidateOnStart dans
Program.cs lorsque l’application démarre :

C#

builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();

Options de post-configuration
Définissez la post-configuration avec IPostConfigureOptions<TOptions>. La post-
configuration s’exécute après chaque configuration de IConfigureOptions<TOptions> :

C#

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()

.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
PostConfigure permet de post-configurer les options nommées :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));

builder.Services.PostConfigure<TopItemSettings>("Month", myOptions =>


{
myOptions.Name = "post_configured_name_value";
myOptions.Model = "post_configured_model_value";
});

var app = builder.Build();

Utilisez PostConfigureAll pour post-configurer toutes les instances de configuration :

C#

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()

.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});

Options d'accès dans Program.cs


Pour accéder à IOptions<TOptions> ou à IOptionsMonitor<TOptions> dans Program.cs ,
appelez GetRequiredService sur WebApplication.Services :

C#

var app = builder.Build();


var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;

Ressources supplémentaires
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Utiliser plusieurs environnements dans
ASP.NET Core
Article • 30/11/2023

Par Rick Anderson et Kirk Larkin

ASP.NET Core configure le comportement de l’application en fonction de


l’environnement d’exécution à l’aide d’une variable d’environnement.

Environnements
Pour déterminer l’environnement d’exécution, ASP.NET Core lit à partir des variables
d’environnement suivantes :

1. DOTNET_ENVIRONMENT
2. ASPNETCORE_ENVIRONMENT lorsque la méthode WebApplication.CreateBuilder est
appelée. Les modèles d’application web par défaut ASP.NET Core appellent
WebApplication.CreateBuilder . La valeur DOTNET_ENVIRONMENT remplace
ASPNETCORE_ENVIRONMENT quand WebApplicationBuilder est utilisé. Pour les autres

hôtes, tels que ConfigureWebHostDefaults et WebHost.CreateDefaultBuilder ,


ASPNETCORE_ENVIRONMENT est prioritaire.

IHostEnvironment.EnvironmentName peut être défini sur n’importe quelle valeur, mais les

valeurs suivantes sont fournies par l’infrastructure :

Development: Le fichier launchSettings.json définit ASPNETCORE_ENVIRONMENT sur


Development sur l’ordinateur local.

Staging
Production: La valeur par défaut si DOTNET_ENVIRONMENT et ASPNETCORE_ENVIRONMENT
n’ont pas été définis.

Le code suivant :

Est similaire au code généré par les modèles ASP.NET Core.


Active la page d’exception du développeur lorsque ASPNETCORE_ENVIRONMENT est
défini sur Development . Cette opération est effectuée automatiquement par la
méthode WebApplication.CreateBuilder.
Appelle UseExceptionHandler lorsque la valeur de ASPNETCORE_ENVIRONMENT est
autre que Development .
Fournit une instance IWebHostEnvironment dans la propriété Environment de
WebApplication .

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Le Tag helper d’environnement utilise la valeur de la propriété


IHostEnvironment.EnvironmentName pour inclure ou exclure le balisage dans l’élément :

CSHTML

<environment include="Development">
<div>Environment is Development</div>
</environment>
<environment exclude="Development">
<div>Environment is NOT Development</div>
</environment>
<environment include="Staging,Development,Staging_2">
<div>Environment is: Staging, Development or Staging_2</div>
</environment>

La page À propos de l’échantillon de code inclut le balisage précédent et affiche la


valeur de IWebHostEnvironment.EnvironmentName .
Sur Windows et macOS, les valeurs et les variables d’environnement ne respectent pas la
casse. Les valeurs et les variables d’environnement Linux respectent la casse par défaut.

Créer des échantillons d’environnements


L’échantillon de code utilisé dans cet article est basé sur un projet Pages Razor
nommé EnvironmentsSample.

Les commandes CLI .NET suivantes créent et exécutent une application web nommée
EnvironmentsSample :

Bash

dotnet new webapp -o EnvironmentsSample


cd EnvironmentsSample
dotnet run --verbosity normal

Lorsque vous exécutez l’application, elle affiche une sortie similaire à ce qui suit :

Bash

info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7152
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5105
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Path\To\EnvironmentsSample

Définir l’environnement sur la ligne de commande


Utilisez l’indicateur --environment pour définir l’environnement. Par exemple :

CLI .NET

dotnet run --environment Production

La commande précédente définit l’environnement sur Production et affiche une sortie


similaire à ce qui suit dans la fenêtre de commande :

Bash
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7262
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5005
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Path\To\EnvironmentsSample

Développement et launchSettings.json
L’environnement de développement peut activer des fonctionnalités qui ne doivent pas
être exposées en production. Par exemple, les modèles du projet ASP.NET Core activent
la page d’exceptions du développeur dans l’environnement de développement. En
raison du coût des performances, la validation de l’étendue et la validation des
dépendances se produisent uniquement dans le développement.

L’environnement de développement de l’ordinateur local peut être défini dans le fichier


Properties\launchSettings.json du projet. Les valeurs d’environnement définies dans
launchSettings.json remplacent les valeurs définies dans l’environnement système.

Le fichier launchSettings.json :

Est utilisé uniquement sur l’ordinateur de développement local.


N’est pas déployé.
Contient les paramètres de profil.

Le fichier JSON suivant montre le fichier launchSettings.json d’un projet web ASP.NET
Core nommé EnvironmentsSample créé avec Visual Studio ou dotnet new :

JSON

{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:59481",
"sslPort": 44308
}
},
"profiles": {
"EnvironmentsSample": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7152;http://localhost:5105",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

Le JSON précédent contient deux profils :

EnvironmentsSample : Le nom du profil est le nom du projet. En tant que premier

profil répertorié, ce profil est utilisé par défaut. La clé "commandName" a la valeur
"Project" , par conséquent, le serveur web Kestrel est lancé.

IIS Express : La clé "commandName" a la valeur "IISExpress" , par conséquent

IISExpress est le serveur web.

Vous pouvez définir le profil de lancement sur le projet ou tout autre profil inclus dans
launchSettings.json . Par exemple, dans l’image ci-dessous, la sélection du nom du

projet lance le serveur webKestrel.


La valeur de commandName peut spécifier le serveur web à lancer. commandName peut avoir
l’une des valeurs suivantes :

IISExpress : lance IIS Express.


IIS : Aucun serveur web n’a été lancé. IIS est censé être disponible.

Project : lance Kestrel.

L’onglet Déboguer/Général des propriétés du projet Visual Studio 2022 fournit un lien
d’interface utilisateur Ouvrir les profils de lancement de débogage. Ce lien ouvre une
boîte de dialogue Lancer les profils qui vous permet de modifier les paramètres de
variable d’environnement dans le fichier launchSettings.json . Vous pouvez également
ouvrir la boîte de dialogue Lancer les profils dans le menu Déboguer en sélectionnant
le <nom du projet> Propriétés du débogage. Les modifications apportées aux profils
de projet peuvent ne prendre effet qu’une fois le serveur web redémarré. Vous devez
redémarrer Kestrel pour qu’il puisse détecter les modifications apportées à son
environnement.

Le fichier suivant launchSettings.json contient plusieurs profils :

JSON

{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:59481",
"sslPort": 44308
}
},
"profiles": {
"EnvironmentsSample": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7152;http://localhost:5105",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"EnvironmentsSample-Staging": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7152;http://localhost:5105",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Staging",
"ASPNETCORE_DETAILEDERRORS": "1",
"ASPNETCORE_SHUTDOWNTIMEOUTSECONDS": "3"
}
},
"EnvironmentsSample-Production": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7152;http://localhost:5105",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

Les profils peuvent être sélectionnés :

À partir de l’interface utilisateur de Visual Studio.

Utilisation de la commande dotnet run CLI avec l’option --launch-profile définie


sur le nom du profil. Cette approche prend uniquement en charge les profils Kestrel.

CLI .NET
dotnet run --launch-profile "EnvironmentsSample"

2 Avertissement

launchSettings.json ne doit pas stocker les secrets. Vous pouvez utiliser l’outil

Secret Manager afin de stocker des secrets pour le développement local.

Quand vous utilisez Visual Studio Code , les variables d’environnement peuvent être
définies dans le fichier .vscode/launch.json . L’exemple suivant définit plusieurs
variables d’environnement pour les valeurs de configuration de l’hôte :

JSON

{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
// Configuration ommitted for brevity.
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "https://localhost:5001",
"ASPNETCORE_DETAILEDERRORS": "1",
"ASPNETCORE_SHUTDOWNTIMEOUTSECONDS": "3"
},
// Configuration ommitted for brevity.

Le fichier .vscode/launch.json est utilisé uniquement par Visual Studio Code.

Production
Vous devez configurer l’environnement de production pour optimiser la sécurité, les
performances et la robustesse de l’application. Voici quelques paramètres courants qui
diffèrent du développement :

Mise en cache.
Les ressources côté client sont groupées, réduites et éventuellement servies à
partir d’un CDN.
Les Pages d’erreur de diagnostic sont désactivées.
Les pages d’erreur conviviales sont activées.
La journalisation et la surveillance de la production sont activées. Par exemple, en
utilisat Application Insights.
Définir l’environnement en définissant une
variable d’environnement
Il est souvent utile de définir un environnement spécifique pour les tests avec une
variable d’environnement ou un paramètre de plateforme. Si l’environnement n’est pas
défini, il prend par défaut la valeur Production , ce qui désactive la plupart des
fonctionnalités de débogage. La méthode de configuration de l’environnement dépend
du système d’exploitation.

Lorsque l’hôte est généré, le dernier paramètre d’environnement lu par l’application


détermine l’environnement de l’application. L’environnement de l’application ne peut
pas être changé pendant que l’application est en cours d’exécution.

La page À propos de l’échantillon de code affiche la valeur de


IWebHostEnvironment.EnvironmentName .

Azure App Service


Production est la valeur par défaut si DOTNET_ENVIRONMENT et ASPNETCORE_ENVIRONMENT
n’ont pas été définis. Les applications déployées sur Azure sont Production par défaut.

Pour définir l’environnement dans une application Azure App Service à l’aide du
portail :

1. Sélectionnez l’application dans la page App Services.


2. Dans le groupe Paramètres, sélectionnez Configuration.
3. Sous l’onglet Paramètres d’application, sélectionnez Nouveau paramètre
d’application.
4. Dans la fenêtre Ajouter/Modifier un paramètre , indiquez ASPNETCORE_ENVIRONMENT
pour le Nom. Pour Valeur, spécifiez l’environnement (par exemple Staging ).
5. Cochez la case Paramètre d’emplacement de déploiement si vous souhaitez que
le paramètre d’environnement reste avec l’emplacement actuel quand des
emplacements de déploiement sont permutés. Pour plus d’informations, consultez
Configurer des environnements intermédiaires dans Azure App Service dans la
documentation Azure.
6. Sélectionnez OK pour fermer la boîte de dialogue Ajouter/modifier le paramètre
d’application .
7. Sélectionnez Enregistrer en haut de la page Configuration.

Azure App Service redémarre automatiquement l’application après qu’un paramètre


d’application est ajouté, changé ou supprimé dans le portail Azure.
Windows - Définir une variable d’environnement pour un
processus
Les valeurs d’environnement dans launchSettings.json remplacent les valeurs définies
dans l’environnement système.

Pour définir le ASPNETCORE_ENVIRONMENT pour la session active lorsque l’application est


démarrée à l’aide de dotnet run, utilisez les commandes suivantes à l’invite de
commandes ou dans PowerShell :

Console

set ASPNETCORE_ENVIRONMENT=Staging
dotnet run --no-launch-profile

PowerShell

$Env:ASPNETCORE_ENVIRONMENT = "Staging"
dotnet run --no-launch-profile

Windows - Définir la variable d’environnement


globalement
Les commandes précédentes définissent ASPNETCORE_ENVIRONMENT uniquement pour les
processus lancés à partir de cette fenêtre de commande.

Pour définir la valeur globalement dans Windows, utilisez l’une des approches suivantes
:

Ouvrez le Panneau de configuration>Système>Paramètres système avancés, puis


ajoutez ou modifiez la valeur ASPNETCORE_ENVIRONMENT :
Ouvrez une invite de commandes d’administration, puis utilisez la commande
setx , ou ouvrez une invite de commandes PowerShell d’administration et utilisez

[Environment]::SetEnvironmentVariable :

Console

setx ASPNETCORE_ENVIRONMENT Staging /M

Le commutateur /M définit la variable d’environnement au niveau du système.


Si le commutateur /M n’est pas utilisé, la variable d’environnement est définie
pour le compte d’utilisateur.

PowerShell

[Environment]::SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT",
"Staging", "Machine")

La valeur d’option Machine définit la variable d’environnement au niveau du


système. Si la valeur d’option est remplacée par User , la variable
d’environnement est définie pour le compte d’utilisateur.
Quand la variable d’environnement ASPNETCORE_ENVIRONMENT est définie globalement,
elle prend effet pour dotnet run dans n’importe quelle fenêtre Commande ouverte une
fois la valeur définie. Les valeurs d’environnement dans launchSettings.json remplacent
les valeurs définies dans l’environnement système.

Windows - Utiliser web.config


Pour définir la variable d’environnement ASPNETCORE_ENVIRONMENT avec web.config ,
consultez la section Définition des variables d’environnement à l’adresse web.config file.

Windows - Déploiements IIS


Incluez la propriété <EnvironmentName> dans le profil de publication (.pubxml) ou le
fichier projet. Cette approche définit l’environnement dans web.config lorsque le projet
est publié :

XML

<PropertyGroup>
<EnvironmentName>Development</EnvironmentName>
</PropertyGroup>

Pour définir la variables d’environnement ASPNETCORE_ENVIRONMENT pour une application


qui s’exécute dans un pool d’applications isolé (prie en charge sur IIS 10.0 ou versions
ultérieures), consultez la section Commande AppCmd.exe de Variables d’environnement
<environmentVariables>. Quand la variable d’environnement ASPNETCORE_ENVIRONMENT
est définie pour un pool d’applications, sa valeur remplace un paramètre au niveau du
système.

Lors de l’hébergement d’une application dans IIS et de l’ajout ou du changement de la


variable d’environnement ASPNETCORE_ENVIRONMENT , utilisez l’une des approches
suivantes pour que la nouvelle valeur soit récupérée par des applications :

Exécutez la commande net stop was /y suivie de net start w3svc à partir d’une
invite de commandes.
Redémarrez le serveur.

macOS
Vous pouvez définir l’environnement actuel pour macOS en ligne durant l’exécution de
l’application :
Bash

ASPNETCORE_ENVIRONMENT=Staging dotnet run

Vous pouvez également définir l’environnement avec export avant d’exécuter


l’application :

Bash

export ASPNETCORE_ENVIRONMENT=Staging

Les variables d’environnement de niveau machine sont définies dans le fichier .bashrc ou
.bash_profile. Modifiez le fichier à l’aide d’un éditeur de texte. Ajoutez l’instruction
suivante :

Bash

export ASPNETCORE_ENVIRONMENT=Staging

Linux
Pour les distributions Linux, exécutez la commande export à une invite de commandes
pour les paramètres de variable basés sur la session, et le fichier bash_profile pour les
paramètres d’environnement de niveau machine.

Définir l’environnement dans le code


Pour définir l’environnement dans le code, utilisez
WebApplicationOptions.EnvironmentName lors de la création de
WebApplicationBuilder, comme illustré dans l’exemple suivant :

C#

var builder = WebApplication.CreateBuilder(new WebApplicationOptions


{
EnvironmentName = Environments.Staging
});

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Pour plus d’informations, consultez Hôte générique .NET dans ASP.NET Core.

Configuration par environnement


Pour charger la configuration par environnement, consultez Configuration dans ASP.NET
Core.

Configurer les services et les intergiciels par


environnement
Utilisez WebApplicationBuilder.Environment ou WebApplication.Environment pour
ajouter de manière conditionnelle des services ou des intergiciels en fonction de
l’environnement actuel. Le modèle de projet inclut un exemple de code qui ajoute des
intergiciels uniquement lorsque l’environnement actuel n’est pas Développement :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Le code en surbrillance vérifie l’environnement actuel lors de la génération du pipeline


de requête. Pour vérifier l’environnement actuel lors de la configuration des services,
utilisez builder.Environment au lieu de app.Environment .

Ressources supplémentaires
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Démarrage d’une application dans ASP.NET Core
Configuration dans ASP.NET Core
Environnements ASP.NET Core Blazor

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Journalisation dans .NET Core et
ASP.NET Core
Article • 06/12/2023

Par Kirk Larkin , Juergen Gutsch et Rick Anderson

Cette rubrique décrit la journalisation dans .NET, telle qu’elle s’applique aux applications
ASP.NET Core. Pour plus d’informations sur la journalisation dans .NET, consultez
Journalisation dans .NET. Pour plus d’informations sur la journalisation dans les
applications Blazor, consultez Journalisation Blazor ASP.NET Core.

Fournisseurs de journalisation
Les fournisseurs de journalisation permettent de stocker les journaux, à l’exception du
fournisseur Console qui permet d’afficher les journaux. Par exemple, le fournisseur Azure
Application Insights stocke les journaux dans Azure Application Insights. Plusieurs
fournisseurs peuvent être activés.

Les modèles d’application web ASP.NET Core par défaut :

Utilisent l’hôte générique.


Appelle WebApplication.CreateBuilder, qui ajoute les fournisseurs de journalisation
suivants :
Console
Déboguer
EventSource
EventLog : Windows uniquement

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Le code précédent montre le fichier Program.cs créé à l’aide des modèles d’application
web ASP.NET Core. Les sections suivantes fournissent des exemples basés sur les
modèles d’application web ASP.NET Core, qui utilisent l’hôte générique.

Le code suivant remplace l’ensemble de fournisseurs de journalisation par défaut ajouté


par WebApplication.CreateBuilder :

C#

var builder = WebApplication.CreateBuilder(args);


builder.Logging.ClearProviders();
builder.Logging.AddConsole();

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Vous pouvez également écrire le code de journalisation précédent de la façon suivante :

C#

var builder = WebApplication.CreateBuilder();


builder.Host.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
});

Pour connaître les autres fournisseurs, consultez :

Fournisseurs de journalisation intégrés


Fournisseurs de journalisation tiers.

Créer des journaux


Pour créer des journaux, utilisez un objet ILogger<TCategoryName> issu d’une injection
de dépendances.

L’exemple suivant :

Crée un enregistreur d’événements ( ILogger<AboutModel> ) qui utilise une catégorie


de journal pour le nom complet du type AboutModel . La catégorie du journal est
une chaîne associée à chaque journal.
Appelle LogInformation pour journaliser au niveau Information. Le niveau du
journal indique la gravité de l’événement consigné.

C#

public class AboutModel : PageModel


{
private readonly ILogger _logger;

public AboutModel(ILogger<AboutModel> logger)


{
_logger = logger;
}

public void OnGet()


{
_logger.LogInformation("About page visited at {DT}",
DateTime.UtcNow.ToLongTimeString());
}
}

Les niveaux et les catégories sont expliqués en détail plus loin dans ce document.

Pour plus d’informations sur Blazor, consultez Journalisation Blazor ASP.NET Core.

Configuration de la journalisation
La configuration de la journalisation est généralement fournie par la section Logging
des fichiers appsettings.{ENVIRONMENT}.json , où l’espace réservé {ENVIRONMENT}
correspond à l’environnement. Le fichier appsettings.Development.json suivant est
généré par les modèles d’application web ASP.NET Core :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

Dans le code JSON précédent :

Les catégories "Default" et "Microsoft.AspNetCore" sont spécifiées.


La catégorie "Microsoft.AspNetCore" s’applique à toutes les catégories qui
commencent par "Microsoft.AspNetCore" . Par exemple, ce paramètre s’applique à
la catégorie "Microsoft.AspNetCore.Routing.EndpointMiddleware" .
La catégorie "Microsoft.AspNetCore" journalise au niveau de journalisation
Warning et aux niveaux supérieurs.

Aucun fournisseur de journaux n’est spécifié. LogLevel s’applique donc à tous les
fournisseurs de journalisation activés, à l’exception de Windows EventLog.

La propriété Logging peut avoir des propriétés LogLevel et des propriétés de fournisseur
de journaux. La propriété LogLevel spécifie le niveau de journalisation minimal pour les
catégories sélectionnées. Dans le code JSON précédent, les niveaux de journalisation
Information et Warning sont spécifiés. LogLevel indique le niveau de gravité du journal,

qui peut varier de 0 à 6 :

Trace = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Critical = 5 et None =

6.

Quand LogLevel est spécifié, la journalisation est activée pour les messages au niveau
spécifié et aux niveaux supérieurs. Dans le code JSON précédent, la catégorie Default
est journalisée pour Information et niveaux supérieurs. Par exemple, les messages
Information , Warning , Error et Critical sont journalisés. Si aucun n’est LogLevel

spécifié, la journalisation est définie par défaut sur le niveau Information . Pour plus
d’informations, consultez Niveaux de journalisation.
Une propriété de fournisseur peut spécifier une propriété LogLevel . Le LogLevel indiqué
sous un fournisseur spécifie les niveaux à journaliser pour ce fournisseur, et remplace les
paramètres de journalisation ne concernant pas le fournisseur. Examinons le fichier
appsettings.json suivant :

JSON

{
"Logging": {
"LogLevel": { // All providers, LogLevel applies to all the enabled
providers.
"Default": "Error", // Default logging, Error and higher.
"Microsoft": "Warning" // All Microsoft* categories, Warning and
higher.
},
"Debug": { // Debug provider.
"LogLevel": {
"Default": "Information", // Overrides preceding LogLevel:Default
setting.
"Microsoft.Hosting": "Trace" // Debug:Microsoft.Hosting category.
}
},
"EventSource": { // EventSource provider
"LogLevel": {
"Default": "Warning" // All categories of EventSource provider.
}
}
}
}

Les paramètres de Logging.{PROVIDER NAME}.LogLevel remplacent les paramètres de


Logging.LogLevel , où l’espace réservé {PROVIDER NAME} correspond au nom du

fournisseur. Dans le code JSON précédent, le niveau de journalisation par défaut du


fournisseur Debug est défini sur Information :

Logging:Debug:LogLevel:Default:Information

Le paramètre précédent spécifie le niveau de journalisation Information pour chaque


catégorie Logging:Debug: , sauf Microsoft.Hosting . Lorsqu’une catégorie spécifique est
listée, celle-ci remplace la catégorie par défaut. Dans le code JSON précédent, les
catégories Logging:Debug:LogLevel "Microsoft.Hosting" et "Default" remplacent les
paramètres de Logging:LogLevel .

Le niveau de journalisation minimal peut être spécifié pour tous les éléments suivants :

Certains fournisseurs : par exemple,


Logging:EventSource:LogLevel:Default:Information
Certaines catégories : par exemple, Logging:LogLevel:Microsoft:Warning
Tous les fournisseurs et toutes les catégories : Logging:LogLevel:Default:Warning

Les journaux situés en dessous du niveau minimal ne seront pas :

Passés au fournisseur.
Journalisés ou affichés.

Pour empêcher la génération de tous les journaux, spécifiez LogLevel.None.


LogLevel.None a une valeur de 6, ce qui est supérieur à LogLevel.Critical (5).

Lorsqu’un fournisseur prend en charge les étendues de journal, IncludeScopes indique si


elles sont activées. Pour plus d’informations, consultez Étendues de journalisation.

Le fichier appsettings.json suivant contient tous les fournisseurs activés par défaut :

JSON

{
"Logging": {
"LogLevel": { // No provider, LogLevel applies to all the enabled
providers.
"Default": "Error",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Warning"
},
"Debug": { // Debug provider.
"LogLevel": {
"Default": "Information" // Overrides preceding LogLevel:Default
setting.
}
},
"Console": {
"IncludeScopes": true,
"LogLevel": {
"Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning",
"Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug",
"Microsoft.AspNetCore.Mvc.Razor": "Error",
"Default": "Information"
}
},
"EventSource": {
"LogLevel": {
"Microsoft": "Information"
}
},
"EventLog": {
"LogLevel": {
"Microsoft": "Information"
}
},
"AzureAppServicesFile": {
"IncludeScopes": true,
"LogLevel": {
"Default": "Warning"
}
},
"AzureAppServicesBlob": {
"IncludeScopes": true,
"LogLevel": {
"Microsoft": "Information"
}
},
"ApplicationInsights": {
"LogLevel": {
"Default": "Information"
}
}
}
}

Dans l’exemple précédent :

Les catégories et les niveaux ne sont pas des valeurs suggérées. L’exemple est
fourni pour afficher tous les fournisseurs par défaut.
Les paramètres de Logging.{PROVIDER NAME}.LogLevel remplacent les paramètres
de Logging.LogLevel , où l’espace réservé {PROVIDER NAME} correspond au nom du
fournisseur. Par exemple, le niveau dans Debug.LogLevel.Default remplace le
niveau dans LogLevel.Default .
Chaque alias de fournisseur par défaut est utilisé. Chaque fournisseur définit un
alias qui peut être utilisé dans la configuration à la place du nom de type complet.
Les alias de fournisseurs intégrés sont les suivants :
Console

Debug
EventSource

EventLog

AzureAppServicesFile
AzureAppServicesBlob

ApplicationInsights

Journal dans Program.cs


L’exemple suivant appelle Builder.WebApplication.Logger dans Program.cs et journalise
les messages d’information :
C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();
app.Logger.LogInformation("Adding Routes");
app.MapGet("/", () => "Hello World!");
app.Logger.LogInformation("Starting the app");
app.Run();

L’exemple suivant appelle AddConsole dans Program.cs et journalise le point de


terminaison /Test :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddConsole();

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/Test", async (ILogger<Program> logger, HttpResponse response)


=>
{
logger.LogInformation("Testing logging in Program.cs");
await response.WriteAsync("Testing");
});

app.Run();

L’exemple suivant appelle AddSimpleConsole dans Program.cs , désactive la sortie


couleur et journalise le point de terminaison /Test :

C#

using Microsoft.Extensions.Logging.Console;

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddSimpleConsole(i => i.ColorBehavior =


LoggerColorBehavior.Disabled);

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/Test", async (ILogger<Program> logger, HttpResponse response)


=>
{
logger.LogInformation("Testing logging in Program.cs");
await response.WriteAsync("Testing");
});

app.Run();

Définir le niveau de journalisation à l’aide de la


ligne de commande, de variables
d’environnement et d’autres méthodes de
configuration
Le niveau de journalisation peut être défini par l’un des fournisseurs de configuration.

Le séparateur : ne fonctionne pas avec les clés hiérarchiques des variables


d’environnement sur toutes les plateformes. Le double trait de soulignement __ est :

Pris en charge par toutes les plateformes. Par exemple, le séparateur : n’est pas
pris en charge par Bash , mais __ est pris en charge.
Remplacé automatiquement par :

Les commandes suivantes permettent de :

Définissez la clé d’environnement Logging:LogLevel:Microsoft sur la valeur


Information dans Windows.

Testez les paramètres lorsque vous utilisez une application créée avec les modèles
d’application web ASP.NET Core. La commande dotnet run doit être exécutée
dans le répertoire du projet après l’utilisation de set .

CLI .NET

set Logging__LogLevel__Microsoft=Information
dotnet run

Le paramètre d’environnement précédent :

Est uniquement défini dans les processus lancés à partir de la fenêtre de


commande dans laquelle il a été défini.
Ne sera pas lu par les navigateurs lancés avec Visual Studio.

La commande setx suivante définit également la clé et la valeur d’environnement dans


Windows. Contrairement à set , les paramètres setx sont persistants. Le commutateur
/M définit la variable dans l’environnement système. Si /M n’est pas utilisé, une variable

d’environnement utilisateur est définie.

Console

setx Logging__LogLevel__Microsoft Information /M

Examinons le fichier appsettings.json suivant :

JSON

"Logging": {
"Console": {
"LogLevel": {
"Microsoft.Hosting.Lifetime": "Trace"
}
}
}

La commande suivante définit la configuration précédente dans l’environnement :

Console

setx Logging__Console__LogLevel__Microsoft.Hosting.Lifetime Trace /M

7 Notes

Quand vous configurez des variables d’environnement avec des noms qui
contiennent . (des points) dans macOS et Linux, prenez en compte la question
« Exporter une variable avec un point (.) » sur Stack Exchange et sa réponse
acceptée correspondante.

Dans Azure App Service , sélectionnez Nouveau paramètre d’application dans la page
Paramètres > Configuration. Les paramètres d’application Azure App Service sont :

Chiffrés au repos et transmis sur un canal chiffré.


Exposés en tant que variables d’environnement.

Pour plus d’informations, consultez Azure Apps : remplacer la configuration de


l’application à l’aide du portail Azure.

Pour plus d’informations sur la définition de valeurs de configuration ASP.NET Core à


l’aide de variables d’environnement, consultez Variables d’environnement. Pour plus
d’informations sur l’utilisation d’autres sources de configuration, notamment la ligne de
commande, Azure Key Vault, Azure App Configuration, d’autres formats de fichiers, etc.
consultez Configuration dans ASP.NET Core.

Mode d’application des règles de filtre


À la création d’un objet ILogger<TCategoryName>, l’objet ILoggerFactory sélectionne
une seule règle à appliquer à cet enregistrement d’événements par fournisseur. Tous les
messages écrits par une instance ILogger sont filtrés selon les règles sélectionnées. La
règle la plus précise qui peut être appliquée à chaque paire catégorie/fournisseur est
sélectionnée parmi les règles disponibles.

L’algorithme suivant est utilisé pour chaque fournisseur quand un objet ILogger est créé
pour une catégorie donnée :

Sélectionnez toutes les règles qui correspondent au fournisseur ou à son alias. Si


aucune correspondance n’est trouvée, sélectionnez toutes les règles avec un
fournisseur vide.
À partir du résultat de l’étape précédente, sélectionnez les règles ayant le plus long
préfixe de catégorie correspondant. Si aucune correspondance n’est trouvée,
sélectionnez toutes les règles qui ne spécifient pas de catégorie.
Si plusieurs règles sont sélectionnées, prenez la dernière.
Si aucune règle n’est sélectionnée, utilisez MinimumLevel .

Journalisation de la sortie de l’exécution dotnet


et Visual Studio
Les journaux créés à l’aide des fournisseurs de journalisation par défaut sont affichés :

Dans Visual Studio


Dans la fenêtre de sortie Débogage lors du débogage.
Dans la fenêtre ASP.NET Core Web Server.
Dans la fenêtre de console lorsque l’application est exécutée avec dotnet run .

Les journaux qui commencent par les catégories « Microsoft » proviennent du code du
framework ASP.NET Core. ASP.NET Core et le code d’application utilisent la même API
de journalisation et les mêmes fournisseurs.

Catégorie de journal
Quand un objet ILogger est créé, une catégorie est spécifiée. Cette catégorie est incluse
dans tous les messages de journal créés par cette instance de ILogger . La chaîne de
catégorie est arbitraire, cependant, la convention veut que l’on utilise le nom de la
classe. Par exemple, dans un contrôleur, le nom peut être
"TodoApi.Controllers.TodoController" . Les applications web ASP.NET Core utilisent

ILogger<T> pour obtenir automatiquement une instance ILogger qui utilise le nom de

type complet de T en tant que catégorie :

C#

public class PrivacyModel : PageModel


{
private readonly ILogger<PrivacyModel> _logger;

public PrivacyModel(ILogger<PrivacyModel> logger)


{
_logger = logger;
}

public void OnGet()


{
_logger.LogInformation("GET Pages.PrivacyModel called.");
}
}

Pour spécifier explicitement la catégorie, appelez ILoggerFactory.CreateLogger :

C#

public class ContactModel : PageModel


{
private readonly ILogger _logger;

public ContactModel(ILoggerFactory logger)


{
_logger = logger.CreateLogger("MyCategory");
}

public void OnGet()


{
_logger.LogInformation("GET Pages.ContactModel called.");
}

L’appel de CreateLogger avec un nom fixe peut être utile si vous l’utilisez dans plusieurs
méthodes afin que les événements puissent être organisés par catégorie.

ILogger<T> équivaut à appeler CreateLogger avec le nom de type complet T .


Log level
Le tableau suivant liste les valeurs LogLevel, la méthode d’extension de commodité
Log{LogLevel} , ainsi que des suggestions d’utilisation :

ノ Expand table

LogLevel Valeur Méthode Description

Trace 0 LogTrace Contiennent les messages les plus détaillés. Ces


messages peuvent contenir des données d’application
sensibles. Ils sont désactivés par défaut et ne doivent
pas être activés dans un environnement de production.

Debug 1 LogDebug Pour le débogage et le développement. À utiliser avec


prudence dans un environnement de production en
raison du volume élevé de messages.

Information 2 LogInformation Effectue le suivi du flux général de l’application. Peut


avoir une valeur à long terme.

Warning 3 LogWarning Pour les événements anormaux ou inattendus.


Comprend généralement des erreurs ou des conditions
qui n’entraînent pas l’échec de l’application.

Error 4 LogError Fournit des informations sur des erreurs et des


exceptions qui ne peuvent pas être gérées. Ces
messages indiquent un échec de l’opération ou de la
demande en cours, et non l’échec de l’application.

Critical 5 LogCritical Fournit des informations sur des échecs qui nécessitent
un examen immédiat. Exemples : perte de données,
espace disque insuffisant.

None 6 Spécifie qu’une catégorie de journalisation ne doit écrire


aucun message.

Dans le tableau précédent, LogLevel est listé du plus faible niveau de gravité au plus
élevé.

Le premier paramètre de la méthode Log (LogLevel) indique le niveau de gravité du


journal. Au lieu d’appeler Log(LogLevel, ...) , la plupart des développeurs appellent les
méthodes d’extension Log{LOG LEVEL}, où l’espace réservé {LOG LEVEL} correspond au
niveau de journalisation. Par exemple, les deux appels de journalisation suivants ont un
fonctionnement équivalent et produisent le même journal :

C#
[HttpGet]
public IActionResult Test1(int id)
{
var routeInfo = ControllerContext.ToCtxString(id);

_logger.Log(LogLevel.Information, MyLogEvents.TestItem, routeInfo);


_logger.LogInformation(MyLogEvents.TestItem, routeInfo);

return ControllerContext.MyDisplayRouteInfo();
}

MyLogEvents.TestItem est l’ID de l’événement. MyLogEvents fait partie de l’exemple

d’application et s’affiche dans la section ID d’événement du journal.

MyDisplayRouteInfo et ToCtxString sont fournis par le package NuGet


Rick.Docs.Samples.RouteInfo . Les méthodes affichent les informations de routage
Controller et Razor Page .

Le code suivant crée des journaux Information et Warning :

C#

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
_logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
{
_logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT
FOUND", id);
return NotFound();
}

return ItemToDTO(todoItem);
}

Dans le code précédent, le premier paramètre Log{LOG LEVEL} ( MyLogEvents.GetItem )


est l’ID de l’événement de journal. Le deuxième paramètre est un modèle de message
contenant des espaces réservés pour les valeurs d’argument fournies par les autres
paramètres de méthode. Les paramètres de méthode sont expliqués dans la section
Modèle de message plus loin dans ce document.

Appelez la méthode Log{LOG LEVEL} appropriée pour contrôler le volume de la sortie de


journal pouvant être écrite sur un support de stockage spécifique. Exemple :
En production :
La journalisation aux niveaux Trace , Debug ou Information produit un volume
élevé de messages de journal détaillés. Pour contrôler les coûts et ne pas
dépasser les limites de stockage des données, journaliser Trace , Debug ou
Information messages de niveau vers un magasin de données à faible coût et à

volume élevé. Envisagez de limiter Trace , Debug ou Information à des


catégories spécifiques.
La journalisation aux niveaux allant de Warning à Critical doivent produire
moins de messages de journal.
Les coûts et les limites de stockage ne posent généralement pas problème.
Peu de journaux permettent une plus grande flexibilité au niveau du choix du
magasin de données.
Lors du développement :
Réglez sur Warning .
Ajoutez Trace , Debug ou Information messages lors de la résolution des
problèmes. Pour limiter la sortie, définissez Trace , Debug ou Information
uniquement pour les catégories en cours d’examen.

ASP.NET Core écrit des journaux pour les événements de framework. Par exemple, tenez
compte de la sortie du journal pour :

Une application Razor Pages créée avec les modèles ASP.NET Core.
Journalisation définie sur Logging:Console:LogLevel:Microsoft:Information .
Navigation vers la page Privacy :

Console

info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 GET https://localhost:5001/Privacy
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint '/Privacy'
info:
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[3]
Route matched with {page = "/Privacy"}. Executing page /Privacy
info:
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[101]
Executing handler method DefaultRP.Pages.PrivacyModel.OnGet -
ModelState is Valid
info:
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[102]
Executed handler method OnGet, returned result .
info:
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[103]
Executing an implicit handler method - ModelState is Valid
info:
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[104]
Executed an implicit handler method, returned result
Microsoft.AspNetCore.Mvc.RazorPages.PageResult.
info:
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker[4]
Executed page /Privacy in 74.5188ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint '/Privacy'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 149.3023ms 200 text/html; charset=utf-8

Le code JSON suivant définit Logging:Console:LogLevel:Microsoft:Information :

JSON

{
"Logging": { // Default, all providers.
"LogLevel": {
"Microsoft": "Warning"
},
"Console": { // Console provider.
"LogLevel": {
"Microsoft": "Information"
}
}
}
}

ID d’événement de log
Chaque journal peut spécifier un ID d’événement. L’exemple d’application utilise la classe
MyLogEvents pour définir les ID d’événement :

C#

public class MyLogEvents


{
public const int GenerateItems = 1000;
public const int ListItems = 1001;
public const int GetItem = 1002;
public const int InsertItem = 1003;
public const int UpdateItem = 1004;
public const int DeleteItem = 1005;

public const int TestItem = 3000;

public const int GetItemNotFound = 4000;


public const int UpdateItemNotFound = 4001;
}
C#

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
_logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
{
_logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT
FOUND", id);
return NotFound();
}

return ItemToDTO(todoItem);
}

Un ID d’événement associe un jeu d’événements. Par exemple, tous les journaux liés à
l’affichage d’une liste d’éléments sur une page peuvent être 1001.

Le fournisseur de journalisation peut stocker l’ID d’événement dans un champ ID, dans
le message de journalisation, ou pas du tout. Le fournisseur Debug n’affiche pas les ID
d’événements. Le fournisseur Console affiche les ID d’événements entre crochets après
la catégorie :

Console

info: TodoApi.Controllers.TodoItemsController[1002]
Getting item 1
warn: TodoApi.Controllers.TodoItemsController[4000]
Get(1) NOT FOUND

Certains fournisseurs de journalisation stockent l’ID d’événement dans un champ, ce qui


permet de filtrer en fonction de l’ID.

Modèle de message de journal


Chaque API de journalisation utilise un modèle de message. Ce dernier peut contenir
des espaces réservés pour lesquels les arguments sont fournis. Utilisez des noms et non
des nombres pour les espaces réservés.

C#

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
_logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);

var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
{
_logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT
FOUND", id);
return NotFound();
}

return ItemToDTO(todoItem);
}

C’est l’ordre des paramètres, et non leur nom d’espace réservé, qui détermine les
paramètres utilisés pour fournir des valeurs d’espace réservé dans les messages de
journal. Dans le code suivant, les noms de paramètres ne sont pas dans l’ordre dans le
modèle de message :

C#

var apples = 1;
var pears = 2;
var bananas = 3;

_logger.LogInformation("Parameters: {Pears}, {Bananas}, {Apples}", apples,


pears, bananas);

Cependant, les paramètres sont attribués aux espaces réservés dans l’ordre : apples ,
pears , bananas . Le message de journal reflète l’ordre des paramètres :

text

Parameters: 1, 2, 3

Cette approche permet aux fournisseurs de journalisation d’implémenter une


journalisation sémantique ou structurée . Les arguments proprement dits, et pas
seulement le modèle de message mis en forme, sont transmis au système de
journalisation. Cela permet aux fournisseurs de journalisation de stocker les valeurs des
paramètres sous forme de champs. Prenons l’exemple de la méthode d’enregistreur
d’événements suivante :

C#

_logger.LogInformation("Getting item {Id} at {RequestTime}", id,


DateTime.Now);

Par exemple, lorsque vous journalisez des événements vers le Stockage Table Azure :

Chaque entité de table Azure peut avoir des propriétés ID et RequestTime .


Les tables avec des propriétés simplifient les requêtes qui sont exécutées sur des
données journalisées. Par exemple, vous pouvez rechercher tous les journaux
compris dans une plage RequestTime spécifique, sans avoir besoin d’analyser le
délai d’expiration du message texte.

Enregistrer des exceptions


Les méthodes d’enregistreur d’événements ont des surcharges qui prennent un
paramètre d’exception :

C#

[HttpGet("{id}")]
public IActionResult TestExp(int id)
{
var routeInfo = ControllerContext.ToCtxString(id);
_logger.LogInformation(MyLogEvents.TestItem, routeInfo);

try
{
if (id == 3)
{
throw new Exception("Test exception");
}
}
catch (Exception ex)
{
_logger.LogWarning(MyLogEvents.GetItemNotFound, ex, "TestExp({Id})",
id);
return NotFound();
}

return ControllerContext.MyDisplayRouteInfo();
}

MyDisplayRouteInfo et ToCtxString sont fournis par le package NuGet


Rick.Docs.Samples.RouteInfo . Les méthodes affichent les informations de routage
Controller et Razor Page .

La journalisation des exceptions dépend du fournisseur.


Niveau de journalisation par défaut
Si le niveau de journalisation par défaut n’est pas défini, la valeur sera Information .

Prenons l’exemple de l’application web suivante :

Créée avec les modèles d’application web ASP.NET.


appsettings.json et appsettings.Development.json ont été supprimés ou

renommés.

Avec la configuration précédente, l’accès à la page de confidentialité ou d’accueil


produit de nombreux messages Trace , Debug et Information de catégorie Microsoft .

Le code suivant définit le niveau de journalisation par défaut lorsque celui-ci n’est pas
défini dans la configuration :

C#

var builder = WebApplication.CreateBuilder();


builder.Logging.SetMinimumLevel(LogLevel.Warning);

En règle générale, les niveaux de journalisation doivent être spécifiés dans la


configuration et non dans le code.

fonction Filter
Une fonction de filtre est appelée pour tous les fournisseurs et toutes les catégories
auxquels aucune règle n’est affectée dans la configuration ou dans le code :

C#

var builder = WebApplication.CreateBuilder();


builder.Logging.AddFilter((provider, category, logLevel) =>
{
if (provider.Contains("ConsoleLoggerProvider")
&& category.Contains("Controller")
&& logLevel >= LogLevel.Information)
{
return true;
}
else if (provider.Contains("ConsoleLoggerProvider")
&& category.Contains("Microsoft")
&& logLevel >= LogLevel.Information)
{
return true;
}
else
{
return false;
}
});

Le code précédent affiche les journaux de console lorsque la catégorie contient


Controller ou Microsoft , et si le niveau de journalisation est Information ou supérieur.

En règle générale, les niveaux de journalisation doivent être spécifiés dans la


configuration et non dans le code.

ASP.NET Core et catégories EF Core


Le tableau suivant contient certaines catégories utilisées par ASP.NET Core et Entity
Framework Core, ainsi que des notes à propos des journaux :

ノ Expand table

Catégorie Notes

Microsoft.AspNetCore Diagnostics ASP.NET Core généraux.

Microsoft.AspNetCore.DataProtection Liste des clés considérées, trouvées et utilisées.

Microsoft.AspNetCore.HostFiltering Hôtes autorisés.

Microsoft.AspNetCore.Hosting Temps de traitement des requêtes HTTP et heure de


démarrage. Liste des assemblys de démarrage
d’hébergement chargés.

Microsoft.AspNetCore.Mvc Diagnostics MVC et Razor. Liaison de données, exécution


de filtres, compilation de vues, sélection d’actions.

Microsoft.AspNetCore.Routing Informations de correspondance des itinéraires.

Microsoft.AspNetCore.Server Démarrage et arrêt de la connexion, et réponses


persistantes. Informations du certificat HTTPS.

Microsoft.AspNetCore.StaticFiles Fichiers pris en charge.

Microsoft.EntityFrameworkCore Diagnostics Entity Framework Core généraux. Activité et


configuration de la base de données, détection des
modifications, migrations.

Pour afficher d’autres catégories dans la fenêtre de console, définissez


appsettings.Development.json sur la valeur suivante :
JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Trace",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

Étendues de journal
Une étendue peut regrouper un ensemble d’opérations logiques. Ce regroupement
permet de joindre les mêmes données à tous les journaux créés au sein d’un ensemble.
Par exemple, chaque journal créé dans le cadre du traitement d’une transaction peut
contenir l’ID de la transaction.

Une étendue :

Est un type IDisposable retourné par la méthode BeginScope.


Dure jusqu’à sa suppression.

Les fournisseurs suivants prennent en charge les étendues :

Console

AzureAppServicesFile et AzureAppServicesBlob

Utilisez une étendue en incluant les appels de l’enregistrement d’événements dans un


bloc using :

C#

[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
TodoItem todoItem;
var transactionId = Guid.NewGuid().ToString();
using (_logger.BeginScope(new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("TransactionId",
transactionId),
}))
{
_logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}",
id);
todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null)
{
_logger.LogWarning(MyLogEvents.GetItemNotFound,
"Get({Id}) NOT FOUND", id);
return NotFound();
}
}

return ItemToDTO(todoItem);
}

Fournisseurs de journalisation intégrés


ASP.NET Core inclut les fournisseurs de journalisation suivants dans le cadre du
framework partagé :

Console
Debug
EventSource
EventLog

Les fournisseurs de journalisation suivants sont fournis par Microsoft, mais pas dans le
cadre du framework partagé. Ils doivent être installés en tant que NuGet
supplémentaire.

AzureAppServicesFile et AzureAppServicesBlob
ApplicationInsights

ASP.NET Core n’inclut pas de fournisseur de journalisation permettant d’écrire des


journaux dans des fichiers. Pour écrire des journaux d’activité dans des fichiers à partir
d’une application ASP.NET Core, vous pouvez utiliser un fournisseur de journalisation
tiers.

Pour plus d’informations sur stdout et sur la journalisation du débogage avec le module
ASP.NET Core, consultez Résoudre les problèmes liés à ASP.NET Core sur Azure App
Service et IIS et Module ASP.NET Core (ANCM) pour IIS.

Console
Le fournisseur Console journalise la sortie de la console. Pour plus d’informations sur
l’affichage des journaux Console durant la phase de développement, consultez
Journalisation de la sortie de l’exécution dotnet et Visual Studio.
Débogage
Le fournisseur Debug écrit la sortie du journal à l’aide de la classe
System.Diagnostics.Debug. Les appels à System.Diagnostics.Debug.WriteLine sont écrits
dans le fournisseur Debug .

Sur Linux, l’emplacement du journal du fournisseur Debug dépend de la distribution et


peut être l’un des emplacements suivants :

/var/log/message
/var/log/syslog

Source de l’événement
Le fournisseur EventSource écrit les données dans une source d’événements
multiplateforme portant le nom de Microsoft-Extensions-Logging . Sur Windows, le
fournisseur utilise ETW.

Outils de suivi dotnet


L’outil dotnet-trace est un outil CLI multiplateforme qui permet la collection de
traces .NET Core issues d’un processus en cours d’exécution. L’outil collecte des
données de fournisseur Microsoft.Extensions.Logging.EventSource à l’aide d’un
LoggingEventSource.

Pour obtenir des instructions d’installation, consultez dotnet-trace.

Utilisez les outils dotnet trace pour collecter une trace à partir d’une application :

1. Exécutez l’application avec la commande dotnet run .

2. Déterminez l’identificateur de processus (PID) de l’application .NET Core :

CLI .NET

dotnet trace ps

Recherchez le PID du processus qui porte le même nom que l’assembly de


l’application.

3. Exécutez la commande dotnet trace .

Syntaxe de commande générale :


CLI .NET

dotnet trace collect -p {PID}


--providers Microsoft-Extensions-Logging:{Keyword}:{Provider Level}
:FilterSpecs=\"
{Logger Category 1}:{Category Level 1};
{Logger Category 2}:{Category Level 2};
...
{Logger Category N}:{Category Level N}\"

Lorsque vous utilisez un interpréteur de commandes PowerShell, placez la valeur -


-providers entre guillemets simples ( ' ) :

CLI .NET

dotnet trace collect -p {PID}


--providers 'Microsoft-Extensions-Logging:{Keyword}:{Provider
Level}
:FilterSpecs=\"
{Logger Category 1}:{Category Level 1};
{Logger Category 2}:{Category Level 2};
...
{Logger Category N}:{Category Level N}\"'

Sur les plateformes autres que Windows, ajoutez l’option -f speedscope pour
remplacer le format du fichier de trace de sortie par speedscope .

Le tableau suivant définit le mot clé :

ノ Expand table

Mot Description
clé

1 Journalise les événements meta à propos de LoggingEventSource . Ne journalise pas


les événements provenant de ILogger .

2 Active l’événement Message lors de l’appel de ILogger.Log() . Fournit les


informations de façon programmatique (sans mise en forme).

4 Active l’événement FormatMessage lors de l’appel de ILogger.Log() . Fournit la


version de chaîne mise en forme des informations.

8 Active l’événement MessageJson lors de l’appel de ILogger.Log() . Fournit une


représentation JSON des arguments.

Le tableau suivant liste les niveaux fournisseur :


ノ Expand table

Niveau fournisseur Description

0 LogAlways

1 Critical

2 Error

3 Warning

4 Informational

5 Verbose

L’analyse d’un niveau de catégorie peut être une chaîne ou un nombre :

ノ Expand table

Valeur nommée de catégorie Valeur numérique

Trace 0

Debug 1

Information 2

Warning 3

Error 4

Critical 5

Le niveau fournisseur et le niveau catégorie :

Sont dans l’ordre inverse.


Les constantes de chaîne ne sont pas toutes identiques.

Si aucune FilterSpecs n’est spécifiée, l’implémentation de EventSourceLogger


tente de convertir le niveau fournisseur en niveau catégorie, et l’applique à toutes
les catégories.

ノ Expand table

Niveau fournisseur Niveau catégorie

Verbose (5) Debug (1)


Niveau fournisseur Niveau catégorie

Informational (4) Information (2)

Warning (3) Warning (3)

Error (2) Error (4)

Critical (1) Critical (5)

Si des FilterSpecs sont fournies, toutes les catégories incluses dans la liste
utiliseront le niveau catégorie qui y est encodé. Toutes les autres catégories seront
filtrées.

Les exemples suivants supposent ce qui suit :

Une application s’exécute et appelle logger.LogDebug("12345") .


L’ID de processus (PID) a été défini via set PID=12345 , où 12345 est le PID
réel.

Prenez la commande suivante :

CLI .NET

dotnet trace collect -p %PID% --providers Microsoft-Extensions-


Logging:4:5

La commande précédente :

Capture les messages de débogage.


Ne s’applique pas aux FilterSpecs .
Spécifie le niveau 5 qui mappe la catégorie Débogage.

Prenez la commande suivante :

CLI .NET

dotnet trace collect -p %PID% --providers Microsoft-Extensions-


Logging:4:5:\"FilterSpecs=*:5\"

La commande précédente :

Ne capture pas les messages de débogage, car le niveau de catégorie 5 est


Critical .

Fournit des FilterSpecs .


La commande suivante capture les messages de débogage, car le niveau de
catégorie 1 spécifie Debug .

CLI .NET

dotnet trace collect -p %PID% --providers Microsoft-Extensions-


Logging:4:5:\"FilterSpecs=*:1\"

La commande suivante capture les messages de débogage, car la catégorie 1


spécifie Debug .

CLI .NET

dotnet trace collect -p %PID% --providers Microsoft-Extensions-


Logging:4:5:\"FilterSpecs=*:Debug\"

Les entrées FilterSpecs pour {Logger Category} et {Category Level}


représentent des conditions de filtrage des journaux supplémentaires. Séparez les
entrées FilterSpecs par un point-virgule ( ; ).

Exemple utilisant un interpréteur de commandes Windows :

CLI .NET

dotnet trace collect -p %PID% --providers Microsoft-Extensions-


Logging:4:2:FilterSpecs=\"Microsoft.AspNetCore.Hosting*:4\"

La commande précédente active :

L’enregistreur d’événements source afin de produire des chaînes mises en


forme ( 4 ) pour les erreurs ( 2 ).
La journalisation Microsoft.AspNetCore.Hosting au niveau de journalisation
Informational ( 4 ).

4. Arrêtez les outils de trace dotnet en appuyant sur la touche Entrée ou Ctrl + C .

La trace est enregistrée sous le nom trace.nettrace dans le dossier où la


commande dotnet trace est exécutée.

5. Ouvrez la trace avec Perfview. Ouvrez le fichier trace.nettrace et explorez les


événements de trace.

Si l’application ne génère pas l’hôte avec WebApplication.CreateBuilder, ajoutez le


fournisseur de source d’événements à la configuration de journalisation de l’application.
Pour plus d'informations, consultez les pages suivantes :

Trace de l’utilitaire d’analyse des performances (dotnet-trace) (documentation .NET


Core)
Trace de l’utilitaire d’analyse des performances (dotnet-trace) (documentation du
dépôt GitHub dotnet/diagnostics)
LoggingEventSource
EventLevel
Perfview : utile pour afficher les traces de source d’événements.

Perfview
Utilisez l’utilitaire PerfView pour collecter et afficher les journaux. Il existe d’autres
outils d’affichage des journaux ETW, mais PerfView est l’outil recommandé pour gérer
les événements ETW générés par ASP.NET Core.

Pour configurer PerfView afin qu’il collecte les événements enregistrés par ce
fournisseur, ajoutez la chaîne *Microsoft-Extensions-Logging à la liste des fournisseurs
supplémentaires. N’oubliez pas d’inclure le * au début de la chaîne.

Journal des événements Windows


Le fournisseur EventLog envoie la sortie de journal dans le journal des événements
Windows. Contrairement aux autres fournisseurs, le fournisseur EventLog n’hérite pas
des paramètres par défaut qui ne sont pas relatifs au fournisseur. Si les paramètres du
journal EventLog ne sont pas spécifiés, leur valeur par défaut est LogLevel.Warning.

Pour journaliser les événements inférieurs à LogLevel.Warning, définissez explicitement


le niveau de journalisation. L’exemple suivant définit le niveau de journalisation par
défaut du journal des événements sur LogLevel.Information :

JSON

"Logging": {
"EventLog": {
"LogLevel": {
"Default": "Information"
}
}
}

Les surcharges AddEventLog peuvent passer EventLogSettings. Si la valeur est null ou


non spécifiée, les paramètres par défaut suivants sont utilisés :
LogName : "Application"
SourceName : ".NET Runtime"

MachineName : le nom de l’ordinateur local est utilisé.

Le code suivant remplace la valeur par défaut de SourceName ( ".NET Runtime" ) par
MyLogs :

C#

var builder = WebApplication.CreateBuilder();


builder.Logging.AddEventLog(eventLogSettings =>
{
eventLogSettings.SourceName = "MyLogs";
});

Azure App Service


Le package de fournisseur Microsoft.Extensions.Logging.AzureAppServices écrit les
journaux dans des fichiers texte dans le système de fichiers d’une application Azure App
Service, ainsi que dans un stockage Blob dans un compte de stockage Azure.

Le fournisseur de package n’est pas inclus dans le framework partagé. Pour utiliser le
fournisseur, ajoutez le package du fournisseur au projet.

Pour configurer les paramètres du fournisseur, utilisez AzureFileLoggerOptions et


AzureBlobLoggerOptions, comme illustré dans l’exemple suivant :

C#

using Microsoft.Extensions.Logging.AzureAppServices;

var builder = WebApplication.CreateBuilder();


builder.Logging.AddAzureWebAppDiagnostics();
builder.Services.Configure<AzureFileLoggerOptions>(options =>
{
options.FileName = "azure-diagnostics-";
options.FileSizeLimit = 50 * 1024;
options.RetainedFileCountLimit = 5;
});
builder.Services.Configure<AzureBlobLoggerOptions>(options =>
{
options.BlobName = "log.txt";
});
Si elle est déployée dans Azure App Service, l’application utilise les paramètres qui sont
définis dans la section Journaux App Service de la page App Service du portail Azure.
Quand les paramètres suivants sont mis à jour, les changements prennent effet
immédiatement sans avoir besoin de redémarrer ou redéployer l’application.

Journalisation des applications (système de fichiers)


Journalisation des applications (objet blob)

L’emplacement par défaut des fichiers journaux est le dossier


D:\\home\\LogFiles\\Application , et le nom de fichier par défaut est diagnostics-
yyyymmdd.txt . La limite de taille de fichier par défaut est de 10 Mo, et le nombre

maximal de fichiers conservés par défaut est de 2. Le nom d’objet blob par défaut est
{app-name}{timestamp}/yyyy/mm/dd/hh/{guid}-applicationLog.txt .

Ce fournisseur journalise uniquement quand le projet s’exécute dans l’environnement


Azure.

Streaming des journaux Azure


Le streaming de journaux Azure prend en charge l’affichage de l’activité des journaux en
temps réel provenant du :

Serveur d'applications
Serveur web
Suivi des demandes ayant échoué

Pour configurer le streaming des journaux Azure :

Accédez à la page Journaux App Service dans le portail de l’application.


Définissez Journal des applications (Système de fichiers) sur Activé.
Choisissez le niveau du journal. Ce paramètre s’applique uniquement au streaming
de journaux Azure.

Accédez à la page Streaming des journaux pour afficher les journaux. Les messages
journalisés le sont avec l’interface ILogger .

Azure Application Insights


Le package de fournisseur Microsoft.Extensions.Logging.ApplicationInsights écrit les
journaux dans Azure Application Insights. Application Insights est un service qui surveille
une application web et fournit des outils pour interroger et analyser les données de
télémétrie. Si vous utilisez ce fournisseur, vous pouvez interroger et analyser vos
journaux à l’aide des outils Application Insights.
Le fournisseur de journalisation est inclus en tant que dépendance de
Microsoft.ApplicationInsights.AspNetCore , qui est le package qui fournit toutes les
données de télémétrie disponibles pour ASP.NET Core. Si vous utilisez ce package, vous
n’avez pas besoin d’installer le package du fournisseur.

Le package Microsoft.ApplicationInsights.Web est destiné à ASP.NET 4.x, et non à


ASP.NET Core.

Pour plus d’informations, consultez les ressources suivantes :

Vue d’ensemble d’Application Insights


Application Insights pour les applications ASP.NET Core : commencez ici si vous
souhaitez implémenter la gamme complète des données de télémétrie
d’Application Insights en même temps que la journalisation.
Journaux ApplicationInsightsLoggerProvider pour .NET Core ILogger : commencez
ici si vous souhaitez implémenter le fournisseur de journalisation sans le reste des
données de télémétrie Application Insights.
Adaptateurs de journalisation Application Insights
Tutoriel interactif Installer, configurer et initialiser le kit SDK Application Insights.

Fournisseurs de journalisation tiers


Frameworks de journalisation tiers qui sont pris en charge dans ASP.NET Core :

elmah.io (dépôt GitHub )


Gelf (Dépôt GitHub )
JSNLog (dépôt GitHub )
KissLog.net (référentiel GitHub )
Log4Net (dépôt GitHub )
NLog (dépôt GitHub )
PLogger (dépôt GitHub )
Sentry (dépôt GitHub )
Serilog (dépôt GitHub )
Stackdriver (Github repo )

Certains frameworks tiers prennent en charge la journalisation sémantique, également


appelée journalisation structurée .

L’utilisation d’un framework tiers est semblable à l’utilisation des fournisseurs intégrés :

1. Ajoutez un package NuGet à votre projet.


2. Appelez une méthode d’extension ILoggerFactory fournie par le framework de
journalisation.
Pour plus d’informations, consultez la documentation de chaque fournisseur. Les
fournisseurs de journalisation tiers ne sont pas pris en charge par Microsoft.

Aucune méthode d’enregistreur d’événements


asynchrone
La journalisation doit être suffisamment rapide par rapport au coût du code asynchrone
en matière de performances. Si un magasin de données de journalisation est lent,
n’écrivez pas de données directement dedans. Écrivez les messages de journal dans un
magasin rapide, puis déplacez-les vers le magasin lent. Par exemple, lorsque vous vous
connectez à SQL Server, ne le faites pas directement dans une méthode Log , puisque les
méthodes Log sont synchrones. Ajoutez plutôt de façon synchronisée des messages de
journal à une file d’attente en mémoire, puis configurez un traitement en arrière-plan
afin d’extraire les messages de la file d’attente et d’effectuer le travail asynchrone
d’envoi des données vers SQL Server. Pour plus d’informations, consultez Guidance on
how to log to a message queue for slow data stores (dotnet/AspNetCore.Docs
n° 11801) .

Modifier les niveaux de journalisation dans une


application en cours d’exécution
L’API de journalisation ne permet pas de modifier les niveaux de journalisation pendant
l’exécution d’une application. Toutefois, certains fournisseurs de configuration sont
capables de recharger la configuration, ce qui agit immédiatement sur la configuration
de la journalisation. Par exemple, File Configuration Provider recharge la configuration
de journalisation par défaut. Si la configuration est modifiée dans le code pendant
l’exécution d’une application, celle-ci peut appeler IConfigurationRoot.Reload pour
mettre à jour la configuration de journalisation de l’application.

ILogger et ILoggerFactory
Les interfaces et les implémentations ILogger<TCategoryName> et ILoggerFactory sont
incluses dans le SDK .NET Core. Elles sont également disponibles dans les packages
NuGet suivants :

Les interfaces se trouvent dans Microsoft.Extensions.Logging.Abstractions .


Les implémentations par défaut se trouvent dans Microsoft.Extensions.Logging .
Appliquer des règles de filtre de journaux dans
le code
Pour définir des règles de filtre de journaux, l’approche recommandée consiste à utiliser
Configuration.

L'exemple suivant montre comment enregistrer des règles de filtre dans le code :

C#

using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Logging.Debug;

var builder = WebApplication.CreateBuilder();


builder.Logging.AddFilter("System", LogLevel.Debug);
builder.Logging.AddFilter<DebugLoggerProvider>("Microsoft",
LogLevel.Information);
builder.Logging.AddFilter<ConsoleLoggerProvider>("Microsoft",
LogLevel.Trace);

logging.AddFilter("System", LogLevel.Debug) spécifie la catégorie System et le niveau

de journalisation Debug . Le filtre est appliqué à tous les fournisseurs, car aucun
fournisseur spécifique n’a été configuré.

AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Information) spécifie :

Le fournisseur de journalisation Debug .


Le niveau de journalisation Information et niveaux supérieurs.
Toutes les catégories commençant par "Microsoft" .

Journalisez automatiquement l’étendue avec


SpanId , TraceId , ParentId , Baggage et Tags .
Les bibliothèques de journalisation créent implicitement un objet d’étendue avec
SpanId , TraceId , ParentId , Baggage et Tags . Ce comportement est configuré via

ActivityTrackingOptions.

C#

var builder = WebApplication.CreateBuilder(args);

builder.Logging.AddSimpleConsole(options =>
{
options.IncludeScopes = true;
});

builder.Logging.Configure(options =>
{
options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
| ActivityTrackingOptions.TraceId
| ActivityTrackingOptions.ParentId
| ActivityTrackingOptions.Baggage
| ActivityTrackingOptions.Tags;
});
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Si l’en-tête de requête HTTP traceparent est défini, le ParentId dans l’étendue du


journal affiche le parent-id W3C issu de l’en-tête entrant traceparent , et le SpanId
dans l’étendue du journal affiche le parent-id mis à jour pour l’étape/l’étendue sortante
suivante. Pour plus d’informations, consultez Mutating the traceparent Field .

Créer un enregistreur d’événements


personnalisé
Pour créer un enregistreur d’événements personnalisé, consultez Implémenter un
fournisseur de journalisation personnalisé dans .NET.

Ressources supplémentaires
Derrière [LogProperties] et le nouveau générateur de source de journalisation de
télémétrie
Source Microsoft.Extensions.Logging sur GitHub
Affichez ou téléchargez un exemple de code (procédure de téléchargement).
Journalisation haute performance
Les bogues de journalisation doivent être créés dans le dépôt GitHub
dotnet/runtime .
Journalisation ASP.NET Core Blazor

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Journalisation HTTP dans ASP.NET Core
Article • 30/11/2023

La journalisation HTTP est un intergiciel qui journalise des informations sur les
requêtes HTTP entrantes et les réponses HTTP. La journalisation HTTP fournit les
journaux suivants :

Informations sur les requêtes HTTP


Propriétés communes
En-têtes
Corps
Informations de réponse HTTP

La journalisation HTTP peut :

Journaliser toutes les requêtes et les réponses ou uniquement les requêtes et


réponses qui répondent à certains critères.
Sélectionner les parties de la requête et de la réponse qui sont journalisées.
Vous autoriser à rédiger des informations sensibles à partir des journaux d’activité.

La journalisation HTTP peut réduire les performances d’une application, en particulier


lors de la journalisation des corps de requête et de réponse. Tenez compte de l’impact
sur les performances lorsque vous sélectionnez les champs à journaliser. Testez l’impact
des propriétés de journalisation sélectionnées sur les performances.

2 Avertissement

La journalisation HTTP peut potentiellement journaliser des informations


d’identification personnelle. Tenez compte des risques, et évitez de journaliser des
informations sensibles.

Activer la journalisation HTTP


La journalisation HTTP est activée en appelant AddHttpLogging et UseHttpLogging,
comme illustré dans l’exemple suivant :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(o => { });


var app = builder.Build();

app.UseHttpLogging();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();

app.MapGet("/", () => "Hello World!");

app.Run();

L’expression lambda vide dans l’exemple précédent d’appel AddHttpLogging ajoute


l’intergiciel avec la configuration par défaut. Par défaut, la journalisation HTTP journalise
les propriétés courantes telles que le chemin d’accès, le code d’état et les en-têtes des
requêtes et des réponses.

Ajoutez la ligne suivante au fichier appsettings.Development.json au niveau "LogLevel":


{ afin que les journaux HTTP soient affichés :

JSON

"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"

Avec la configuration par défaut, une requête et une réponse sont enregistrées sous la
forme d’une paire de messages similaire à l’exemple suivant :

Sortie

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
Request:
Protocol: HTTP/2
Method: GET
Scheme: https
PathBase:
Path: /
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,
*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Host: localhost:52941
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36
Edg/118.0.2088.61
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Upgrade-Insecure-Requests: [Redacted]
sec-ch-ua: [Redacted]
sec-ch-ua-mobile: [Redacted]
sec-ch-ua-platform: [Redacted]
sec-fetch-site: [Redacted]
sec-fetch-mode: [Redacted]
sec-fetch-user: [Redacted]
sec-fetch-dest: [Redacted]
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
Response:
StatusCode: 200
Content-Type: text/plain; charset=utf-8
Date: Tue, 24 Oct 2023 02:03:53 GMT
Server: Kestrel

Options de journalisation HTTP


Pour configurer des options globales pour l’intergiciel de journalisation HTTP, appelez-
AddHttpLogging dans Program.cs à l’aide de l’expression lambda pour configurer
HttpLoggingOptions.

C#

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging();

app.Use(async (context, next) =>


{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };
await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

7 Notes

Dans l’exemple précédent et les exemples suivants, UseHttpLogging est appelé


après UseStaticFiles , la journalisation HTTP n’est donc pas activée pour le fichier
statique. Pour activer la journalisation HTTP de fichier statique, appelez
UseHttpLogging avant UseStaticFiles .

LoggingFields

HttpLoggingOptions.LoggingFields est un indicateur d’énumération qui configure


certaines parties de la requête et de la réponse à journaliser.
HttpLoggingOptions.LoggingFields a la valeur par défaut RequestPropertiesAndHeaders

| ResponsePropertiesAndHeaders.

RequestHeaders et ResponseHeaders

RequestHeaders et ResponseHeaders sont des ensembles d’en-têtes HTTP journalisés.


Les valeurs d’en-tête sont journalisées uniquement pour les noms d’en-tête qui se
trouvent dans ces collections. Le code suivant ajoute sec-ch-ua à RequestHeaders, de
sorte que la valeur de l’en-tête sec-ch-ua est journalisée. Et il ajoute MyResponseHeader à
ResponseHeaders, de sorte que la valeur de l’en-tête MyResponseHeader est journalisée.
Si ces lignes sont supprimées, les valeurs de ces en-têtes sont [Redacted] .

C#

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging();

app.Use(async (context, next) =>


{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };

await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

MediaTypeOptions

MediaTypeOptions fournit la configuration permettant de sélectionner l’encodage à


utiliser pour un type de média spécifique.

C#

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
logging.CombineLogs = true;
});

var app = builder.Build();


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging();

app.Use(async (context, next) =>


{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };

await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Cette approche peut également être utilisée pour activer la journalisation des données
qui ne sont pas journalisées par défaut (par exemple, les données de formulaire, qui
peuvent avoir un type de média tel que application/x-www-form-urlencoded ou
multipart/form-data ).

Méthodes MediaTypeOptions

AddText
AddBinary
Clear

RequestBodyLogLimit et ResponseBodyLogLimit

RequestBodyLogLimit
ResponseBodyLogLimit

C#

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging();

app.Use(async (context, next) =>


{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };

await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

CombineLogs

Définir CombineLogs sur true configure l’intergiciel pour consolider tous ses journaux
activés pour une requête et une réponse dans un journal à la fin. Cela inclut la requête,
le corps de la requête, la réponse, le corps de la réponse et la durée.

C#

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("sec-ch-ua");
logging.ResponseHeaders.Add("MyResponseHeader");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
logging.CombineLogs = true;
});
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging();

app.Use(async (context, next) =>


{
context.Response.Headers["MyResponseHeader"] =
new string[] { "My Response Header Value" };

await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Configuration spécifique au point de


terminaison
Pour une configuration spécifique au point de terminaison dans des applications API
minimales, une méthode d’extension WithHttpLogging est disponible. L’exemple suivant
montre comment configurer la journalisation HTTP pour un point de terminaison :

C#

app.MapGet("/response", () => "Hello World! (logging response)")


.WithHttpLogging(HttpLoggingFields.ResponsePropertiesAndHeaders);

Pour la configuration spécifique au point de terminaison dans les applications qui


utilisent des contrôleurs, l’attribut [HttpLogging] est disponible. L’attribut peut
également être utilisé dans des applications API minimales, comme illustré dans
l’exemple suivant :

C#

app.MapGet("/duration", [HttpLogging(loggingFields:
HttpLoggingFields.Duration)]
() => "Hello World! (logging duration)");
IHttpLoggingInterceptor
IHttpLoggingInterceptor est l’interface d’un service qui peut être implémenté pour gérer
les rappels par requête et par réponse pour personnaliser les détails enregistrés. Tous les
paramètres de journal spécifiques au point de terminaison sont appliqués en premier et
peuvent ensuite être remplacés dans ces rappels. Une implémentation peut :

Inspecter une requête ou une réponse.


Activer ou désactiver un HttpLoggingFields quelconque.
Ajuster la quantité du corps de la requête ou de la réponse journalisée.
Ajouter des champs personnalisés aux journaux.

Inscrire une implémentation IHttpLoggingInterceptor en appelant


AddHttpLoggingInterceptor<T> dans Program.cs . Si plusieurs instances
IHttpLoggingInterceptor sont inscrites, elles sont exécutées dans l’ordre dans lequel

elles ont été enregistrées.

L'exemple suivant montre comment enregistrer une implémentation


IHttpLoggingInterceptor :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.Duration;
});
builder.Services.AddHttpLoggingInterceptor<SampleHttpLoggingInterceptor>();

L'exemple suivant est une implémentation IHttpLoggingInterceptor qui :

Inspecte la méthode de requête et désactive la journalisation des requêtes POST.


Pour les requêtes non POST :
Rédige le chemin de requête, les en-têtes de requête et les en-têtes de réponse.
Ajoute des champs personnalisés et des valeurs de champ aux journaux de
requête et de réponse.

C#

using Microsoft.AspNetCore.HttpLogging;

namespace HttpLoggingSample;

internal sealed class SampleHttpLoggingInterceptor : IHttpLoggingInterceptor


{
public ValueTask OnRequestAsync(HttpLoggingInterceptorContext
logContext)
{
if (logContext.HttpContext.Request.Method == "POST")
{
// Don't log anything if the request is a POST.
logContext.LoggingFields = HttpLoggingFields.None;
}

// Don't enrich if we're not going to log any part of the request.
if (!logContext.IsAnyEnabled(HttpLoggingFields.Request))
{
return default;
}

if (logContext.TryDisable(HttpLoggingFields.RequestPath))
{
RedactPath(logContext);
}

if (logContext.TryDisable(HttpLoggingFields.RequestHeaders))
{
RedactRequestHeaders(logContext);
}

EnrichRequest(logContext);

return default;
}

public ValueTask OnResponseAsync(HttpLoggingInterceptorContext


logContext)
{
// Don't enrich if we're not going to log any part of the response
if (!logContext.IsAnyEnabled(HttpLoggingFields.Response))
{
return default;
}

if (logContext.TryDisable(HttpLoggingFields.ResponseHeaders))
{
RedactResponseHeaders(logContext);
}

EnrichResponse(logContext);

return default;
}

private void RedactPath(HttpLoggingInterceptorContext logContext)


{
logContext.AddParameter(nameof(logContext.HttpContext.Request.Path),
"RedactedPath");
}
private void RedactRequestHeaders(HttpLoggingInterceptorContext
logContext)
{
foreach (var header in logContext.HttpContext.Request.Headers)
{
logContext.AddParameter(header.Key, "RedactedHeader");
}
}

private void EnrichRequest(HttpLoggingInterceptorContext logContext)


{
logContext.AddParameter("RequestEnrichment", "Stuff");
}

private void RedactResponseHeaders(HttpLoggingInterceptorContext


logContext)
{
foreach (var header in logContext.HttpContext.Response.Headers)
{
logContext.AddParameter(header.Key, "RedactedHeader");
}
}

private void EnrichResponse(HttpLoggingInterceptorContext logContext)


{
logContext.AddParameter("ResponseEnrichment", "Stuff");
}
}

Avec cet intercepteur, une requête POST ne génère aucun journal même si la
journalisation HTTP est configurée pour journaliser HttpLoggingFields.All . Une requête
GET génère des journaux similaires à l’exemple suivant :

Sortie

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
Request:
Path: RedactedPath
Accept: RedactedHeader
Host: RedactedHeader
User-Agent: RedactedHeader
Accept-Encoding: RedactedHeader
Accept-Language: RedactedHeader
Upgrade-Insecure-Requests: RedactedHeader
sec-ch-ua: RedactedHeader
sec-ch-ua-mobile: RedactedHeader
sec-ch-ua-platform: RedactedHeader
sec-fetch-site: RedactedHeader
sec-fetch-mode: RedactedHeader
sec-fetch-user: RedactedHeader
sec-fetch-dest: RedactedHeader
RequestEnrichment: Stuff
Protocol: HTTP/2
Method: GET
Scheme: https
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
Response:
Content-Type: RedactedHeader
MyResponseHeader: RedactedHeader
ResponseEnrichment: Stuff
StatusCode: 200
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[4]
ResponseBody: Hello World!
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[8]
Duration: 2.2778ms

Ordre de priorité de la configuration de


journalisation
La liste suivante montre l’ordre de priorité pour la configuration de la journalisation :

1. Configuration globale à partir de HttpLoggingOptions, définie en appelant


AddHttpLogging.
2. La configuration spécifique au point de terminaison à partir de l’attribut
[HttpLogging] ou de la méthode d’extension WithHttpLogging remplace la
configuration globale.
3. IHttpLoggingInterceptor est appelé avec les résultats et peut modifier davantage la
configuration par requête.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
W3CLogger dans ASP.NET Core
Article • 30/11/2023

W3CLogger est un middleware (intergiciel) qui écrit des fichiers journaux au format
normalisé du W3C . Les journaux contiennent des informations sur les requêtes HTTP
et les réponses HTTP. W3CLogger fournit des journaux relatifs aux éléments suivants :

Informations sur les requêtes HTTP


Propriétés communes
En-têtes
Informations de réponse HTTP
Métadonnées relatives à la paire requête/réponse (date/heure de début, temps
nécessaire)

W3CLogger est utile dans plusieurs scénarios pour :

Enregistrer des informations sur les requêtes entrantes et les réponses.


Filtrer les parties des requêtes et des réponses à journaliser.
Filtrer les en-têtes à journaliser.

W3CLogger peut réduire les performances d’une application. Tenez compte de l’impact
sur les performances au moment de la sélection des champs à journaliser. La réduction
des performances augmente à mesure que vous journalisez davantage de propriétés.
Testez l’impact des propriétés de journalisation sélectionnées sur les performances.

2 Avertissement

W3CLogger peut éventuellement journaliser des informations d’identification


personnelle (PII). Tenez compte des risques, et évitez de journaliser des
informations sensibles. Par défaut, les champs qui peuvent contenir des
informations d’identification personnelle ne sont pas journalisés.

Activer W3CLogger
Activez W3CLogger avec UseW3CLogging, qui ajoute le middleware W3CLogger :

C#

var app = builder.Build();

app.UseW3CLogging();
app.UseRouting();

Par défaut, W3CLogger journalise les propriétés courantes telles que le chemin, le code
d’état, la date, l’heure et le protocole. Toutes les informations relatives à une seule paire
requête/réponse sont écrites sur la même ligne.

#Version: 1.0
#Start-Date: 2021-09-29 22:18:28
#Fields: date time c-ip s-computername s-ip s-port cs-method cs-uri-stem cs-
uri-query sc-status time-taken cs-version cs-host cs(User-Agent) cs(Referer)
2021-09-29 22:18:28 ::1 DESKTOP-LH3TLTA ::1 5000 GET / - 200 59.9171
HTTP/1.1 localhost:5000 Mozilla/5.0+
(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+
(KHTML,+like+Gecko)+Chrome/93.0.4577.82+Safari/537.36 -
2021-09-29 22:18:28 ::1 DESKTOP-LH3TLTA ::1 5000 GET / - 200 0.1802 HTTP/1.1
localhost:5000 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+
(KHTML,+like+Gecko)+Chrome/93.0.4577.82+Safari/537.36 -
2021-09-29 22:18:30 ::1 DESKTOP-LH3TLTA ::1 5000 GET / - 200 0.0966 HTTP/1.1
localhost:5000 Mozilla/5.0+(Windows+NT+10.0;+WOW64)+AppleWebKit/537.36+
(KHTML,+like+Gecko)+Chrome/93.0.4577.82+Safari/537.36 -

Options de W3CLogger
Pour configurer le middleware W3CLogger, appelez AddW3CLogging dans Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddW3CLogging(logging =>
{
// Log all W3C fields
logging.LoggingFields = W3CLoggingFields.All;

logging.AdditionalRequestHeaders.Add("x-forwarded-for");
logging.AdditionalRequestHeaders.Add("x-client-ssl-protocol");
logging.FileSizeLimit = 5 * 1024 * 1024;
logging.RetainedFileCountLimit = 2;
logging.FileName = "MyLogFile";
logging.LogDirectory = @"C:\logs";
logging.FlushInterval = TimeSpan.FromSeconds(2);
});

LoggingFields
W3CLoggerOptions.LoggingFields est une énumération d’indicateurs de bit qui
configure des parties spécifiques de la requête et de la réponse à journaliser ainsi que
d’autres informations relatives à la connexion. Par défaut, LoggingFields inclut tous les
champs possibles à l’exception de UserName et Cookie . Pour obtenir la liste complète
des champs disponibles, consultez W3CLoggingFields.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Contrôles d’intégrité dans ASP.NET Core
Article • 11/01/2024

Par Glenn Condron et Juergen Gutsch

ASP.NET Core met à disposition un intergiciel de contrôle d’intégrité et des


bibliothèques afin de créer des rapports sur l’intégrité des composants d’une
infrastructure d’application.

Les contrôles d’intégrité sont exposés par une application comme des points de
terminaison HTTP. Les points de terminaison du contrôle d’intégrité peuvent être
configurés pour divers scénarios de surveillance en temps réel :

Les sondes d’intégrité peuvent être utilisées par les orchestrateurs de conteneurs
et les équilibreurs de charge afin de vérifier l’état d’une application. Par exemple,
un orchestrateur de conteneurs peut répondre à un résultat de non intégrité en
arrêtant le déploiement ou en redémarrant un conteneur. Face à une application
non saine, l’équilibreur de charge peut réagir en redirigeant le trafic vers une
instance saine.
L’utilisation de la mémoire, des disques et des autres ressources de serveur
physique peut être supervisée dans le cadre d’un contrôle d’intégrité.
Les contrôles d’intégrité peuvent tester les dépendances d’une application, telles
que les bases de données et les points de terminaison de service externes, dans le
but de vérifier leur disponibilité et leur bon fonctionnement.

Les contrôles d’intégrité sont généralement utilisés avec un service de surveillance


externe ou un orchestrateur de conteneurs pour vérifier l’état d’une application. Avant
d’ajouter des contrôles d’intégrité à une application, vous devez décider du système de
supervision à utiliser. Le système de supervision détermine les types de contrôles
d’intégrité qui doivent être créés ainsi que la configuration de leurs points de
terminaison.

Sondage d’intégrité de base


Pour de nombreuses applications, un sondage d’intégrité de base qui signale la
disponibilité d’une application pour le traitement des requêtes (liveness) suffit à
découvrir l’état de l’application.

La configuration de base enregistre les services de contrôle d’intégrité, puis appelle


l’intergiciel de contrôle d’intégrité pour répondre à un point de terminaison d’URL avec
une réponse sur l’intégrité. Par défaut, aucun contrôle d’intégrité n’est inscrit pour tester
les dépendances ou le sous-système. L’application est considérée comme saine si elle
est capable de répondre à l’URL de point de terminaison d’intégrité. L’enregistreur de
réponses par défaut écrit HealthStatus comme réponse en texte brut au client. Le
HealthStatus est HealthStatus.Healthy, HealthStatus.Degraded ou

HealthStatus.Unhealthy.

Pour inscrire les services de contrôle d’intégrité, utilisez AddHealthChecks dans


Program.cs . Créez un point de terminaison de contrôle d’intégrité en appelant

MapHealthChecks.

L’exemple suivant crée un point de terminaison de contrôle d’intégrité sur /healthz :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks();

var app = builder.Build();

app.MapHealthChecks("/healthz");

app.Run();

Docker HEALTHCHECK
Docker fournit une directive HEALTHCHECK intégrée qui peut être utilisée pour vérifier
l’état d’une application utilisant la configuration de contrôle d’intégrité de base :

Dockerfile

HEALTHCHECK CMD curl --fail http://localhost:5000/healthz || exit

L’exemple précédent utilise curl pour envoyer une requête HTTP au point de
terminaison de contrôle d’intégrité à /healthz . curl n’est pas inclus dans les images
conteneur Linux .NET, mais il peut être ajouté par l’installation le package requis dans le
Dockerfile. Les conteneurs qui utilisent des images basées sur Alpine Linux peuvent
utiliser le wget inclus à la place de curl .

Créer des contrôles d’intégrité


Les contrôles d’intégrité sont créés via l’implémentation de l’interface IHealthCheck. La
méthode CheckHealthAsync retourne un HealthCheckResult qui indique l’état d’intégrité
comme étant Healthy , Degraded ou Unhealthy . Le résultat est écrit sous forme de
réponse en texte brut avec un code d’état configurable. La configuration est décrite
dans la section Options de contrôle d’intégrité. HealthCheckResult peut également
retourner des paires clé-valeur facultatives.

L’exemple suivant montre la disposition d’un contrôle d’intégrité :

C#

public class SampleHealthCheck : IHealthCheck


{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken cancellationToken =
default)
{
var isHealthy = true;

// ...

if (isHealthy)
{
return Task.FromResult(
HealthCheckResult.Healthy("A healthy result."));
}

return Task.FromResult(
new HealthCheckResult(
context.Registration.FailureStatus, "An unhealthy
result."));
}
}

La logique du contrôle d’intégrité est placée dans la méthode CheckHealthAsync.


L’exemple précédent définit une variable factice, de isHealthy , à true . Si la valeur de
isHealthy est définie sur false , l’état HealthCheckRegistration.FailureStatus est

retourné.

Si CheckHealthAsync lève une exception durant la vérification, un nouvel état


HealthReportEntry est retourné avec son HealthReportEntry.Status défini sur
FailureStatus. Cet état est défini par AddCheck (consulter la section Enregistrer les
services de contrôle d’intégrité) et inclut l’exception interne qui a provoqué l’échec de la
vérification. Description est défini sur le message de l’exception.

Inscrire les services de contrôle d’intégrité


Pour enregistrer un service de contrôle d’intégrité, appelez AddCheck dans Program.cs :
C#

builder.Services.AddHealthChecks()
.AddCheck<SampleHealthCheck>("Sample");

La surcharge AddCheck de l’exemple suivant définit l’état d’échec (HealthStatus) de


manière à être signalé lorsque le contrôle d’intégrité signale un échec. Si l’état d’échec
est défini sur null (par défaut), HealthStatus.Unhealthy est signalé. Cette surcharge est
utile pour les créateurs de bibliothèque, dans les cas où l’état d’échec indiqué par la
bibliothèque est appliqué par l’application lorsqu’un échec est signalé par le contrôle
d’intégrité, si l’implémentation de ce dernier respecte le paramètre.

Les contrôles d’intégrité peuvent être filtrés à l’aide de balises. Les balises sont décrites
dans la section Filtrer les contrôles d’intégrité.

C#

builder.Services.AddHealthChecks()
.AddCheck<SampleHealthCheck>(
"Sample",
failureStatus: HealthStatus.Degraded,
tags: new[] { "sample" });

AddCheck peut également exécuter une fonction lambda. Dans l’exemple suivant, le
contrôle d’intégrité retourne toujours un résultat sain :

C#

builder.Services.AddHealthChecks()
.AddCheck("Sample", () => HealthCheckResult.Healthy("A healthy
result."));

Appelez AddTypeActivatedCheck pour faire passer des arguments à une implémentation


de contrôle d’intégrité. Dans l’exemple suivant, un contrôle d’intégrité activé par type
accepte un entier et une chaîne dans son constructeur :

C#

public class SampleHealthCheckWithArgs : IHealthCheck


{
private readonly int _arg1;
private readonly string _arg2;

public SampleHealthCheckWithArgs(int arg1, string arg2)


=> (_arg1, _arg2) = (arg1, arg2);
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken cancellationToken =
default)
{
// ...

return Task.FromResult(HealthCheckResult.Healthy("A healthy


result."));
}
}

Pour enregistrer le contrôle d’intégrité précédent, appelez AddTypeActivatedCheck avec


l’entier et la chaîne passés en tant qu’arguments :

C#

builder.Services.AddHealthChecks()
.AddTypeActivatedCheck<SampleHealthCheckWithArgs>(
"Sample",
failureStatus: HealthStatus.Degraded,
tags: new[] { "sample" },
args: new object[] { 1, "Arg" });

Utiliser le routage des contrôles d’intégrité


Dans Program.cs , appelez MapHealthChecks sur le générateur de points de terminaison
avec l’URL du point de terminaison ou le chemin relatif :

C#

app.MapHealthChecks("/healthz");

Exiger un hôte
Appelez RequireHost pour spécifier un ou plusieurs hôtes autorisés pour le point de
terminaison du contrôle d’intégrité. Les hôtes doivent être de type Unicode plutôt que
Punycode, et peuvent inclure un port. Lorsqu’aucune collection n’est fournie, tous les
hôtes sont acceptés :

C#

app.MapHealthChecks("/healthz")
.RequireHost("www.contoso.com:5001");
Pour limiter la réponse du point de terminaison du contrôle d’intégrité à un port
spécifique uniquement, spécifiez un port dans l’appel à RequireHost . Cette approche est
généralement utilisée dans les environnements de conteneur pour exposer un port aux
services de surveillance :

C#

app.MapHealthChecks("/healthz")
.RequireHost("*:5001");

2 Avertissement

L’API qui s’appuie sur l’en-tête d’hôte , comme HttpRequest.Host et


RequireHost, est soumise à une usurpation potentielle par les clients.

Pour éviter l’usurpation d’hôte ou de port, utilisez l’une des approches suivantes :

Utilisez HttpContext.Connection (ConnectionInfo.LocalPort) où les ports sont


vérifiés.
Utilisez le filtrage d’hôtes.

Pour empêcher les clients non autorisés d’usurper le port, appelez


RequireAuthorization :

C#

app.MapHealthChecks("/healthz")
.RequireHost("*:5001")
.RequireAuthorization();

Pour plus d’informations, consultez Correspondance de l’hôte dans les itinéraires avec
RequireHost.

Exiger une autorisation


Appelez RequireAuthorization pour exécuter l’intergiciel d’autorisation sur le point de
terminaison de la requête de contrôle d’intégrité. Une surcharge de
RequireAuthorization accepte une ou plusieurs stratégies d’autorisation. Lorsqu’aucune

stratégie n’est fournie, la stratégie d’autorisation par défaut est utilisée :

C#
app.MapHealthChecks("/healthz")
.RequireAuthorization();

Activer les requêtes d’origines différentes


Bien qu’il ne soit pas courant d’exécuter les contrôles d’intégrité manuellement à partir
d’un navigateur, l’intergiciel CORS peut être activé en appelant RequireCors sur les
points de terminaison du contrôle d’intégrité. La surcharge RequireCors accepte un
délégué du générateur de stratégies CORS ( CorsPolicyBuilder ) ou un nom de stratégie.
Pour plus d’informations, consultez Activer les requêtes cross-origin (CORS) dans
ASP.NET Core.

Options de contrôle d’intégrité


HealthCheckOptions permet de personnaliser le comportement du contrôle d’intégrité :

Filtrer les contrôles d’intégrité


Personnaliser le code d’état HTTP
Supprimer les en-têtes de cache
Personnaliser la sortie

Filtrer les contrôles d’intégrité


Par défaut, l’intergiciel de contrôle d’intégrité exécute tous les contrôles d’intégrité
enregistrés. Pour exécuter un sous-ensemble de contrôles d’intégrité, fournissez une
fonction qui retourne une valeur booléenne à l’option Predicate.

L’exemple suivant filtre les contrôles d’intégrité afin d’exécuter uniquement ceux qui
comportent la balise sample :

C#

app.MapHealthChecks("/healthz", new HealthCheckOptions


{
Predicate = healthCheck => healthCheck.Tags.Contains("sample")
});

Personnaliser le code d’état HTTP


Utilisez ResultStatusCodes pour personnaliser le mappage de l’état d’intégrité aux codes
d’état HTTP. Les affectations StatusCodes suivantes sont les valeurs par défaut utilisées
par le middleware. Modifiez les valeurs de code d’état selon vos besoins :

C#

app.MapHealthChecks("/healthz", new HealthCheckOptions


{
ResultStatusCodes =
{
[HealthStatus.Healthy] = StatusCodes.Status200OK,
[HealthStatus.Degraded] = StatusCodes.Status200OK,
[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
}
});

Supprimer les en-têtes de cache


AllowCachingResponses contrôle si l’intergiciel de contrôle d’intégrité ajoute des en-
têtes HTTP à une réponse de sonde pour empêcher la mise en cache de la réponse. Si la
valeur est false (par défaut), le middleware définit ou substitue les en-têtes Cache-
Control , Expires et Pragma afin d’empêcher la mise en cache de la réponse. Si la valeur

est true , l’intergiciel ne modifie pas les en-têtes de cache de la réponse :

C#

app.MapHealthChecks("/healthz", new HealthCheckOptions


{
AllowCachingResponses = true
});

Personnaliser la sortie
Pour personnaliser la sortie d’un rapport de contrôle d’intégrité, définissez la propriété
HealthCheckOptions.ResponseWriter sur un délégué qui écrit la réponse :

C#

app.MapHealthChecks("/healthz", new HealthCheckOptions


{
ResponseWriter = WriteResponse
});
Le délégué par défaut écrit une réponse minimale constituée de texte en clair, avec la
valeur de chaîne HealthReport.Status. Le délégué personnalisé suivant génère une
réponse JSON personnalisée à l’aide de System.Text.Json :

C#

private static Task WriteResponse(HttpContext context, HealthReport


healthReport)
{
context.Response.ContentType = "application/json; charset=utf-8";

var options = new JsonWriterOptions { Indented = true };

using var memoryStream = new MemoryStream();


using (var jsonWriter = new Utf8JsonWriter(memoryStream, options))
{
jsonWriter.WriteStartObject();
jsonWriter.WriteString("status", healthReport.Status.ToString());
jsonWriter.WriteStartObject("results");

foreach (var healthReportEntry in healthReport.Entries)


{
jsonWriter.WriteStartObject(healthReportEntry.Key);
jsonWriter.WriteString("status",
healthReportEntry.Value.Status.ToString());
jsonWriter.WriteString("description",
healthReportEntry.Value.Description);
jsonWriter.WriteStartObject("data");

foreach (var item in healthReportEntry.Value.Data)


{
jsonWriter.WritePropertyName(item.Key);

JsonSerializer.Serialize(jsonWriter, item.Value,
item.Value?.GetType() ?? typeof(object));
}

jsonWriter.WriteEndObject();
jsonWriter.WriteEndObject();
}

jsonWriter.WriteEndObject();
jsonWriter.WriteEndObject();
}

return context.Response.WriteAsync(
Encoding.UTF8.GetString(memoryStream.ToArray()));
}

L’API de contrôle d’intégrité ne fournit pas de prise en charge intégrée pour les formats
de retour JSON complexes, car le format est spécifique à votre choix de système de
surveillance. Personnalisez la réponse des exemples précédents selon vos besoins. Pour
plus d’informations sur la sérialisation JSON avec System.Text.Json , consultez Comment
sérialiser et désérialiser JSON dans .NET.

Sonde de base de données


Un contrôle d’intégrité peut spécifier une requête de base de données à exécuter en
tant que test booléen pour indiquer si la base de données répond normalement.

AspNetCore.Diagnostics.HealthChecks , une bibliothèque de contrôle d’intégrité pour


les applications ASP.NET Core, inclut un contrôle d’intégrité qui s’exécute sur une base
de données SQL Server. AspNetCore.Diagnostics.HealthChecks exécute une demande
SELECT 1 sur la base de données pour confirmer que la connexion à la base de données

est saine.

2 Avertissement

Lorsque vous vérifiez une connexion de base de données à l’aide d’une requête,
choisissez une requête qui est retournée rapidement. L’utilisation d’une requête
risque néanmoins d’entraîner une surcharge de la base de données et d’en
diminuer les performances. Dans la plupart des cas, il n’est pas nécessaire d’utiliser
une requête de test. Il suffit simplement d’établir une connexion à la base de
données. Si vous avez besoin d’exécuter une requête, choisissez une requête
SELECT simple, telle que SELECT 1 .

Pour utiliser ce contrôle d’intégrité de SQL Server, incluez une référence de package au
package AspNetCore.HealthChecks.SqlServer NuGet. L’exemple suivant enregistre le
contrôle d’intégrité de SQL Server :

C#

var conStr = builder.Configuration.GetConnectionString("DefaultConnection");


if (string.IsNullOrEmpty(conStr))
{
throw new InvalidOperationException(
"Could not find a connection string named
'DefaultConnection'.");
}
builder.Services.AddHealthChecks()
.AddSqlServer(conStr);

7 Notes
AspNetCore.Diagnostics.HealthChecks n’est pas géré ni pris en charge par
Microsoft.

Sondage DbContext Entity Framework Core


La vérification DbContext confirme que l’application peut communiquer avec la base de
données configurée pour un EF Core DbContext . La vérification DbContext est prise en
charge dans les applications qui :

Utilisent Entity Framework (EF) Core.


Vous devez inclure une référence de package dans le package
Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore NuGet.

AddDbContextCheck inscrit un contrôle d’intégrité pour un DbContext. Le DbContext est


fourni à la méthode en tant que TContext . Une surcharge est disponible pour configurer
l’état d’échec, les étiquettes et une requête de test personnalisée.

Par défaut :

DbContextHealthCheck appelle la méthode EF Core, CanConnectAsync . Vous pouvez

choisir quelle opération doit être exécutée lors du contrôle d’intégrité à l’aide des
surcharges de la méthode AddDbContextCheck .
Le nom du contrôle d’intégrité correspond à celui du type TContext .

L’exemple suivant enregistre un DbContext et un DbContextHealthCheck associé :

C#

builder.Services.AddDbContext<SampleDbContext>(options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddHealthChecks()
.AddDbContextCheck<SampleDbContext>();

Séparer les sondages probe readiness et probe


liveness
Dans certains scénarios d’hébergement, une paire de contrôles d’intégrité est utilisée
pour déterminer deux états de l’application :
La disponibilité indique si l’application s’exécute normalement, sans toutefois être
prête à recevoir des requêtes.
L’activité indique si une application a cessé de fonctionner et doit être redémarrée.

Examinez l’exemple suivant : une application doit télécharger un fichier de configuration


volumineux avant d’être prête à traiter les requêtes. Nous ne souhaitons pas que
l’application redémarre si le téléchargement initial échoue, car l’application peut
réessayer de télécharger le fichier plusieurs fois. Nous utilisons un probe liveness pour
décrire l’activité du processus. Aucune autre vérification n’est exécutée. Nous souhaitons
également empêcher l’envoi de requêtes à l’application avant la réussite du
téléchargement du fichier de configuration. Nous utilisons un probe readiness pour
indiquer un état « non prêt », et ce, jusqu’à ce que le téléchargement ait réussi et que
l’application soit prête à recevoir des requêtes.

La tâche en arrière-plan suivante simule un processus de démarrage qui prend environ


15 secondes. Une fois terminée, la tâche définit la propriété
StartupHealthCheck.StartupCompleted sur vraie :

C#

public class StartupBackgroundService : BackgroundService


{
private readonly StartupHealthCheck _healthCheck;

public StartupBackgroundService(StartupHealthCheck healthCheck)


=> _healthCheck = healthCheck;

protected override async Task ExecuteAsync(CancellationToken


stoppingToken)
{
// Simulate the effect of a long-running task.
await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken);

_healthCheck.StartupCompleted = true;
}
}

Le StartupHealthCheck signale l’achèvement de la tâche de démarrage de longue durée


et expose la propriété StartupCompleted qui est définie par le service en arrière-plan :

C#

public class StartupHealthCheck : IHealthCheck


{
private volatile bool _isReady;
public bool StartupCompleted
{
get => _isReady;
set => _isReady = value;
}

public Task<HealthCheckResult> CheckHealthAsync(


HealthCheckContext context, CancellationToken cancellationToken =
default)
{
if (StartupCompleted)
{
return Task.FromResult(HealthCheckResult.Healthy("The startup
task has completed."));
}

return Task.FromResult(HealthCheckResult.Unhealthy("That startup


task is still running."));
}
}

Le contrôle d’intégrité est inscrit auprès de AddCheck dans Program.cs en même temps
que le service hébergé. Étant donné que le service hébergé doit définir la propriété sur
le contrôle d’intégrité, le contrôle d’intégrité est également enregistré dans le conteneur
du service en tant que singleton :

C#

builder.Services.AddHostedService<StartupBackgroundService>();
builder.Services.AddSingleton<StartupHealthCheck>();

builder.Services.AddHealthChecks()
.AddCheck<StartupHealthCheck>(
"Startup",
tags: new[] { "ready" });

Pour créer deux points de terminaison de contrôle d’intégrité différents, appelez


MapHealthChecks deux fois :

C#

app.MapHealthChecks("/healthz/ready", new HealthCheckOptions


{
Predicate = healthCheck => healthCheck.Tags.Contains("ready")
});

app.MapHealthChecks("/healthz/live", new HealthCheckOptions


{
Predicate = _ => false
});

L’exemple précédent crée les points de terminaison de contrôle d’intégrité suivants :

/healthz/ready pour la vérification de la disponibilité. La vérification de la

disponibilité filtre les contrôles d’intégrité pour les éléments avec les balises ready .
/healthz/live pour la vérification de l’activité. La vérification de l’activité applique

un filtre pour exclure tous les contrôles d’intégrité en retournant false dans le
délégué HealthCheckOptions.Predicate. Pour plus d’informations sur le filtrage des
contrôles d’intégrité, consultez la section Filtrer les contrôles d’intégrité dans cet
article.

Avant l’achèvement de la tâche de démarrage, le point de terminaison /healthz/ready


signale un état Unhealthy . Une fois la tâche de démarrage achevée, ce point de
terminaison signale un état Healthy . Le point de terminaison /healthz/live exclut tous
les contrôles et signale un état Healthy pour tous les appels.

Exemple Kubernetes
Dans un environnement tel que Kubernetes , il peut être utile d’utiliser séparément le
test permettant de savoir si la l’application est prête et celui visant à savoir si
l’application est active. Dans Kubernetes, une application peut devoir exécuter une tâche
de démarrage de longue durée avant d’accepter des requêtes, telle qu’un test de la
disponibilité de la base de données sous-jacente. L’utilisation séparée des deux tests
permet à l’orchestrateur de faire la distinction entre une application qui fonctionne mais
qui n’est pas encore prête, et une application qui n’a pas pu démarrer. Pour plus
d’informations sur les sondages probe readiness et probe liveness dans Kubernetes,
consultez Configure Liveness and Readiness Probes dans la documentation
Kubernetes.

L’exemple suivant montre une configuration probe readiness Kubernetes :

YAML

spec:
template:
spec:
readinessProbe:
# an http probe
httpGet:
path: /healthz/ready
port: 80
# length of time to wait for a pod to initialize
# after pod startup, before applying health checking
initialDelaySeconds: 30
timeoutSeconds: 1
ports:
- containerPort: 80

Distribuer une bibliothèque de contrôle


d’intégrité
Pour distribuer une bibliothèque comme un contrôle d’intégrité :

1. Écrivez un contrôle d’intégrité qui implémente l’interface IHealthCheck comme une


classe autonome. La classe peut utiliser une injection de dépendance, une
activation de type et des options nommées pour accéder aux données de
configuration.

2. Écrivez une méthode d’extension avec des paramètres que l’application


consommatrice appelle dans sa méthode Program.cs . Envisagez l’exemple de
contrôle d’intégrité suivant, qui accepte arg1 et arg2 en tant que paramètres de
constructeur :

C#

public SampleHealthCheckWithArgs(int arg1, string arg2)


=> (_arg1, _arg2) = (arg1, arg2);

La signature précédente indique que le contrôle d’intégrité nécessite des données


personnalisées pour traiter la logique du probe de contrôle d’intégrité. Les
données sont fournies au délégué qui est utilisé pour créer l’instance de contrôle
d’intégrité lorsque le contrôle d’intégrité est inscrit avec une méthode d’extension.
Dans l’exemple qui suit, l’appelant spécifie les éléments suivants :

arg1 : point de données entier du contrôle d’intégrité.


arg2 : argument de chaîne pour le contrôle d’intégrité.

name : nom de contrôle d’intégrité facultatif. Une valeur par défaut est utilisée

pour null .
failureStatus : HealthStatus facultatif, signalé pour un état d’échec. Si null ,

HealthStatus.Unhealthy est utilisé.


tags : collection IEnumerable<string> facultative de balises.

C#
public static class SampleHealthCheckBuilderExtensions
{
private const string DefaultName = "Sample";

public static IHealthChecksBuilder AddSampleHealthCheck(


this IHealthChecksBuilder healthChecksBuilder,
int arg1,
string arg2,
string? name = null,
HealthStatus? failureStatus = null,
IEnumerable<string>? tags = default)
{
return healthChecksBuilder.Add(
new HealthCheckRegistration(
name ?? DefaultName,
_ => new SampleHealthCheckWithArgs(arg1, arg2),
failureStatus,
tags));
}
}

Serveur de publication des contrôles d’intégrité


Quand un IHealthCheckPublisher est ajouté au conteneur du service, le système de
contrôle d’intégrité exécute régulièrement vos contrôles d’intégrité et appelle
PublishAsync avec le résultat. Ce processus est utile dans un scénario impliquant un
système de surveillance d’intégrité basé sur les envois (push) qui nécessite que chaque
processus appelle le système de surveillance régulièrement afin de déterminer l’état
d’intégrité.

HealthCheckPublisherOptions vous autorise à définir les éléments suivants :

Delay : retard initial appliqué après le démarrage de l’application avant d’exécuter


les instances IHealthCheckPublisher. Le retard est appliqué une seule fois au
démarrage et ne s’applique pas aux itérations ultérieures. La valeur par défaut est
de cinq secondes.
Period : période d’exécution de IHealthCheckPublisher. La valeur par défaut est de
30 secondes.
Predicate : si Predicate est null (valeur par défaut), le service de publication du
contrôle d’intégrité exécute tous les contrôles d’intégrité enregistrés. Pour exécuter
un sous-ensemble de contrôles d’intégrité, fournissez une fonction qui filtre
l’ensemble de vérifications. Le prédicat est évalué à chaque période.
Timeout : délai d’attente pour l’exécution des contrôles d’intégrité pour toutes les
instances IHealthCheckPublisher. Utilisez InfiniteTimeSpan pour une exécution sans
délai d’attente. La valeur par défaut est de 30 secondes.
L’exemple suivant montre la disposition d’un éditeur d’intégrité :

C#

public class SampleHealthCheckPublisher : IHealthCheckPublisher


{
public Task PublishAsync(HealthReport report, CancellationToken
cancellationToken)
{
if (report.Status == HealthStatus.Healthy)
{
// ...
}
else
{
// ...
}

return Task.CompletedTask;
}
}

La classe HealthCheckPublisherOptions fournit les propriétés de configuration du


comportement de l’éditeur de contrôle d’intégrité.

L’exemple suivant enregistre un éditeur de contrôle d’intégrité en tant que singleton et


configure HealthCheckPublisherOptions :

C#

builder.Services.Configure<HealthCheckPublisherOptions>(options =>
{
options.Delay = TimeSpan.FromSeconds(2);
options.Predicate = healthCheck => healthCheck.Tags.Contains("sample");
});

builder.Services.AddSingleton<IHealthCheckPublisher,
SampleHealthCheckPublisher>();

AspNetCore.Diagnostics.HealthChecks :

inclut des éditeurs de plusieurs systèmes, y compris Application Insights.


N’est pas géré ni pris en charge par Microsoft.

Vérifications d’intégrité individuelles


Delay et Period peuvent être définis sur chaque HealthCheckRegistration
individuellement. Cela est utile lorsque vous souhaitez exécuter des vérifications
d’intégrité à une fréquence différente de la période définie dans
HealthCheckPublisherOptions.

Le code suivant définit les Delay et Period pour SampleHealthCheck1 :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks()
.Add(new HealthCheckRegistration(
name: "SampleHealthCheck1",
instance: new SampleHealthCheck(),
failureStatus: null,
tags: null,
timeout: default)
{
Delay = TimeSpan.FromSeconds(40),
Period = TimeSpan.FromSeconds(30)
});

var app = builder.Build();

app.MapHealthChecks("/healthz");

app.Run();

Injection de dépendances et contrôles


d’intégrité
Il est possible d’utiliser l’injection de dépendances pour consommer une instance d’un
Type spécifique dans une classe de contrôle d’intégrité. L’injection de dépendances peut

être utile pour injecter des options ou une configuration globale dans un contrôle
d’intégrité. L’utilisation de l’injection de dépendances n’est pas un scénario courant de la
configuration des contrôles d’intégrité. En règle générale, chaque contrôle d’intégrité
est assez spécifique au test en question et est configuré à l’aide des méthodes
d’extension IHealthChecksBuilder .

L’exemple suivant présente un exemple de contrôle d’intégrité qui récupère un objet de


configuration via l’injection de dépendances :

C#

public class SampleHealthCheckWithDI : IHealthCheck


{
private readonly SampleHealthCheckWithDiConfig _config;
public SampleHealthCheckWithDI(SampleHealthCheckWithDiConfig config)
=> _config = config;

public Task<HealthCheckResult> CheckHealthAsync(


HealthCheckContext context, CancellationToken cancellationToken =
default)
{
var isHealthy = true;

// use _config ...

if (isHealthy)
{
return Task.FromResult(
HealthCheckResult.Healthy("A healthy result."));
}

return Task.FromResult(
new HealthCheckResult(
context.Registration.FailureStatus, "An unhealthy
result."));
}
}

Le SampleHealthCheckWithDiConfig et le contrôle d’intégrité doivent être ajoutés au


conteneur de service :

C#

builder.Services.AddSingleton<SampleHealthCheckWithDiConfig>(new
SampleHealthCheckWithDiConfig
{
BaseUriToCheck = new Uri("https://sample.contoso.com/api/")
});
builder.Services.AddHealthChecks()
.AddCheck<SampleHealthCheckWithDI>(
"With Dependency Injection",
tags: new[] { "inject" });

UseHealthChecks et MapHealthChecks
Il existe deux façons de rendre les contrôles d’intégrité accessibles aux appelants :

UseHealthChecks inscrit l’intergiciel pour gérer les requêtes de contrôle d’intégrité


dans le pipeline d’intergiciels.
MapHealthChecks inscrit un point de terminaison de contrôle d’intégrité. Le point
de terminaison est mis en correspondance et exécuté avec d’autres points de
terminaison dans l’application.

Utiliser MapHealthChecks plutôt que UseHealthChecks permet d’utiliser un intergiciel


prenant en compte les points de terminaison, comme l’autorisation, et d’avoir un
contrôle plus affiné sur la stratégie de correspondance. Utiliser UseHealthChecks plutôt
que MapHealthChecks permet principalement de déterminer exactement où les contrôles
d’intégrité s’exécutent dans le pipeline d’intergiciel.

UseHealthChecks:

Met fin au pipeline quand une requête correspond au point de terminaison de


contrôle d’intégrité. Un court-circuit est souvent souhaitable, car il évite le travail
inutile, tel que la journalisation et d’autres intergiciels.
Sert principalement à configurer l’intergiciel de contrôle d’intégrité dans le
pipeline.
Peut correspondre à n’importe quel chemin sur un port avec une valeur null ou
un élément PathString vide. Autorise l’exécution d’un contrôle d’intégrité sur
n’importe quelle requête adressée au port spécifié.
Code source

MapHealthChecks autorise :

Fin du pipeline quand une requête correspond au point de terminaison de


contrôle d’intégrité, en appelant ShortCircuit. Par exemple,
app.MapHealthChecks("/healthz").ShortCircuit(); Pour plus d’informations,

consultez Court-circuiter l’intergiciel après le routage.


Le mappage d’itinéraires ou de points de terminaison spécifiques pour les
contrôles d’intégrité.
La personnalisation de l’URL ou du chemin où le point de terminaison de contrôle
d’intégrité est accessible.
Le mappage de plusieurs points de terminaison de contrôle d’intégrité avec
différents itinéraires ou configurations. La prise en charge de plusieurs points de
terminaison :
Active des points de terminaison distincts pour différents types de contrôles
d’intégrité ou de composants.
Permet d’établir la distinction entre les différents aspects de l’intégrité de
l’application ou d’appliquer des configurations spécifiques à des sous-
ensembles de contrôles d’intégrité.
Code source

Ressources supplémentaires
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

7 Notes

Cet article a été partiellement créé avec l’aide de l’intelligence artificielle. Avant la
publication, un auteur a revu et révisé le contenu, si nécessaire. Voir Nos principes
d’utilisation du contenu généré par l’IA dans Microsoft Learn .

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Métriques ASP.NET Core
Article • 01/02/2024

Les mesures sont des mesures numériques rapportées au fil du temps. Elles sont
généralement utilisées pour surveiller l’intégrité d’une application et générer des alertes.
Par exemple, un service web peut suivre le nombre de :

Demandes reçues par seconde.


Millisecondes avant la réponse.
Les réponses ont envoyé une erreur.

Ces métriques peuvent être communiquées à un système de surveillance à intervalles


réguliers. Les tableaux de bord peuvent être configurés pour afficher les métriques et les
alertes créées pour notifier les utilisateurs des problèmes. Si le service web est prévu
pour répondre aux requêtes dans un délai de 400 ms et commence à répondre en
600 ms, le système de surveillance peut informer le personnel chargé des opérations
que la réponse de l’application est plus lente que prévu.

 Conseil

Consultez Métriques ASP.NET Core pour obtenir la liste complète de tous les
instruments avec leurs attributs.

Utilisation des métriques


L’utilisation des métriques dans une application .NET se décompose en deux parties :

Instrumentation : le code des bibliothèques .NET prend des mesures et les associe
à un nom de métrique. .NET et ASP.NET Core comptent de nombreuses métriques
intégrées.
Collecte : une applications .NET configure les mesures nommées qui doivent être
transmises à partir de l’application à des fins de stockage et d’analyse externes.
Certains outils peuvent effectuer une configuration en dehors de l’application à
l’aide de fichiers de configuration ou d’un outil d’IU.

Le code instrumenté peut enregistrer des mesures numériques, mais les mesures
doivent être agrégées, transmises et stockées pour créer des métriques utiles à la
surveillance. Le processus d’agrégation, de transmission et de stockage des données est
appelé collecte. Ce tutoriel présente plusieurs exemples de collecte de métriques :
Remplissage des métriques dans Grafana avec OpenTelemetry et
Prometheus .
Affichage des métriques en temps réel avec dotnet-counters

Les mesures peuvent également être associées à des paires clé-valeur appelées balises
qui permettent de classer les données à des fins d’analyse. Pour plus d’informations,
consultez la section Métriques multidimensionnelles.

Créer l’application de démarrage


Créez une application ASP.NET Core avec la commande suivante :

CLI .NET

dotnet new web -o WebMetric


cd WebMetric
dotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCore --prerelease
dotnet add package OpenTelemetry.Extensions.Hosting

Remplacez le contenu de Program.cs par le code qui suit :

C#

using OpenTelemetry.Metrics;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddOpenTelemetry()
.WithMetrics(builder =>
{
builder.AddPrometheusExporter();

builder.AddMeter("Microsoft.AspNetCore.Hosting",
"Microsoft.AspNetCore.Server.Kestrel");
builder.AddView("http.server.request.duration",
new ExplicitBucketHistogramConfiguration
{
Boundaries = new double[] { 0, 0.005, 0.01, 0.025, 0.05,
0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10 }
});
});
var app = builder.Build();

app.MapPrometheusScrapingEndpoint();

app.MapGet("/", () => "Hello OpenTelemetry! ticks:"


+ DateTime.Now.Ticks.ToString()[^3..]);

app.Run();
Afficher les métriques avec dotnet-counters
dotnet-counters est un outil en ligne de commande qui peut afficher les métriques
actives pour les applications .NET Core à la demande. Il ne nécessite pas d’installation,
ce qui le rend utile pour les enquêtes ad hoc ou pour vérifier que l’instrumentation des
métriques fonctionne. Il fonctionne avec les API basées sur System.Diagnostics.Metrics
et EventCounters.

Si l’outil dotnet-counters n’est pas installé, exécutez la commande suivante :

CLI .NET

dotnet tool update -g dotnet-counters

Pendant l’exécution de l’application test, lancez dotnet-counters. La commande suivante


montre un exemple de dotnet-counters supervisant toutes les métriques du compteur
Microsoft.AspNetCore.Hosting.

CLI .NET

dotnet-counters monitor -n WebMetric --counters Microsoft.AspNetCore.Hosting

Une sortie similaire à la suivante s’affiche à l’écran :

CLI .NET

Press p to pause, r to resume, q to quit.


Status: Running

[Microsoft.AspNetCore.Hosting]
http-server-current-requests
host=localhost,method=GET,port=5045,scheme=http 0
http-server-request-duration (s)
host=localhost,method=GET,port=5045,protocol=HTTP/1.1,ro
0.001
host=localhost,method=GET,port=5045,protocol=HTTP/1.1,ro
0.001
host=localhost,method=GET,port=5045,protocol=HTTP/1.1,ro
0.001
host=localhost,method=GET,port=5045,protocol=HTTP/1.1,ro 0
host=localhost,method=GET,port=5045,protocol=HTTP/1.1,ro 0
host=localhost,method=GET,port=5045,protocol=HTTP/1.1,ro 0

Pour plus d’informations, consultez compteurs dotnet.


Enrichir l’indicateur de performance de requête
ASP.NET Core
ASP.NET Core possède de nombreux indicateurs de performance intégrés. L’indicateur
de performance http.server.request.duration :

Enregistre la durée des requêtes HTTP sur le serveur.


Capture les informations de requête dans des balises, telles que l’itinéraire
correspondant et le code d’état de la réponse.

L’indicateur de performance http.server.request.duration prend en charge


l'enrichissement des balises à l'aide de IHttpMetricsTagsFeature. L'enrichissement se
produit lorsqu'une bibliothèque ou une application ajoute ses propres balises à un
indicateur de performance. Ceci est utile si une application souhaite ajouter une
catégorisation personnalisée aux tableaux de bord ou aux alertes créées avec des
indicateurs de performance.

C#

using Microsoft.AspNetCore.Http.Features;

var builder = WebApplication.CreateBuilder();


var app = builder.Build();

app.Use(async (context, next) =>


{
var tagsFeature = context.Features.Get<IHttpMetricsTagsFeature>();
if (tagsFeature != null)
{
var source = context.Request.Query["utm_medium"].ToString() switch
{
"" => "none",
"social" => "social",
"email" => "email",
"organic" => "organic",
_ => "other"
};
tagsFeature.Tags.Add(new KeyValuePair<string, object?>("mkt_medium",
source));
}

await next.Invoke();
});

app.MapGet("/", () => "Hello World!");

app.Run();
L’exemple suivant :

Ajoute un middleware pour enrichir l’indicateur de performance de requête


ASP.NET Core.
Obtient la IHttpMetricsTagsFeature de la HttpContext . La fonctionnalité n'est
présente sur le contexte que si quelqu'un écoute l’indicateur de performance.
Vérifiez IHttpMetricsTagsFeature n’est pas null avant de l’utiliser.
Ajoute une balise personnalisée contenant la source marketing de la requête à
l’indicateur de performance http.server.request.duration.
La balise a un nom mkt_medium et une valeur basés sur la valeur de la chaîne de
requête utm_medium . La valeur utm_medium est résolue selon une plage de
valeurs connue.
La balise permet de classer les demandes par type de support marketing, ce qui
peut être utile lors de l'analyse du trafic des applications Web.

7 Notes

Suivez les bonnes pratiques en matière des indicateurs de performance


multidimensionnelles lors de l'enrichissement avec des balises personnalisées. Un
trop grand nombre de balises ou des balises avec une plage non limitée entraînent
une grande combinaison de balises. Les outils de collecte ont une limite quant au
nombre de combinaisons qu'ils prennent en charge pour un compteur et peuvent
commencer à filtrer les résultats pour éviter une utilisation excessive de la mémoire.

Créer des métriques personnalisées


Les indicateurs de performance sont créées à l'aide d'API dans l'espace de noms
System.Diagnostics.Metrics. Pour plus d’informations sur la création de métriques
personnalisées, consultez la section Création de métriques personnalisées.

Création d’indicateurs de performance dans les


applications ASP.NET Core avec IMeterFactory
Nous avons recommandé de créer des instances Meter dans les applications ASP.NET
Core avec IMeterFactory.

ASP.NET Core s’enregistre IMeterFactory par défaut dans l’injection de dépendances


(DI). L'usine de compteurs intègre les indicateurs de performance à DI, ce qui facilite
l'isolement et la collecte des métriques. IMeterFactory est particulièrement utile pour
les tests. Il permet d'exécuter plusieurs tests côte à côte et de collecter uniquement les
valeurs d’indicateurs de performance enregistrés dans un test.

Pour utiliser IMeterFactory dans une application, créez un type qui servira
IMeterFactory à créer les métriques personnalisées de l'application :

C#

public class ContosoMetrics


{
private readonly Counter<int> _productSoldCounter;

public ContosoMetrics(IMeterFactory meterFactory)


{
var meter = meterFactory.Create("Contoso.Web");
_productSoldCounter = meter.CreateCounter<int>
("contoso.product.sold");
}

public void ProductSold(string productName, int quantity)


{
_productSoldCounter.Add(quantity,
new KeyValuePair<string, object?>("contoso.product.name",
productName));
}
}

Enregistrez le type d’indicateur de performance avec DI dans Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddSingleton<ContosoMetrics>();

Injectez le type d’indicateur de performance et enregistrez les valeurs si nécessaire.


Étant donné que le type d’indicateur de performance est enregistré dans DI, il peut être
utilisé avec des contrôleurs MVC, des API minimales ou tout autre type créé par DI :

C#

app.MapPost("/complete-sale", (SaleModel model, ContosoMetrics metrics) =>


{
// ... business logic such as saving the sale to a database ...

metrics.ProductSold(model.ProductName, model.QuantitySold);
});
Pour surveiller le compteur « Contoso.Web », utilisez la commande dotnet-counters
suivante.

CLI .NET

dotnet-counters monitor -n WebMetric --counters Contoso.Web

Une sortie similaire à la suivante s’affiche à l’écran :

CLI .NET

Press p to pause, r to resume, q to quit.


Status: Running

[Contoso.Web]
contoso.product.sold (Count / 1 sec)
contoso.product.name=Eggs 12
contoso.product.name=Milk 0

Afficher les métriques dans Grafana avec


OpenTelemetry et Prometheus

Vue d'ensemble
OpenTelemetry :

Il s’agit d’un projet open source indépendant du fournisseur soutenu par la Cloud
Native Computing Foundation .
Standardise la génération et la collecte des données de télémétrie pour les
logiciels natifs cloud.
Fonctionne avec .NET à l’aide des API de métrique .NET.
Est approuvé par Azure Monitor et de nombreux fournisseurs APM.

Ce tutoriel montre l’une des intégrations disponibles pour les métriques OpenTelemetry
à l’aide des projets OSS Prometheus et Grafana . Flux de données des métriques :

1. Les API de métrique ASP.NET Core enregistrent les mesures de l’exemple


d’application.

2. La bibliothèque OpenTelemetry .NET en cours d’exécution dans l’application


agrège les mesures.
3. La bibliothèque d’exportation (« Exporter ») Prometheus rend les données
agrégées disponibles via un point de terminaison de métriques HTTP. « Exporter »
est le nom donné par OpenTelemetry aux bibliothèques qui transmettent les
données de télémétrie aux back-ends spécifiques au fournisseur.

4. Un serveur Prometheus :

Interroge le point de terminaison des métriques


Lit les données
Stocke les données dans une base de données pour une persistance à long
terme. Prometheus fait référence au processus de lecture et de stockage de
données sous le nom de scraping de point de terminaison.
Peut s’exécuter sur une autre machine

5. Le serveur Grafana :

Interroge les données stockées dans Prometheus et les affiche sur un tableau
de bord de supervision web.
Peut s’exécuter sur une autre machine.

Afficher les métriques de l’exemple d’application


Naviguez jusqu’à l’exemple d’application. Le navigateur affiche Hello OpenTelemetry!
ticks:<3digits> où 3digits sont les 3 derniers chiffres du DateTime.Ticks actuel.

Ajoutez /metrics à l’URL pour afficher le point de terminaison des métriques. Le


navigateur affiche les métriques en cours de collection :

Installer et configurer Prometheus


Suivez les Premières étapes de Prometheus pour configurer un serveur Prometheus et
vérifier qu’il fonctionne.

Modifiez le fichier de configuration prometheus.yml afin que Prometheus scrape le point


de terminaison des métriques que l’exemple d’application expose. Ajoutez le texte en
surbrillance suivant dans la section scrape_configs :

YAML

# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds.
Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is
every 1 minute.
# scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093

# Load rules once and periodically evaluate them according to the global
'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:


# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries
scraped from this config.
- job_name: "prometheus"

# metrics_path defaults to '/metrics'


# scheme defaults to 'http'.

static_configs:
- targets: ["localhost:9090"]

- job_name: 'MyASPNETApp'
scrape_interval: 5s # Poll every 5 seconds for a more responsive demo.
static_configs:
- targets: ["localhost:5045"] ## Enter the HTTP port number of the
demo app.

Dans le YAML mis en surbrillance précédent, remplacez 5045 par le numéro de port sur
lequel l’exemple d’application s’exécute.
Démarrer Prometheus
1. Rechargez la configuration ou redémarrez le serveur Prometheus.
2. Vérifiez qu’OpenTelemetryTest est dans l’état UP sur la page État>Cibles du portail
web Prometheus.

Sélectionnez l’icône Ouvrir l’explorateur de métriques pour afficher les métriques


disponibles :
Saisissez une catégorie de compteurs, comme http_ dans la zone d’entrée Expression
pour afficher les métriques disponibles :

Vous pouvez également saisir une catégorie de compteurs, comme kestrel dans la
zone d’entrée Expression pour afficher les métriques disponibles :
Afficher les métriques sur un tableau de bord Grafana
Suivez les instructions d’installation pour installer Grafana et la connecter à une
source de données Prometheus.

Suivez la Création d’un graphique Prometheus . Vous pouvez également


télécharger des tableaux de bord prédéfinis pour les métriques .NET sur les
tableaux de bord d’équipe .NET @ grafana.com . Le fichier JSON de tableau de
bord téléchargé peut être importé dans Grafana .
Métriques de test dans des applications
ASP.NET Core
Le test des métriques est possible dans des applications ASP.NET Core. Une façon de
procéder est de collecter et d’affirmer des valeurs de métriques dans les tests
d’intégration ASP.NET Core à l’aide de MetricCollector<T>.

C#

public class BasicTests : IClassFixture<WebApplicationFactory<Program>>


{
private readonly WebApplicationFactory<Program> _factory;
public BasicTests(WebApplicationFactory<Program> factory) => _factory =
factory;

[Fact]
public async Task Get_RequestCounterIncreased()
{
// Arrange
var client = _factory.CreateClient();
var meterFactory =
_factory.Services.GetRequiredService<IMeterFactory>();
var collector = new MetricCollector<double>(meterFactory,
"Microsoft.AspNetCore.Hosting", "http.server.request.duration");

// Act
var response = await client.GetAsync("/");

// Assert
Assert.Contains("Hello OpenTelemetry!", await
response.Content.ReadAsStringAsync());

await collector.WaitForMeasurementsAsync(minCount:
1).WaitAsync(TimeSpan.FromSeconds(5));
Assert.Collection(collector.GetMeasurementSnapshot(),
measurement =>
{
Assert.Equal("http", measurement.Tags["url.scheme"]);
Assert.Equal("GET",
measurement.Tags["http.request.method"]);
Assert.Equal("/", measurement.Tags["http.route"]);
});
}
}

Le test de procédure :

Démarre une application web en mémoire avec


WebApplicationFactory<TEntryPoint>. Dans l’argument générique de la fabrique,
Program spécifie l’application web.

Collecte des valeurs de métriques avec MetricCollector<T>.


Demande une référence de package à Microsoft.Extensions.Telemetry.Testing .
Le fichier MetricCollector<T> est créé à l’aide du IMeterFactory de l’application
web. Cela permet au collecteur de ne signaler que des valeurs de métriques
enregistrées par le test.
Comprend le nom du compteur Microsoft.AspNetCore.Hosting , tout comme le
nom du compteur http.server.request.duration à collecter.
Effectue une requête HTTP vers l’application web.
Fait valoir le test à l’aide des résultats du collecteur de métriques.

Métriques et compteurs ASP.NET Core


Consultez Métriques ASP.NET Core pour obtenir la liste des compteurs ASP.NET Core.

6 Collaborer avec nous sur


GitHub
La source de ce contenu se
trouve sur GitHub, où vous
pouvez également créer et
examiner les problèmes et les
demandes de tirage. Pour plus
d’informations, consultez notre
guide du contributeur.
Commentaires sur ASP.NET
Core
ASP.NET Core est un projet open
source. Sélectionnez un lien pour
fournir des commentaires :

 Ouvrir un problème de
documentation

 Indiquer des commentaires sur


le produit
Métriques ASP.NET Core
Article • 06/02/2024

Cet article décrit les indicateurs de performance intégrées pour ASP.NET Core produites à l’aide de l’API
System.Diagnostics.Metrics. Pour une liste des indicateurs de performance basées sur l'ancienne API
EventCounters, voir ici.

 Conseil

Pour plus d’informations sur la collecte, le rapport, l’enrichissement et le test des métriques ASP.NET
Core, consultez Utilisation des métriques ASP.NET Core.

Microsoft.AspNetCore.Hosting
Les métriques Microsoft.AspNetCore.Hosting donnent des informations générales sur les requêtes HTTP
reçues par ASP.NET Core :

http.server.request.duration
http.server.active_requests

Métrique : http.server.request.duration

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

http.server.request.duration Histogramme s Mesure la durée des requêtes HTTP


entrantes.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

http.route string L'itinéraire {controller}/{action}/{id?} Si c'est


correspondant. disponible.

error.type string Décrit une classe timeout ; Si la


d'erreur avec laquelle name_resolution_error ; 500 requête
l'opération s'est s'est
terminée. terminée
par une
erreur.

http.request.method string Méthode de requête GET ; POST ; HEAD Toujours


HTTP.

http.response.status_code int Code de statut de la 200 Si on en a


réponse HTTP . envoyé un.
Attribut Type Description Exemples Présence

network.protocol.version string Version du protocole 3.1.1 Toujours


spécifié dans
network.protocol.name .

url.scheme string Le composant du http ; https Toujours


schéma URI
identifiant le protocole
utilisé.

aspnetcore.request.is_unhandled boolean Vrai lorsque la requête true Si la


n’a pas été traitée par le requête n'a
pipeline d’application. pas été
traitée.

Temps utilisé pour traiter une requête HTTP entrante, tel que mesuré au niveau de la couche
d'hébergement d'ASP.NET Core. La mesure du temps démarre une fois que l'hébergeur Web sous-jacent
a:

Analyse suffisante des en-têtes de requête HTTP sur le flux réseau entrant pour identifier la nouvelle
requête.
Initialisation des structures de données contextuelles telles que HttpContext.

Le temps se termine lorsque :

L’exécution du pipeline du gestionnaire ASP.NET Core est terminée.


Toutes les données de réponse ont été envoyées.
Les structures de données contextuelles de la requête sont en cours de suppression.

Lorsque vous utilisez OpenTelemetry, les groupes de données par défaut pour cette mesure sont définis
sur [ 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10 ].

Disponible à partir de : .NET 8.0.

Métrique : http.server.active_requests

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

http.server.active_requests UpDownCounter {request} Mesure le nombre de requêtes HTTP simultanées


actuellement en cours.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

http.request.method string Méthode de requête HTTP. [1] GET ; POST ; Toujours


HEAD

url.scheme string Le composant du schéma URI identifiant le protocole http ; https Toujours
utilisé.
Disponible à partir de : .NET 8.0.

Microsoft.AspNetCore.Routing
Les métriques Microsoft.AspNetCore.Routing donnent des informations sur le routage des requêtes HTTP
vers les points de terminaison ASP.NET Core :

aspnetcore.routing.match_attempts

Métrique : aspnetcore.routing.match_attempts

ノ Agrandir le tableau

Nom Type Unité (UCUM) Description


d’instrument

aspnetcore.routing.match_attempts Compteur {match_attempt} Nombre de requêtes qui ont tenté


d'être mises en correspondance avec un
point de terminaison.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

aspnetcore.routing.match_status string Résultat du success ; failure Toujours


match

aspnetcore.routing.is_fallback_route boolean Valeur qui True Si un itinéraire a


indique si été mis en
l’itinéraire correspondance
correspondant avec succès.
est un
itinéraire de
secours.

http.route string L'itinéraire {controller}/{action}/{id?} Si un itinéraire a


adapté été mis en
correspondance
avec succès.

Disponible à partir de : .NET 8.0.

Microsoft.AspNetCore.Diagnostics
Les métriques Microsoft.AspNetCore.Diagnostics donnent des informations de diagnostic provenant de
l’intergiciel de gestion des erreurs d’ASP.NET Core :

aspnetcore.diagnostics.exceptions

Métrique : aspnetcore.diagnostics.exceptions
ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

aspnetcore.diagnostics.exceptions Compteur {exception} Nombre d'exceptions interceptées par le


middleware de gestion des exceptions.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

aspnetcore.diagnostics.exception.result string Résultat de la handled ; unhandled Toujours


gestion du
middleware des
exceptions
ASP.NET Core

aspnetcore.diagnostics.handler.type string Nom de type Contoso.MyHandler Si


complet de l'exception a
l’implémentation été gérée
IExceptionHandler par ce
qui a géré gestionnaire.
l’exception.

exception.type string Le nom complet System.OperationCanceledException ; Toujours


du type Contoso.MyException
d’exception.

Disponible à partir de : .NET 8.0.

Microsoft.AspNetCore.RateLimiting
Les métriques Microsoft.AspNetCore.RateLimiting donnent des informations sur la limitation du débit
provenant de l’intergiciel de limitation du débit d’ASP.NET Core :

aspnetcore.rate_limiting.active_request_leases
aspnetcore.rate_limiting.request_lease.duration
aspnetcore.rate_limiting.queued_requests
aspnetcore.rate_limiting.request.time_in_queue
aspnetcore.rate_limiting.requests

Métrique : aspnetcore.rate_limiting.active_request_leases

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

aspnetcore.rate_limiting.active_request_leases UpDownCounter {request} Nombre de requêtes


actuellement actives sur le
serveur qui détiennent un bail à
limitation de débit.
ノ Agrandir le tableau

Attribut Type Description Exemples Présence

aspnetcore.rate_limiting.policy string Nom de la stratégie fixed ; Si le point de terminaison


de limitation de sliding ; correspondant à la requête avait
débit. token une stratégie de limitation de
débit.

Disponible à partir de : .NET 8.0.

Métrique : aspnetcore.rate_limiting.request_lease.duration

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

aspnetcore.rate_limiting.request_lease.duration Histogramme s La durée du bail de limitation de


débit détenu par les requêtes sur
le serveur.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

aspnetcore.rate_limiting.policy string Nom de la stratégie fixed ; Si le point de terminaison


de limitation de sliding ; correspondant à la requête avait
débit. token une stratégie de limitation de
débit.

Disponible à partir de : .NET 8.0.

Métrique : aspnetcore.rate_limiting.queued_requests

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

aspnetcore.rate_limiting.queued_requests UpDownCounter {request} Nombre de demandes actuellement


en file d'attente en attente
d'acquisition d'un bail à limitation de
débit.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

aspnetcore.rate_limiting.policy string Nom de la stratégie fixed ; Si le point de terminaison


de limitation de sliding ; correspondant à la requête avait
débit. token une stratégie de limitation de
débit.
Disponible à partir de : .NET 8.0.

Métrique : aspnetcore.rate_limiting.request.time_in_queue

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

aspnetcore.rate_limiting.request.time_in_queue Histogramme s Temps passé par une requête dans


une file d'attente en attente
d'acquisition d'un bail à limitation
de débit.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

aspnetcore.rate_limiting.policy string Nom de la stratégie de fixed ; sliding ; Si le point de


limitation de débit. token terminaison
correspondant à la
requête avait une
stratégie de limitation de
débit.

aspnetcore.rate_limiting.result string Le résultat de la acquired ; Toujours


limitation du tarif request_canceled
indique si le bail a été
acquis ou s'il contient
un motif de rejet.

Disponible à partir de : .NET 8.0.

Métrique : aspnetcore.rate_limiting.requests

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

aspnetcore.rate_limiting.requests Compteur {request} Nombre de requêtes ayant tenté d'acquérir


un bail à taux limité.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

aspnetcore.rate_limiting.policy string Nom de la stratégie de fixed ; sliding ; Si le point de


limitation de débit. token terminaison
correspondant à la
requête avait une
stratégie de limitation de
débit.
Attribut Type Description Exemples Présence

aspnetcore.rate_limiting.result string Le résultat de la acquired ; Toujours


limitation du tarif request_canceled
indique si le bail a été
acquis ou s'il contient
un motif de rejet.

Disponible à partir de : .NET 8.0.

Microsoft.AspNetCore.HeaderParsing
Les métriques Microsoft.AspNetCore.HeaderParsing donnent des informations sur l’analyse des en-têtes
d’ASP.NET Core :

aspnetcore.header_parsing.parse_errors
aspnetcore.header_parsing.cache_accesses

Métrique : aspnetcore.header_parsing.parse_errors

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

aspnetcore.header_parsing.parse_errors Compteur {parse_error} Nombre d’erreurs qui se sont


produites lors de l’analyse des en-
têtes de requête HTTP.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

aspnetcore.header_parsing.header.name string Nom de l'en- Content-Type Toujours


tête.

error.type string Message Unable to parse media type Toujours


d’erreur. value.

Disponible à partir de : .NET 8.0.

Métrique : aspnetcore.header_parsing.cache_accesses

La métrique est émise uniquement pour les analyseurs d’en-tête de requête HTTP qui prennent en charge
la mise en cache.

ノ Agrandir le tableau
Nom Type Unité (UCUM) Description
d’instrument

aspnetcore.header_parsing.cache_accesses Compteur {cache_access} Nombre de fois qu’un cache


stockant les valeurs d’en-tête
analysées a été accédé.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

aspnetcore.header_parsing.header.name string Nom de l'en-tête. Content- Toujours


Type

aspnetcore.header_parsing.cache_access.type string Valeur indiquant si la valeur de Hit ; Miss Toujours


l’en-tête a été trouvée dans le
cache ou non.

Disponible à partir de : .NET 8.0.

Microsoft.AspNetCore.Server.Kestrel
Les métriques Microsoft.AspNetCore.Server.Kestrel donnent des informations sur les connexions HTTP
provenant du serveur web Kestrel d’ASP.NET Core :

kestrel.active_connections
kestrel.connection.duration
kestrel.rejected_connections
kestrel.queued_connections
kestrel.queued_requests
kestrel.upgraded_connections
kestrel.tls_handshake.duration
kestrel.active_tls_handshakes

Métrique : kestrel.active_connections

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

kestrel.active_connections UpDownCounter {connection} Nombre de connexions actuellement actives sur


le serveur.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

network.transport string Couche de transport OSI ou méthode de tcp ; unix Toujours


communication inter-processus .
Attribut Type Description Exemples Présence

network.type string Couche réseau OSI ou équivalent non OSI. ipv4 ; ipv6 Si le transport
est tcp ou
udp .

server.address string Nom de domaine de l'adresse du serveur s'il est example.com Toujours
disponible sans recherche DNS inversée ; sinon,
l'adresse IP ou le nom du socket de domaine Unix.

server.port int Numéro de port du serveur 80 ; 8080 ; Si le transport


443 est tcp ou
udp .

Disponible à partir de : .NET 8.0.

Métrique : kestrel.connection.duration

ノ Agrandir le tableau

Nom Type d’instrument Unité (UCUM) Description

kestrel.connection.duration Histogramme s La durée des connexions sur le serveur.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

error.type string Le nom complet du type System.OperationCanceledException ; Si une


d’exception. Contoso.MyException exception
a été
levée.

network.protocol.name string Couche application OSI http ; web_sockets Toujours


ou équivalent non OSI.

network.protocol.version string Version du protocole 1.1 ; 2 Toujours


spécifié dans
network.protocol.name .

network.transport string Couche de transport OSI tcp ; unix Toujours


ou méthode de
communication inter-
processus .

network.type string Couche réseau OSI ou ipv4 ; ipv6 Si le


équivalent non OSI. transport
est tcp ou
udp .

server.address string Nom de domaine de example.com Toujours


l'adresse du serveur s'il est
disponible sans recherche
DNS inversée ; sinon,
l'adresse IP ou le nom du
socket de domaine Unix.
Attribut Type Description Exemples Présence

server.port int Numéro de port du serveur 80 ; 8080 ; 443 Si le


transport
est tcp ou
udp .

tls.protocol.version string Version du protocole TLS. 1.2 ; 1.3 Si la


connexion
est
sécurisée
avec TLS.

Étant donné que cette mesure suit la durée de la connexion et que, dans l’idéal, les connexions HTTP sont
utilisées pour plusieurs requêtes, les intervalles doivent être plus longs que ceux utilisés pour les durées
des requêtes. Par exemple, pour un compartiment supérieur de 5 minutes, on utilisera [ 0,01, 0,02, 0,05,
0,1, 0,2, 0,5, 1, 2, 5, 10, 30, 60, 120, 300].

Disponible à partir de : .NET 8.0.

Métrique : kestrel.rejected_connections

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

kestrel.rejected_connections Compteur {connection} Nombre de connexions rejetées par le


serveur.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

network.transport string Couche de transport OSI ou méthode de tcp ; unix Toujours


communication inter-processus .

network.type string Couche réseau OSI ou équivalent non OSI. ipv4 ; ipv6 Si le transport
est tcp ou
udp .

server.address string Nom de domaine de l'adresse du serveur s'il est example.com Toujours
disponible sans recherche DNS inversée ; sinon,
l'adresse IP ou le nom du socket de domaine Unix.

server.port int Numéro de port du serveur 80 ; 8080 ; Si le transport


443 est tcp ou
udp .

Les connexions sont rejetées lorsque le nombre actuellement actif dépasse la valeur configurée avec
MaxConcurrentConnections .

Disponible à partir de : .NET 8.0.


Métrique : kestrel.queued_connections

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

kestrel.queued_connections UpDownCounter {connection} Nombre de connexions actuellement en file


d'attente et en attente de démarrage.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

network.transport string Couche de transport OSI ou méthode de tcp ; unix Toujours


communication inter-processus .

network.transport string Couche réseau OSI ou équivalent non OSI. ipv4 ; ipv6 Si le transport
est tcp ou
udp .

server.address string Nom de domaine de l'adresse du serveur s'il est example.com Toujours
disponible sans recherche DNS inversée ; sinon,
l'adresse IP ou le nom du socket de domaine Unix.

server.port int Numéro de port du serveur 80 ; 8080 ; Si le transport


443 est tcp ou
udp .

Disponible à partir de : .NET 8.0.

Métrique : kestrel.queued_requests

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

kestrel.queued_requests UpDownCounter {request} Nombre de requêtes HTTP sur les connexions


multiplexées (HTTP/2 et HTTP/3) actuellement en file
d'attente et en attente de démarrage.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

network.protocol.name string Couche application OSI ou équivalent non http ; Toujours


OSI. web_sockets

network.protocol.version string Version du protocole spécifié dans 1.1 ; 2 Toujours


network.protocol.name .

network.transport string Couche de transport OSI ou méthode de tcp ; unix Toujours


communication inter-processus .
Attribut Type Description Exemples Présence

network.transport string Couche réseau OSI ou équivalent non OSI. ipv4 ; ipv6 Si le
transport est
tcp ou udp .

server.address string Nom de domaine de l'adresse du serveur s'il est example.com Toujours
disponible sans recherche DNS inversée ; sinon,
l'adresse IP ou le nom du socket de domaine
Unix.

server.port int Numéro de port du serveur 80 ; 8080 ; Si le


443 transport est
tcp ou udp .

Disponible à partir de : .NET 8.0.

Métrique : kestrel.upgraded_connections

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

kestrel.upgraded_connections UpDownCounter {connection} Nombre de connexions actuellement mises à


niveau (WebSockets).

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

network.transport string Couche de transport OSI ou méthode de tcp ; unix Toujours


communication inter-processus .

network.transport string Couche réseau OSI ou équivalent non OSI. ipv4 ; ipv6 Si le transport
est tcp ou
udp .

server.address string Nom de domaine de l'adresse du serveur s'il est example.com Toujours
disponible sans recherche DNS inversée ; sinon,
l'adresse IP ou le nom du socket de domaine Unix.

server.port int Numéro de port du serveur 80 ; 8080 ; Si le transport


443 est tcp ou
udp .

Le compteur suit uniquement les connexions HTTP/1.1.

Disponible à partir de : .NET 8.0.

Métrique : kestrel.tls_handshake.duration

ノ Agrandir le tableau
Nom Type Unité Description
d’instrument (UCUM)

kestrel.tls_handshake.duration Histogramme s La durée des établissements d’une liaison TLS


sur le serveur.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

error.type string Le nom complet du type System.OperationCanceledException ; Si une


d’exception. Contoso.MyException exception a
été levée.

network.transport string Couche de transport OSI tcp ; unix Toujours


ou méthode de
communication inter-
processus .

network.transport string Couche réseau OSI ou ipv4 ; ipv6 Si le


équivalent non OSI. transport est
tcp ou udp .

server.address string Nom de domaine de example.com Toujours


l'adresse du serveur s'il est
disponible sans recherche
DNS inversée ; sinon,
l'adresse IP ou le nom du
socket de domaine Unix.

server.port int Numéro de port du serveur 80 ; 8080 ; 443 Si le


transport est
tcp ou udp .

tls.protocol.version string Version du protocole TLS. 1.2 ; 1.3 Si la


connexion
est sécurisée
avec TLS.

Lors de l’utilisation d’OpenTelemetry, les compartiments de cette mesure sont définis par défaut sur [
0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10 ].

Disponible à partir de : .NET 8.0.

Métrique : kestrel.active_tls_handshakes

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

kestrel.active_tls_handshakes UpDownCounter {handshake} Nombre d’établissement d’une liaison TLS


actuellement en cours sur le serveur.

ノ Agrandir le tableau
Attribut Type Description Exemples Présence

network.transport string Couche de transport OSI ou méthode de tcp ; unix Toujours


communication inter-processus .

network.transport string Couche réseau OSI ou équivalent non OSI. ipv4 ; ipv6 Si le transport
est tcp ou
udp .

server.address string Nom de domaine de l'adresse du serveur s'il est example.com Toujours
disponible sans recherche DNS inversée ; sinon,
l'adresse IP ou le nom du socket de domaine Unix.

server.port int Numéro de port du serveur 80 ; 8080 ; Si le transport


443 est tcp ou
udp .

Disponible à partir de : .NET 8.0.

Microsoft.AspNetCore.Http.Connections
Les métriques Microsoft.AspNetCore.Http.Connections donnent des informations sur les connexions
provenant de SignalR d’ASP.NET Core :

signalr.server.connection.duration
signalr.server.active_connections

Métrique : signalr.server.connection.duration

ノ Agrandir le tableau

Nom Type d’instrument Unité (UCUM) Description

signalr.server.connection.duration Histogramme s La durée des connexions sur le serveur.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

signalr.connection.status string État de fermeture de la connexion HTTP app_shutdown ; timeout Toujours


SignalR.

signalr.transport string Type de transport SignalR web_sockets ; Toujours


long_polling

Disponible à partir de : .NET 8.0.

ノ Agrandir le tableau

Valeur Description

normal_closure La connexion était fermée normalement.


Valeur Description

timeout La connexion a été fermée en raison d'un délai d'attente.

app_shutdown La connexion a été fermée car l'application est en train de se fermer.

signalr.transport prend l’une des valeurs suivantes :

ノ Agrandir le tableau

Valeur Protocole

server_sent_events événements envoyés par le serveur

long_polling Longue interrogation

web_sockets WebSocket

Étant donné que cette mesure suit la durée de la connexion et que, dans l’idéal, les connexions SignalR
sont durables, les intervalles doivent être plus longs que ceux utilisés pour les durées des requêtes. Par
exemple, pour un compartiment supérieur de 5 minutes, on utilisera [0, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2,
5, 10, 30, 60, 120, 300].

Disponible à partir de : .NET 8.0.

Métrique : signalr.server.active_connections

ノ Agrandir le tableau

Nom Type Unité Description


d’instrument (UCUM)

signalr.server.active_connections UpDownCounter {connection} Nombre de connexions actuellement


actives sur le serveur.

ノ Agrandir le tableau

Attribut Type Description Exemples Présence

signalr.connection.status string État de fermeture de la connexion HTTP app_shutdown ; timeout Toujours


SignalR.

signalr.transport string Type de transport SignalR web_sockets ; Toujours


long_polling

Disponible à partir de : .NET 8.0.

6 Collaborer avec nous sur


GitHub Commentaires sur .NET
La source de ce contenu se trouve .NET est un projet open source. Sélectionnez un
sur GitHub, où vous pouvez lien pour fournir des commentaires :
également créer et examiner les
problèmes et les demandes de  Ouvrir un problème de documentation
tirage. Pour plus d’informations,
consultez notre guide du  Indiquer des commentaires sur le produit
contributeur.
Utiliser HttpContext dans ASP.NET Core
Article • 26/12/2023

HttpContext encapsule toutes les informations sur une requête et une réponse HTTP
individuelles. Une instance HttpContext est initialisée lorsqu’une requête HTTP est
reçue. L’instance HttpContext est accessible par les intergiciels et frameworks
d’application comme les contrôleurs d’API web, Razor Pages, SignalR, gRPC, etc.

Pour plus d’informations sur l’accès au HttpContext , consultez Accéder à HttpContext


dans ASP.NET Core.

HttpRequest
HttpContext.Request fournit l’accès à HttpRequest. HttpRequest contient des
informations sur la requête HTTP entrante et est initialisée lorsqu’une requête HTTP est
reçue par le serveur. HttpRequest n’est pas en lecture seule, et les intergiciels peuvent
modifier les valeurs de requête dans le pipeline d’intergiciels.

Les membres couramment utilisés sur HttpRequest incluent :

ノ Agrandir le tableau

Propriété Description Exemple

HttpRequest.Path chemin d'accès de la requête. /en/article/getstarted

HttpRequest.Method Méthode de demande. GET

HttpRequest.Headers Collection d'en-têtes de requêtes. user-agent=Edge


x-custom-
header=MyValue

HttpRequest.RouteValues Une collection de valeurs d’itinéraire. language=en


La collection est définie lorsque la article=getstarted
requête est mise en correspondance
avec un itinéraire.

HttpRequest.Query Collection de valeurs de requête filter=hello


analysées à partir de QueryString. page=1

HttpRequest.ReadFormAsync() Méthode qui lit le corps de la requête email=user@contoso.com


en tant que formulaire et retourne password=TNkt4taM
une collection de valeurs de
formulaire. Pour plus d’informations
Propriété Description Exemple

sur la raison pour laquelle


ReadFormAsync doit être utilisé pour
accéder aux données de formulaire,
consultez Préférer ReadFormAsync à
Request.Form.

HttpRequest.Body Un Stream pour lire le corps de la Charge utile JSON UTF-


requête. 8

Obtenir les en-têtes de requête


HttpRequest.Headers fournit l’accès aux en-têtes de requête envoyés avec la requête
HTTP. Il existe deux façons d’accéder aux en-têtes à l’aide de cette collection :

Indiquez le nom de l’en-tête à l’indexeur sur la collection d’en-têtes. Le nom de


l’en-tête ne respecte pas la casse. L’indexeur peut accéder à n’importe quelle
valeur d’en-tête.
La collection d’en-têtes possède également des propriétés permettant d’obtenir et
de définir des en-têtes HTTP couramment utilisés. Les propriétés offrent un moyen
rapide et piloté par IntelliSense d’accéder aux en-têtes.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", (HttpRequest request) =>


{
var userAgent = request.Headers.UserAgent;
var customHeader = request.Headers["x-custom-header"];

return Results.Ok(new { userAgent = userAgent, customHeader =


customHeader });
});

app.Run();

Corps de la requête de lecture


Une requête HTTP peut inclure un corps de requête. Le corps de la requête est des
données associées à la requête, comme le contenu d’un formulaire HTML, la charge utile
JSON UTF-8 ou un fichier.

HttpRequest.Body permet de lire le corps de la requête avec Stream :


C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapPost("/uploadstream", async (IConfiguration config, HttpContext


context) =>
{
var filePath = Path.Combine(config["StoredFilesPath"],
Path.GetRandomFileName());

await using var writeStream = File.Create(filePath);


await context.Request.Body.CopyToAsync(writeStream);
});

app.Run();

HttpRequest.Body peut être lu directement ou utilisé avec d’autres API qui acceptent le

flux.

7 Notes

Les API minimales prennent en charge la liaison de HttpRequest.Body directement


à un paramètre Stream.

Activer la mise en mémoire tampon du corps de la requête


Le corps de la requête ne peut être lu qu’une seule fois, du début à la fin. La lecture
avant uniquement du corps de la requête évite la surcharge liée à la mise en mémoire
tampon de l’ensemble du corps de la requête et réduit l’utilisation de la mémoire.
Toutefois, dans certains scénarios, il est nécessaire de lire le corps de la requête
plusieurs fois. Par exemple, l’intergiciel peut avoir besoin de lire le corps de la requête,
puis du rembobiner afin d’être disponible pour le point de terminaison.

La méthode d’extension EnableBuffering active la mise en mémoire tampon du corps de


la requête HTTP et est la méthode recommandée pour activer les lectures multiples.
Étant donné qu’une requête peut être de n’importe quelle taille, EnableBuffering prend
en charge les options permettant de mettre en mémoire tampon les corps de requête
volumineux sur le disque ou de les rejeter entièrement.

L’intergiciel dans l’exemple suivant :

Active les lectures multiples avec EnableBuffering . Vous devez l’appeler avant de
lire le corps de la requête.
Lit le corps de la requête.
Rembobine le corps de la requête au début afin que d’autres intergiciels ou le
point de terminaison puissent le lire.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.Use(async (context, next) =>


{
context.Request.EnableBuffering();
await ReadRequestBody(context.Request.Body);
context.Request.Body.Position = 0;

await next.Invoke();
});

app.Run();

BodyReader
Une autre façon de lire le corps de la requête consiste à utiliser la propriété
HttpRequest.BodyReader. La propriété BodyReader expose le corps de la requête en tant
que PipeReader. Cette API provient de pipelines d’E/S, un moyen avancé et à hautes
performances de lire le corps de la requête.

Le lecteur accède directement au corps de la requête et gère la mémoire pour le compte


de l’appelant. Contrairement à HttpRequest.Body , le lecteur ne copie pas les données de
requête dans une mémoire tampon. Toutefois, un lecteur est plus compliqué à utiliser
qu’un flux et doit être manié avec précaution.

Pour plus d’informations sur la lecture du contenu à partir de BodyReader , consultez


PipeReader pour les pipelines d’E/S.

HttpResponse
HttpContext.Response fournit l’accès à HttpResponse. HttpResponse est utilisé pour
définir des informations sur la réponse HTTP renvoyée au client.

Les membres couramment utilisés sur HttpResponse incluent :

ノ Agrandir le tableau
Propriété Description Exemple

HttpResponse.StatusCode Le code de réponse. Doit être défini avant 200


d’écrire dans le corps de la réponse.

HttpResponse.ContentType L’en-tête content-type de la réponse. Doit application/json


être défini avant d’écrire dans le corps de la
réponse.

HttpResponse.Headers Une collection des en-têtes de réponse. Doit server=Kestrel


être défini avant d’écrire dans le corps de la x-custom-
réponse. header=MyValue

HttpResponse.Body Un Stream pour écrire le corps de la réponse. Page web générée

Définir les en-têtes de réponse


HttpResponse.Headers fournit l’accès aux en-têtes de réponse envoyés avec la réponse
HTTP. Il existe deux façons d’accéder aux en-têtes à l’aide de cette collection :

Indiquez le nom de l’en-tête à l’indexeur sur la collection d’en-têtes. Le nom de


l’en-tête ne respecte pas la casse. L’indexeur peut accéder à n’importe quelle
valeur d’en-tête.
La collection d’en-têtes possède également des propriétés permettant d’obtenir et
de définir des en-têtes HTTP couramment utilisés. Les propriétés offrent un moyen
rapide et piloté par IntelliSense d’accéder aux en-têtes.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", (HttpResponse response) =>


{
response.Headers.CacheControl = "no-cache";
response.Headers["x-custom-header"] = "Custom value";

return Results.File(File.OpenRead("helloworld.txt"));
});

app.Run();

Une application ne peut pas modifier les en-têtes une fois la réponse démarrée. Une fois
la réponse démarrée, les en-têtes sont envoyés au client. Une réponse est démarrée en
vidant le corps de la réponse ou en appelant
HttpResponse.StartAsync(CancellationToken). La propriété HttpResponse.HasStarted
indique si la réponse a démarré. Une erreur est générée lors de la tentative de
modification des en-têtes après le démarrage de la réponse :

System.InvalidOperationException : les en-têtes sont en lecture seule, la réponse a


déjà démarré.

7 Notes

Sauf si la mise en mémoire tampon des réponses est activée, toutes les opérations
d’écriture (par exemple WriteAsync) vident le corps de la réponse en interne et
marquent la réponse comme étant démarrée. La mise en mémoire tampon des
réponses est désactivée par défaut.

Écrire le corps de la réponse


Une réponse HTTP peut inclure un corps de réponse. Le corps de la réponse est des
données associées à la réponse, comme le contenu de page web généré, la charge utile
JSON UTF-8 ou un fichier.

HttpResponse.Body permet d’écrire le corps de la réponse avec Stream :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapPost("/downloadfile", async (IConfiguration config, HttpContext


context) =>
{
var filePath = Path.Combine(config["StoredFilesPath"],
"helloworld.txt");

await using var fileStream = File.OpenRead(filePath);


await fileStream.CopyToAsync(context.Response.Body);
});

app.Run();

HttpResponse.Body peut être écrit directement ou utilisé avec d’autres API qui écrivent

dans un flux.

BodyWriter
Une autre façon d’écrire le corps de la réponse consiste à utiliser la propriété
HttpResponse.BodyWriter. La propriété BodyWriter expose le corps de la réponse en
tant que PipeWriter. Cette API provient de pipelines d’E/S, et il s’agit d’un moyen avancé
et à hautes performances d’écrire la réponse.

L’enregistreur fournit un accès direct au corps de la réponse et gère la mémoire pour le


compte de l’appelant. Contrairement à HttpResponse.Body , l’enregistreur ne copie pas
les données de requête dans une mémoire tampon. Toutefois, un enregistreur est plus
compliqué à utiliser qu’un flux et le code de l’enregistreur doit être testé de façon
exhaustive.

Pour plus d’informations sur l’écriture de contenu dans BodyWriter , consultez PipeWriter
pour les pipelines d’E/S.

Définir des amorces de réponse


HTTP/2 et HTTP/3 prennent en charge les amorces de réponse. Les amorces sont des
en-têtes envoyés avec la réponse une fois le corps de la réponse terminé. Étant donné
que les amorces sont envoyées après le corps de la réponse, elles peuvent être ajoutées
à la réponse à tout moment.

Le code suivant définit des amorces à l’aide de AppendTrailer :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/", (HttpResponse response) =>


{
// Write body
response.WriteAsync("Hello world");

if (response.SupportsTrailers())
{
response.AppendTrailer("trailername", "TrailerValue");
}
});

app.Run();

RequestAborted
Le jeton d’annulation HttpContext.RequestAborted peut être utilisé pour avertir que la
requête HTTP a été abandonnée par le client ou le serveur. Le jeton d’annulation doit
être passé aux tâches de longue durée afin qu’elles puissent être annulées si la requête
est abandonnée. Par exemple, l’abandon d’une requête de base de données ou d’une
requête HTTP pour obtenir des données à retourner dans la réponse.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

var httpClient = new HttpClient();


app.MapPost("/books/{bookId}", async (int bookId, HttpContext context) =>
{
var stream = await httpClient.GetStreamAsync(
$"http://consoto/books/{bookId}.json", context.RequestAborted);

// Proxy the response as JSON


return Results.Stream(stream, "application/json");
});

app.Run();

Le jeton d’annulation RequestAborted n’a pas besoin d’être utilisé pour les opérations de
lecture du corps de la requête, car les lectures réagissent toujours immédiatement
lorsque la requête est abandonnée. Le jeton RequestAborted est généralement inutile
lors de l’écriture des corps de réponse, car les écritures s’arrêtent immédiatement
lorsque la requête est abandonnée.

Dans certains cas, le passage du jeton RequestAborted aux opérations d’écriture peut
être un moyen pratique de forcer une boucle d’écriture à quitter tôt avec un
OperationCanceledException. Toutefois, il est généralement préférable de passer le jeton
RequestAborted dans toutes les opérations asynchrones responsables de la récupération

du contenu du corps de la réponse à la place.

7 Notes

Les API minimales prennent en charge la liaison de HttpContext.RequestAborted


directement à un paramètre CancellationToken.

Abort()
La méthode HttpContext.Abort() peut être utilisée pour abandonner une requête HTTP à
partir du serveur. L’abandon de la requête HTTP déclenche immédiatement le jeton
d’annulation HttpContext.RequestAborted et envoie une notification au client que le
serveur a abandonné la requête.

L’intergiciel dans l’exemple suivant :

Ajoute une vérification personnalisée pour les requêtes malveillantes.


Abandonne la requête HTTP si celle-ci est malveillante.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.Use(async (context, next) =>


{
if (RequestAppearsMalicious(context.Request))
{
// Malicious requests don't even deserve an error response (e.g.
400).
context.Abort();
return;
}

await next.Invoke();
});

app.Run();

User
La propriété HttpContext.User est utilisée pour obtenir ou définir l’utilisateur, représenté
par ClaimsPrincipal, pour la requête. Le ClaimsPrincipal est généralement défini par
l’authentification ASP.NET Core.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/user/current", [Authorize] async (HttpContext context) =>


{
var user = await GetUserAsync(context.User.Identity.Name);
return Results.Ok(user);
});

app.Run();
7 Notes

Les API minimales prennent en charge la liaison de HttpContext.User directement


à un paramètre ClaimsPrincipal.

Features
La propriété HttpContext.Features fournit l’accès à la collection d’interfaces de
fonctionnalités pour la requête actuelle. Étant donné que la collection de fonctionnalités
est mutable même dans le contexte d’une requête, il est possible d’utiliser un intergiciel
(middleware) pour modifier la collection et ajouter la prise en charge de fonctionnalités
supplémentaires. Certaines fonctionnalités avancées sont uniquement disponibles en
accédant à l’interface associée via la collection de fonctionnalités.

L’exemple suivant :

Obtient le IHttpMinRequestBodyDataRateFeature à partir de la collection de


fonctionnalités.
Met MinDataRate sur la valeur Null. Cela supprime l’exigence de débit de données
minimal que le corps de la requête doit recevoir du client pour cette requête HTTP.

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();

app.MapGet("/long-running-stream", async (HttpContext context) =>


{
var feature = context.Features.Get<IHttpMinRequestBodyDataRateFeature>
();
if (feature != null)
{
feature.MinDataRate = null;
}

// await and read long-running stream from request body.


await Task.Yield();
});

app.Run();

Pour plus d’informations sur l’utilisation des fonctionnalités de requête et de


HttpContext , consultez Fonctionnalités de requête dans ASP.NET Core.
HttpContext n’est pas thread-safe
Cet article traite principalement de l’utilisation de HttpContext dans le flux de requête et
de réponse de Razor Pages, des contrôleurs, des intergiciels, etc. Tenez compte des
éléments suivants lors de l’utilisation de HttpContext en dehors du flux de demande et
de réponse :

Le HttpContext n’est PAS thread-safe ; l’accès à celui-ci à partir de plusieurs


threads peut entraîner des exceptions, une altération des données et des résultats
généralement imprévisibles.
L’interface IHttpContextAccessor doit être utilisée avec précaution. Comme
toujours, le HttpContext doit pas être capturé en dehors du flux de requête.
IHttpContextAccessor :
S’appuie sur AsyncLocal<T>, ce qui peut avoir un impact négatif sur les
performances sur les appels asynchrones.
Crée une dépendance sur « l’état ambiant », ce qui peut rendre les tests plus
difficiles.
IHttpContextAccessor.HttpContext peut être null en cas d’accès en dehors du flux
de requête.
Pour accéder aux informations de HttpContext en dehors du flux de requête,
copiez les informations à l’intérieur du flux de requête. Veillez à copier les données
réelles et pas seulement les références. Par exemple, au lieu de copier une
référence à un IHeaderDictionary , copiez les valeurs d’en-tête appropriées ou
copiez le dictionnaire entier clé par clé avant de quitter le flux de requête.
Ne capturez pas IHttpContextAccessor.HttpContext dans un constructeur.

L’exemple suivant journalise les branches GitHub lorsque vous le demandez à partir du
point de terminaison /branch :

C#

using System.Text.Json;
using HttpContextInBackgroundThread;
using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpContextAccessor();
builder.Services.AddHostedService<PeriodicBranchesLoggerService>();

builder.Services.AddHttpClient("GitHub", httpClient =>


{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// The GitHub API requires two headers. The Use-Agent header is added
// dynamically through UserAgentHeaderHandler
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
}).AddHttpMessageHandler<UserAgentHeaderHandler>();

builder.Services.AddTransient<UserAgentHeaderHandler>();

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/branches", async (IHttpClientFactory httpClientFactory,


HttpContext context, Logger<Program> logger) =>
{
var httpClient = httpClientFactory.CreateClient("GitHub");
var httpResponseMessage = await httpClient.GetAsync(
"repos/dotnet/AspNetCore.Docs/branches");

if (!httpResponseMessage.IsSuccessStatusCode)
return Results.BadRequest();

await using var contentStream =


await httpResponseMessage.Content.ReadAsStreamAsync();

var response = await JsonSerializer.DeserializeAsync


<IEnumerable<GitHubBranch>>(contentStream);

app.Logger.LogInformation($"/branches request: " +


$"{JsonSerializer.Serialize(response)}");

return Results.Ok(response);
});

app.Run();

L’API GitHub nécessite deux en-têtes. L’en-tête User-Agent est ajouté dynamiquement
par UserAgentHeaderHandler :

C#

using System.Text.Json;
using HttpContextInBackgroundThread;
using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpContextAccessor();
builder.Services.AddHostedService<PeriodicBranchesLoggerService>();
builder.Services.AddHttpClient("GitHub", httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");

// The GitHub API requires two headers. The Use-Agent header is added
// dynamically through UserAgentHeaderHandler
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
}).AddHttpMessageHandler<UserAgentHeaderHandler>();

builder.Services.AddTransient<UserAgentHeaderHandler>();

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/branches", async (IHttpClientFactory httpClientFactory,


HttpContext context, Logger<Program> logger) =>
{
var httpClient = httpClientFactory.CreateClient("GitHub");
var httpResponseMessage = await httpClient.GetAsync(
"repos/dotnet/AspNetCore.Docs/branches");

if (!httpResponseMessage.IsSuccessStatusCode)
return Results.BadRequest();

await using var contentStream =


await httpResponseMessage.Content.ReadAsStreamAsync();

var response = await JsonSerializer.DeserializeAsync


<IEnumerable<GitHubBranch>>(contentStream);

app.Logger.LogInformation($"/branches request: " +


$"{JsonSerializer.Serialize(response)}");

return Results.Ok(response);
});

app.Run();

UserAgentHeaderHandler :

C#

using Microsoft.Net.Http.Headers;

namespace HttpContextInBackgroundThread;

public class UserAgentHeaderHandler : DelegatingHandler


{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger _logger;
public UserAgentHeaderHandler(IHttpContextAccessor httpContextAccessor,
ILogger<UserAgentHeaderHandler> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}

protected override async Task<HttpResponseMessage>


SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var contextRequest = _httpContextAccessor.HttpContext?.Request;
string? userAgentString = contextRequest?.Headers["user-
agent"].ToString();

if (string.IsNullOrEmpty(userAgentString))
{
userAgentString = "Unknown";
}

request.Headers.Add(HeaderNames.UserAgent, userAgentString);
_logger.LogInformation($"User-Agent: {userAgentString}");

return await base.SendAsync(request, cancellationToken);


}
}

Dans le code précédent, lorsque le HttpContext est null , la chaîne userAgent est
définie sur "Unknown" . Si possible, HttpContext doit être explicitement passé au service.
La transmission explicite de données HttpContext :

Rend l’API de service plus utilisable en dehors du flux de requête.


Est préférable pour les performances.
Rend le code plus facile à comprendre et expliquer qu’en s’appuyant sur l’état
ambiant.

Lorsque le service doit accéder à HttpContext , il doit tenir compte de la possibilité que
HttpContext soit null lorsqu’il n’est pas appelé à partir d’un thread de requête.

L’application inclut également PeriodicBranchesLoggerService , qui journalise les


branches GitHub ouvertes du référentiel spécifié toutes les 30 secondes :

C#

using System.Text.Json;

namespace HttpContextInBackgroundThread;

public class PeriodicBranchesLoggerService : BackgroundService


{
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
private readonly PeriodicTimer _timer;

public PeriodicBranchesLoggerService(IHttpClientFactory
httpClientFactory,

ILogger<PeriodicBranchesLoggerService> logger)
{
_httpClientFactory = httpClientFactory;
_logger = logger;
_timer = new PeriodicTimer(TimeSpan.FromSeconds(30));
}

protected override async Task ExecuteAsync(CancellationToken


stoppingToken)
{
while (await _timer.WaitForNextTickAsync(stoppingToken))
{
try
{
// Cancel sending the request to sync branches if it takes
too long
// rather than miss sending the next request scheduled 30
seconds from now.
// Having a single loop prevents this service from sending
an unbounded
// number of requests simultaneously.
using var syncTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
syncTokenSource.CancelAfter(TimeSpan.FromSeconds(30));

var httpClient = _httpClientFactory.CreateClient("GitHub");


var httpResponseMessage = await
httpClient.GetAsync("repos/dotnet/AspNetCore.Docs/branches",

stoppingToken);

if (httpResponseMessage.IsSuccessStatusCode)
{
await using var contentStream =
await
httpResponseMessage.Content.ReadAsStreamAsync(stoppingToken);

// Sync the response with preferred datastore.


var response = await JsonSerializer.DeserializeAsync<
IEnumerable<GitHubBranch>>(contentStream,
cancellationToken: stoppingToken);

_logger.LogInformation(
$"Branch sync successful! Response:
{JsonSerializer.Serialize(response)}");
}
else
{
_logger.LogError(1, $"Branch sync failed! HTTP status
code: {httpResponseMessage.StatusCode}");
}
}
catch (Exception ex)
{
_logger.LogError(1, ex, "Branch sync failed!");
}
}
}

public override Task StopAsync(CancellationToken stoppingToken)


{
// This will cause any active call to WaitForNextTickAsync() to
return false immediately.
_timer.Dispose();
// This will cancel the stoppingToken and await
ExecuteAsync(stoppingToken).
return base.StopAsync(stoppingToken);
}
}

PeriodicBranchesLoggerService est un service hébergé, qui s’exécute en dehors du flux

de requête et de réponse. La journalisation à partir du PeriodicBranchesLoggerService a


un HttpContext null. Le PeriodicBranchesLoggerService a été écrit pour ne pas
dépendre du HttpContext .

C#

using System.Text.Json;
using HttpContextInBackgroundThread;
using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpContextAccessor();
builder.Services.AddHostedService<PeriodicBranchesLoggerService>();

builder.Services.AddHttpClient("GitHub", httpClient =>


{

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.
 Indiquer des commentaires sur
le produit
Routage dans ASP.NET Core
Article • 30/11/2023
) Contenu assisté par l'IA. Cet article a été en partie créé à l’aide d’une IA. Un auteur a examiné et
si besoin révisé le contenu. En savoir plus

Par Ryan Nowak , Kirk Larkin et Rick Anderson

Le routage est responsable de la correspondance des requêtes HTTP entrantes et de la


distribution de ces requêtes aux points de terminaison exécutables de l’application. Les
points de terminaison sont les unités de code de gestion des requêtes exécutables de
l’application. Les points de terminaison sont définies dans l’application et configurées au
démarrage de l’application. Le processus de correspondance de point de terminaison
peut extraire des valeurs de l’URL de la requête et fournir ces valeurs pour le traitement
des demandes. Avec les informations de point de terminaison fournies par l’application,
le routage peut également générer des URL qui mappent vers des points de
terminaison.

Les applications peuvent configurer le routage à l’aide des éléments suivants :

Contrôleurs
Razor Pages
SignalR
Services gRPC
Intergiciels avec point de terminaison, tels que les vérifications d’intégrité.
Délégués et lambdas inscrits avec le routage.

Cet article décrit les détails de bas niveau du routage ASP.NET Core. Pour plus
d’informations sur la configuration du routage :

Pour les contrôleurs, consultez Routage vers les actions du contrôleur dans
ASP.NET Core.
Pour les conventions Razor Pages, consultez les conventions de routage et
d’application Razor Pages dans ASP.NET Core.

Concepts de base du routage


Le code suivant illustre un exemple de routage de base :

C#

var builder = WebApplication.CreateBuilder(args);


var app = builder.Build();
app.MapGet("/", () => "Hello World!");

app.Run();

L’exemple précédent inclut un point de terminaison unique à l’aide de la méthode


MapGet :

Lorsqu’une requête http GET est envoyée à l’URL racine / :


Le délégué de requête s’exécute.
Hello World! est écrit dans la réponse HTTP.

Si la méthode de requête n’est pas GET ou si l’URL racine n’est pas / , aucun
routage ne correspond et un HTTP 404 est retourné.

Le routage utilise une paire d’intergiciels, inscrite par UseRouting et UseEndpoints :

UseRouting ajoute la correspondance de routage au pipeline d’intergiciels. Cet

intergiciel examine l’ensemble des points de terminaison définis dans l’application


et sélectionne la meilleure correspondance en fonction de la requête.
UseEndpoints ajoute l’exécution du point de terminaison au pipeline de

l’intergiciel. Il exécute le délégué associé au point de terminaison sélectionné.

Les applications n’ont généralement pas besoin d’appeler UseRouting ou UseEndpoints .


WebApplicationBuilder configure un pipeline d’intergiciels qui encapsule l’intergiciel
ajouté dans Program.cs avec UseRouting et UseEndpoints . Toutefois, les applications
peuvent modifier l’ordre dans lequel UseRouting et UseEndpoints s’exécutent en
appelant ces méthodes explicitement. Par exemple, le code suivant effectue un appel
explicite à UseRouting :

C#

app.Use(async (context, next) =>


{
// ...
await next(context);
});

app.UseRouting();

app.MapGet("/", () => "Hello World!");

Dans le code précédent :

L’appel à app.Use inscrit un intergiciel personnalisé qui s’exécute au début du


pipeline.
L’appel à UseRouting configure l’intergiciel de correspondance de routage à
exécuter après l’intergiciel personnalisé.
Le point de terminaison inscrit avec MapGet s’exécute à la fin du pipeline.

Si l’exemple précédent n’incluait pas d’appel à UseRouting , l’intergiciel personnalisé


s’exécuterait après l’intergiciel de correspondance de routage.

Remarque : Itinéraires ajoutés directement à l’WebApplication exécution à la fin du


pipeline.

Points de terminaison
La méthode MapGet est utilisée pour définir un point de terminaison. Un point de
terminaison peut être :

Sélectionné, en correspondant à l’URL et à la méthode HTTP.


Exécuté, en exécutant le délégué.

Les points de terminaison qui peuvent être mis en correspondance et exécutés par
l’application sont configurés dans UseEndpoints . Par exemple, MapGet, MapPost et des
méthodes similaires connectent des délégués de requête au système de routage. Des
méthodes supplémentaires peuvent être utilisées pour connecter les fonctionnalités
d’infrastructure ASP.NET Core au système de routage :

MapRazorPages pour Razor Pages


MapControllers pour les contrôleurs
MapHub<THub> pour SignalR
MapGrpcService<TService> pour gRPC

L’exemple suivant montre le routage avec un modèle de routage plus sophistiqué :

C#

app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");

La chaîne /hello/{name:alpha} est un modèle de routage. Un modèle de routage est


utilisé pour configurer la mise en correspondance du point de terminaison. Dans ce cas,
le modèle correspond à :

Un URL comme /hello/Docs


Tout chemin d’URL qui commence par /hello/ suivi d’une séquence de caractères
alphabétiques. :alpha applique une contrainte de routage qui fait correspondre
uniquement les caractères alphabétiques. Les contraintes de routage sont
expliquées plus loin dans cet article.

Deuxième segment du chemin d’URL, {name:alpha} :

Est lié au paramètre name .


Est capturé et stocké dans HttpRequest.RouteValues.

L’exemple suivant montre le routage avec les contrôles d’intégrité et l’autorisation :

C#

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");

L’exemple précédent montre comment :

L’intergiciel d’autorisation peut être utilisé avec le routage.


Les points de terminaison peuvent être utilisés pour configurer le comportement
d’autorisation.

L’appel MapHealthChecks ajoute un point de terminaison de contrôle d’intégrité. Le


chaînage RequireAuthorization sur cet appel attache une stratégie d’autorisation au
point de terminaison.

Appeler UseAuthentication et UseAuthorization ajoute l’intergiciel d’authentification et


d’autorisation. Ces intergiciels sont placés entre UseRouting et UseEndpoints afin qu’ils
puissent :

Voir le point de terminaison sélectionné par UseRouting .


Appliquez une stratégie d’autorisation avant que UseEndpoints les distribue au
point de terminaison.

Métadonnées de point de terminaison


Dans l’exemple précédent, il existe deux points de terminaison, mais seul le point de
terminaison de contrôle d’intégrité a une stratégie d’autorisation attachée. Si la
demande correspond au point de terminaison de contrôle d’intégrité, /healthz , une
vérification d’autorisation est effectuée. Cela montre que les points de terminaison
peuvent avoir des données supplémentaires attachées. Ces données supplémentaires
sont appelées métadonnées de point de terminaison :
Les métadonnées peuvent être traitées par un intergiciel prenant en charge le
routage.
Les métadonnées peuvent être de n’importe quel type .NET.

Concepts de routage
Le système de routage s’appuie sur le pipeline d’intergiciels en ajoutant le concept de
point de terminaison puissant. Les points de terminaison représentent des unités des
fonctionnalités de l’application qui sont distinctes les unes des autres en termes de
routage, d’autorisation et de n’importe quel nombre de systèmes ASP.NET Core.

Définition de point de terminaison ASP.NET Core


Un point de terminaison ASP.NET Core est :

Exécutable : a un RequestDelegate.
Extensible : possède une collection de métadonnées.
Sélectionnable : peut contenir des informations de routage.
Énumérable : la collection de points de terminaison peut être répertoriée en
récupérant EndpointDataSource à partir de DI.

Le code suivant montre comment récupérer et inspecter le point de terminaison


correspondant à la requête actuelle :

C#

app.Use(async (context, next) =>


{
var currentEndpoint = context.GetEndpoint();

if (currentEndpoint is null)
{
await next(context);
return;
}

Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");

if (currentEndpoint is RouteEndpoint routeEndpoint)


{
Console.WriteLine($" - Route Pattern:
{routeEndpoint.RoutePattern}");
}

foreach (var endpointMetadata in currentEndpoint.Metadata)


{
Console.WriteLine($" - Metadata: {endpointMetadata}");
}

await next(context);
});

app.MapGet("/", () => "Inspect Endpoint.");

Le point de terminaison, s’il est sélectionné, peut être récupéré à partir de HttpContext .
Ses propriétés peuvent être inspectées. Les objets de point de terminaison sont
immuables et ne peuvent pas être modifiés après la création. Le type de point de
terminaison le plus courant est RouteEndpoint. RouteEndpoint inclut des informations
qui lui permettent d’être sélectionné par le système de routage.

Dans le code précédent, app.Use configure un intergiciel inclus.

Le code suivant montre que, selon l’endroit où app.Use est appelé dans le pipeline, il se
peut qu’il n’y ait pas de point de terminaison :

C#

// Location 1: before routing runs, endpoint is always null here.


app.Use(async (context, next) =>
{
Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ??
"(null)"}");
await next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing


found a match.
app.Use(async (context, next) =>
{
Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ??
"(null)"}");
await next(context);
});

// Location 3: runs when this endpoint matches


app.MapGet("/", (HttpContext context) =>
{
Console.WriteLine($"3. Endpoint: {context.GetEndpoint()?.DisplayName ??
"(null)"}");
return "Hello World!";
}).WithDisplayName("Hello");

app.UseEndpoints(_ => { });

// Location 4: runs after UseEndpoints - will only run if there was no


match.
app.Use(async (context, next) =>
{
Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ??
"(null)"}");
await next(context);
});

L’exemple précédent ajoute des instructions Console.WriteLine qui indiquent si un


point de terminaison a été sélectionné ou non. Pour plus de clarté, l’exemple affecte un
nom complet au point de terminaison / fourni.

L’exemple précédent inclut également des appels vers UseRouting et UseEndpoints pour
contrôler exactement quand ces intergiciels s’exécutent dans le pipeline.

L’exécution de ce code avec une URL / affiche :

txt

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

L’exécution de ce code avec toute autre URL affiche :

txt

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

Cette sortie montre que :

Le point de terminaison est toujours null avant que soit UseRouting appelé.
Si une correspondance est trouvée, le point de terminaison n’est pas null entre
UseRouting et UseEndpoints.

L’intergiciel UseEndpoints est terminal lorsqu’une correspondance est trouvée.


L’intergiciel terminal est défini plus loin dans cet article.
L’intergiciel après UseEndpoints s’exécute uniquement lorsqu’aucune
correspondance n’est trouvée.

L’intergiciel UseRouting utilise la méthode SetEndpoint pour attacher le point de


terminaison au contexte actuel. Il est possible de remplacer l’intergiciel UseRouting par
une logique personnalisée et d’obtenir les avantages de l’utilisation de points de
terminaison. Les points de terminaison sont une primitive de bas niveau comme
l’intergiciel et ne sont pas couplés à l’implémentation du routage. La plupart des
applications n’ont pas besoin de remplacer UseRouting par une logique personnalisée.

L’intergiciel UseEndpoints est conçu pour être utilisé en tandem avec l’intergiciel
UseRouting . La logique principale pour exécuter un point de terminaison n’est pas

compliquée. Utilisez GetEndpoint pour récupérer le point de terminaison, puis appelez


sa propriété RequestDelegate.

Le code suivant montre comment l’intergiciel peut influencer ou réagir au routage :

C#

app.UseHttpMethodOverride();
app.UseRouting();

app.Use(async (context, next) =>


{
if (context.GetEndpoint()?.Metadata.GetMetadata<RequiresAuditAttribute>
() is not null)
{
Console.WriteLine($"ACCESS TO SENSITIVE DATA AT:
{DateTime.UtcNow}");
}

await next(context);
});

app.MapGet("/", () => "Audit isn't required.");


app.MapGet("/sensitive", () => "Audit required for sensitive data.")
.WithMetadata(new RequiresAuditAttribute());

C#

public class RequiresAuditAttribute : Attribute { }

L’exemple précédent illustre deux concepts importants :

L’intergiciel peut s’exécuter avant UseRouting pour modifier les données sur
lesquelles le routage fonctionne.
Généralement, l’intergiciel qui apparaît avant le routage modifie une propriété
de la demande, telle que UseRewriter, UseHttpMethodOverrideou UsePathBase.
L’intergiciel peut s’exécuter entre UseRouting et UseEndpoints pour traiter les
résultats du routage avant l’exécution du point de terminaison.
Intergiciel qui s’exécute entre UseRouting et UseEndpoints :
Inspecte généralement les métadonnées pour comprendre les points de
terminaison.
Prend souvent des décisions de sécurité, comme le font UseAuthorization et
UseCors .

La combinaison d’intergiciels et de métadonnées permet de configurer des


stratégies par point de terminaison.

Le code précédent montre un exemple d’intergiciel personnalisé qui prend en charge les
stratégies par point de terminaison. L’intergiciel écrit un journal d’audit de l’accès aux
données sensibles dans la console. L’intergiciel peut être configuré pour auditer un
point de terminaison avec les métadonnées RequiresAuditAttribute . Cet exemple
illustre un modèle d’activation dans lequel seuls les points de terminaison marqués
comme sensibles sont audités. Il est possible de définir l’inverse de cette logique, en
auditant tout ce qui n’est pas marqué comme sécurisé, par exemple. Le système de
métadonnées de point de terminaison est flexible. Cette logique peut être conçue de
quelque manière que ce soit en fonction du cas d’usage.

L’exemple de code précédent est destiné à illustrer les concepts de base des points de
terminaison. L’exemple n’est pas destiné à une utilisation en production. Une version
plus complète d’un intergiciel de journal d’audit :

Se connecterais à un fichier ou une base de données.


Inclurais des détails tels que l’utilisateur, l’adresse IP, le nom du point de
terminaison sensible, etc.

Les métadonnées de stratégie d’audit RequiresAuditAttribute sont définies en tant que


Attribute pour faciliter l’utilisation avec des infrastructures basées sur des classes telles
que des contrôleurs et SignalR. Lors de l’utilisation de route vers le code :

Les métadonnées sont attachées à une API de générateur.


Les infrastructure basées sur des classes incluent tous les attributs sur la méthode
et la classe correspondantes lors de la création de points de terminaison.

Les meilleures pratiques pour les types de métadonnées sont de les définir en tant
qu’interfaces ou attributs. Les interfaces et les attributs autorisent la réutilisation du
code. Le système de métadonnées est flexible et n’impose aucune limitation.

Comparer l’intergiciel terminal avec le routage


L’exemple suivant illustre à la fois l’intergiciel terminal et le routage :

C#

// Approach 1: Terminal Middleware.


app.Use(async (context, next) =>
{
if (context.Request.Path == "/")
{
await context.Response.WriteAsync("Terminal Middleware.");
return;
}

await next(context);
});

app.UseRouting();

// Approach 2: Routing.
app.MapGet("/Routing", () => "Routing.");

Le style d’intergiciel indiqué avec Approach 1: est l’intergiciel terminal. Il est appelé
intergiciel terminal, car il effectue une opération de correspondance :

L’opération de correspondance dans l’exemple précédent est Path == "/" pour


l’intergiciel et Path == "/Routing" pour le routage.
Lorsqu’une correspondance réussit, elle exécute certaines fonctionnalités et
retourne, plutôt que d’appeler l’intergiciel next .

Il est appelé intergiciel de terminal, car il met fin à la recherche, exécute certaines
fonctionnalités, puis retourne.

La liste suivante compare les intergiciels de terminal avec le routage :

Les deux approches permettent de terminer le pipeline de traitement :


L’intergiciel met fin au pipeline en retournant plutôt qu’en appelant next .
Les points de terminaison sont toujours terminaux.
L’intergiciel terminal permet de positionner l’intergiciel à un emplacement
arbitraire dans le pipeline :
Les points de terminaison s’exécutent à la position de UseEndpoints.
L’intergiciel de terminal permet au code arbitraire de déterminer quand l’intergiciel
fait correspondre :
Le code de correspondance de routage personnalisé peut être détaillé et
difficile à écrire correctement.
Le routage fournit des solutions simples pour les applications classiques. La
plupart des applications ne nécessitent pas de code de correspondance de
routage personnalisé.
L’interface des points de terminaison avec un intergiciel tel que UseAuthorization
et UseCors .
L’utilisation d’un intergiciel terminal avec UseAuthorization ou UseCors
nécessite une interaction manuelle avec le système d’autorisation.
Un point de terminaison définit les :

Le délégué pour traiter les demandes.


La collection de métadonnées arbitraires. Les métadonnées sont utilisées pour
implémenter des problèmes transversaux basés sur des stratégies et une
configuration attachées à chaque point de terminaison.

L’intergiciel terminal peut être un outil efficace, mais peut nécessiter :

Une quantité importante de codage et de test.


L’intégration manuelle avec d’autres systèmes pour atteindre le niveau de flexibilité
souhaité.

Envisagez d’intégrer le routage avant d’écrire un intergiciel terminal.

Les intergiciels terminaux existants qui s’intègrent à Map ou MapWhen peuvent


généralement être transformés en point de terminaison prenant en charge le routage.
MapHealthChecks illustre le modèle de routeur-ware :

Écrire une méthode d’extension sur IEndpointRouteBuilder.


Créer un pipeline d’intergiciels imbriqués à l’aide de CreateApplicationBuilder.
Attacher l’intergiciel au nouveau pipeline. Dans ce cas, UseHealthChecks.
Build le pipeline d’intergiciel dans un RequestDelegate.
Appeler Map et fournir le nouveau pipeline d’intergiciels.
Retourner l’objet générateur fourni par Map à partir de la méthode d’extension.

Le code suivant montre l’utilisation de MapHealthChecks :

C#

app.UseAuthentication();
app.UseAuthorization();

app.MapHealthChecks("/healthz").RequireAuthorization();

L’exemple précédent montre pourquoi le retour de l’objet générateur est important. Le


renvoi de l’objet générateur permet au développeur d’applications de configurer des
stratégies telles que l’autorisation pour le point de terminaison. Dans cet exemple,
l’intergiciel de contrôle d’intégrité n’a pas d’intégration directe avec le système
d’autorisation.

Le système de métadonnées a été créé en réponse aux problèmes rencontrés par les
auteurs d’extensibilité à l’aide de l’intergiciel terminal. Il est problématique pour chaque
intergiciel d’implémenter sa propre intégration avec le système d’autorisation.
Correspondance d’URL
La correspondance d’URL est le processus par lequel le routage distribue une
requête entrante à un point de terminaison.
Est basé sur des données dans le chemin d’URL et les en-têtes.
Peut être étendu pour prendre en compte toutes les données de la demande.

Lorsqu’un intergiciel de routage s’exécute, il définit les valeurs de Endpoint et de


routage et vers une fonctionnalité de requête sur HttpContext à partir de la requête
actuelle :

L’appel de HttpContext.GetEndpoint obtient le point de terminaison.


HttpRequest.RouteValues récupère la collection de valeurs d’itinéraire.

L’intergiciel s’exécute après que l’intergiciel de routage puisse inspecter le point de


terminaison et prendre des mesures. Par exemple, un intergiciel d’autorisation peut
interroger la collection de métadonnées du point de terminaison pour une stratégie
d’autorisation. Une fois que tous les intergiciels dans le pipeline de traitement de
requêtes sont exécutés, le délégué du point de terminaison sélectionné est appelé.

Le système de routage dans le routage de point de terminaison est responsable de


toutes les décisions de distribution. Étant donné que l’intergiciel applique des stratégies
basées sur le point de terminaison sélectionné, il est important que :

Toute décision susceptible d’affecter la répartition ou l’application de stratégies de


sécurité soit prise à l’intérieur du système de routage.

2 Avertissement

Pour une compatibilité descendante, lorsqu’un délégué de point de terminaison


Contrôleur ou Razor Pages est exécuté, les propriétés de RouteContext.RouteData
sont définies sur des valeurs appropriées en fonction du traitement des requêtes
effectué jusqu’à présent.

Le type RouteContext sera marqué comme obsolète dans une version ultérieure :

Migrez RouteData.Values vers HttpRequest.RouteValues .


Migrez RouteData.DataTokens pour récupérer IDataTokensMetadata à partir
des métadonnées du point de terminaison.

La correspondance d’URL fonctionne dans un ensemble configurable de phases. Dans


chaque phase, la sortie est un ensemble de correspondances. L’ensemble de
correspondances peut être réduit plus loin par la phase suivante. L’implémentation du
routage ne garantit pas un ordre de traitement pour les points de terminaison
correspondants. Toutes les correspondances possibles sont traitées simultanément. Les
phases de correspondance d’URL se produisent dans l’ordre suivant. ASP.NET Core :

1. Traite le chemin d’URL par rapport à l’ensemble de points de terminaison et à leurs


modèles de routage, en collectant toutes les correspondances.
2. Prend la liste précédente et supprime les correspondances qui échouent avec les
contraintes de routage appliquées.
3. Prend la liste précédente et supprime les correspondances qui échouent au jeu
d’instances MatcherPolicy.
4. Utilise EndpointSelector pour prendre une décision finale à partir de la liste
précédente.

La liste des points de terminaison est hiérarchisée en fonction des éléments suivants :

RouteEndpoint.Order.
Priorité du modèle de routage

Tous les points de terminaison correspondants sont traités dans chaque phase jusqu’à
ce que EndpointSelector soit atteint. EndpointSelector est la phase finale. Il choisit le
point de terminaison avec la priorité la plus élevée parmi les correspondances comme
correspondance optimale. S’il existe d’autres correspondances avec la même priorité
que la meilleure correspondance, une exception de correspondance ambiguë est levée.

La priorité du routage est calculée en fonction d’un modèle de routage plus spécifique
qui reçoit une priorité plus élevée. Par exemple, considérez les modèles /hello et
/{message} :

Les deux correspondent au chemin d’URL /hello .


/hello est plus spécifique et, par conséquent, a une priorité plus élevée.

En général, la priorité des routages permet de choisir la meilleure correspondance pour


les types de schémas d’URL utilisés dans la pratique. Utilisez Order uniquement si
nécessaire pour éviter une ambiguïté.

En raison des types d’extensibilité fournis par le routage, il n’est pas possible que le
système de routage calcule à l’avance les routages ambigus. Prenons un exemple tel
que les modèles de routage /{message:alpha} et /{message:int} :

La contrainte alpha ne fait correspondre que les caractères alphabétiques.


La contrainte int ne fait correspondre que les nombres.
Ces modèles ont la même priorité de routage, mais il n’existe aucune URL à
laquelle ils correspondent.
Si le système de routage a signalé une erreur d’ambiguïté au démarrage, il bloque
ce cas d’usage valide.

2 Avertissement

L’ordre des opérations à l’intérieur de UseEndpoints n’influence pas le


comportement du routage, à une exception près. MapControllerRoute et
MapAreaRoute attribuent automatiquement une valeur de commande à leurs
points de terminaison en fonction de l’ordre qu’ils appellent. Cela simule le
comportement long des contrôleurs sans le système de routage fournissant les
mêmes garanties que les implémentations de routage plus anciennes.

Routage des points de terminaison dans ASP.NET Core :

N’a pas le concept de routages.


Ne fournit pas de garanties de commande. Tous les points de terminaison
sont traités simultanément.

Priorité du modèle de routage et ordre de sélection du


point de terminaison
La priorité du modèle de routage est un système qui attribue à chaque modèle de
routage une valeur en fonction de sa spécificité. La priorité du modèle de routage :

Évite la nécessité d’ajuster l’ordre des points de terminaison dans les cas courants.
Tente de faire correspondre les attentes courantes du comportement de routage.

Par exemple, envisagez des modèles /Products/List et /Products/{id} . Il serait


raisonnable de supposer que /Products/List est une meilleure correspondance que
/Products/{id} pour le chemin d’URL /Products/List . Cela fonctionne parce que le
segment littéral /List est considéré comme ayant une meilleure priorité que le
segment de paramètre /{id} .

Les détails du fonctionnement de la priorité sont couplés à la façon dont les modèles de
routage sont définis :

Les modèles avec plus de segments sont considérés comme plus spécifiques.
Un segment avec du texte littéral est considéré comme plus spécifique qu’un
segment de paramètre.
Un segment de paramètre avec une contrainte est considéré comme plus
spécifique qu’un segment sans.
Un segment complexe est considéré aussi spécifique qu’un segment de paramètre
avec une contrainte.
Les paramètres catch-all sont les moins spécifiques. Consultez catch-all dans la
section Modèles de routage pour obtenir des informations importantes sur les
routages catch-all.

Concepts de génération d’URL


La génération des URL :

Est le processus par lequel le routage peut créer un chemin d’URL basé sur un
ensemble de valeurs de route.
Permet une séparation logique entre les points de terminaison et les URL qui y
accèdent.

Le routage des points de terminaison inclut l’API LinkGenerator. LinkGenerator est un


service singleton disponible à partir de DI. L’API LinkGenerator peut être utilisée en
dehors du contexte d’une requête en cours d’exécution. Mvc.IUrlHelper et les scénarios
qui s’appuient sur IUrlHelper, comme l’Assistance des balises, l’assistance HTML et les
résultats d’action, utilisent l’API LinkGenerator pour fournir les fonctionnalités de
création de liens.

Le générateur de liens est basé sur le concept d’une adresse et de schémas d’adresse.
Un schéma d’adresse est un moyen de déterminer les points de terminaison à prendre
en compte pour la génération de liens. Par exemple, les scénarios de nom de route et de
valeurs de route que de nombreux utilisateurs connaissent bien dans les contrôleurs et
Razor Pages sont implémentés en tant que schémas d’adresse.

Le générateur de liens peut lier à des contrôleurs et Razor Pages via les méthodes
d’extension suivantes :

GetPathByAction
GetUriByAction
GetPathByPage
GetUriByPage

Une surcharge de ces méthodes accepte des arguments qui incluent HttpContext . Ces
méthodes sont fonctionnellement équivalentes à Url.Action et à Url.Page, mais elles
offrent davantage de flexibilité et d’options.
Les méthodes GetPath* sont les plus similaires à Url.Action et Url.Page , car elles
génèrent un URI contenant un chemin d’accès absolu. Les méthodes GetUri* génèrent
toujours un URI absolu contenant un schéma et un hôte. Les méthodes qui acceptent un
HttpContext génèrent un URI dans le contexte de la requête en cours d’exécution. Les

valeurs de route ambiante, le chemin de base d’URL, le schéma et l’hôte de la requête


en cours d’exécution sont utilisés, sauf s’ils sont remplacés.

LinkGenerator est appelé avec une adresse. La génération d’un URI se fait en deux
étapes :

1. Une adresse est liée à une liste de points de terminaison qui correspondent à
l’adresse.
2. Le RoutePattern de chaque point de terminaison est évalué jusqu’à ce qu’un
modèle de route correspondant aux valeurs fournies soit trouvé. Le résultat obtenu
est combiné avec d’autres parties de l’URI fournies par le générateur de liens, puis
il est retourné.

Les méthodes fournies par LinkGenerator prennent en charge des fonctionnalités de


génération de liens standard pour n’importe quel type d’adresse. La façon la plus
pratique d’utiliser le générateur de liens est de le faire via des méthodes d’extension qui
effectuent des opérations pour un type d’adresse spécifique :

Méthode d’extension Description

GetPathByAddress Génère un URI avec un chemin absolu basé sur les valeurs fournies.

GetUriByAddress Génère un URI absolu basé sur les valeurs fournies.

2 Avertissement

Faites attention aux implications suivantes de l’appel de méthodes LinkGenerator :

Utilisez les méthodes d’extension GetUri* avec précaution dans une


configuration d’application qui ne valide pas l’en-tête Host des requêtes
entrantes. Si l’en-tête Host des requêtes entrantes n’est pas validé, l’entrée de
requête non approuvée peut être renvoyée au client dans les URI d’une page
ou d’une vue. Nous recommandons que toutes les applications de production
configurent leur serveur pour qu’il valide l’en-tête Host par rapport à des
valeurs valides connues.

Utilisez LinkGenerator avec précaution dans le middleware en combinaison


avec Map ou MapWhen . Map* modifie le chemin de base de la requête en cours
d’exécution, ce qui affecte la sortie de la génération de liens. Toutes les API
LinkGenerator permettent la spécification d’un chemin de base. Spécifiez un
chemin de base vide pour annuler l’effet de Map* sur la génération de liens.

Exemple de middleware
Dans l’exemple suivant, un intergiciel utilise l’API LinkGenerator pour créer un lien vers
une méthode d’action qui liste les produits d’un magasin. L’utilisation du générateur de
liens en l’injectant dans une classe et en appelant GenerateLink est disponible pour
n’importe quelle classe dans une application :

C#

public class ProductsMiddleware


{
private readonly LinkGenerator _linkGenerator;

public ProductsMiddleware(RequestDelegate next, LinkGenerator


linkGenerator) =>
_linkGenerator = linkGenerator;

public async Task InvokeAsync(HttpContext httpContext)


{
httpContext.Response.ContentType = MediaTypeNames.Text.Plain;

var productsPath = _linkGenerator.GetPathByAction("Products",


"Store");

await httpContext.Response.WriteAsync(
$"Go to {productsPath} to see our products.");
}
}

Modèles de route
Les jetons dans {} définissent les paramètres de routage liés si le routage est mis en
correspondance. Plusieurs paramètres de routage peuvent être définis dans un segment
de routage, mais les paramètres de routage doivent être séparés par une valeur littérale.
Par exemple :

{controller=Home}{action=Index}

n’est pas un routage valide, car il n’y a pas de valeur littérale entre {controller} et
{action} . Les paramètres de routage doivent avoir un nom, et ils autorisent la
spécification d’attributs supplémentaires.

Un texte littéral autre que les paramètres de routage (par exemple, {id} ) et le
séparateur de chemin / doit correspondre au texte présent dans l’URL. La
correspondance de texte ne respecte pas la casse et est basée sur la représentation
décodée du chemin des URL. Pour mettre en correspondance un délimiteur de
paramètre de route littéral { ou } , placez-le dans une séquence d’échappement en
répétant le caractère. Par exemple {{ ou }} .

Astérisque * ou astérisque double ** :

Peut être utilisé comme préfixe pour un paramètre de routage pour établir une
liaison au reste de l’URI.
Ils sont appelés des paramètres catch-all. Par exemple, blog/{**slug} :
Correspond à n’importe quel URI qui commence par blog/ et a n’importe
quelle valeur qui suit.
La valeur suivant blog/ est affectée à la valeur de routage slug .

Les paramètres fourre-tout peuvent également établir une correspondance avec la


chaîne vide.

Le paramètre catch-all place les caractères appropriés dans une séquence


d’échappement lorsque la route est utilisée pour générer une URL, y compris les
caractères de séparation de chemin / . Par exemple, la route foo/{*path} avec les
valeurs de route { path = "my/path" } génère foo/my%2Fpath . Notez la barre oblique
d’échappement. Pour les séparateurs de chemin aller-retour, utilisez le préfixe de
paramètre de routage ** . La route foo/{**path} avec { path = "my/path" } génère
foo/my/path .

Les modèles d’URL qui tentent de capturer un nom de fichier avec une extension de
fichier facultative doivent faire l’objet de considérations supplémentaires. Prenez par
exemple le modèle files/{filename}.{ext?} . Quand des valeurs existent à la fois pour
filename et pour ext , les deux valeurs sont renseignées. Si seule une valeur existe pour

filename dans l’URL, une correspondance est trouvée pour la route, car le . de fin est

facultatif. Les URL suivantes correspondent à cette route :

/files/myFile.txt
/files/myFile

Les paramètres de route peuvent avoir des valeurs par défaut, désignées en spécifiant
la valeur par défaut après le nom du paramètre, séparée par un signe égal ( = ). Par
exemple, {controller=Home} définit Home comme valeur par défaut de controller . La
valeur par défaut est utilisée si aucune valeur n’est présente dans l’URL pour le
paramètre. Vous pouvez rendre facultatifs les paramètres de route en ajoutant un point
d’interrogation ( ? ) à la fin du nom du paramètre. Par exemple, id? La différence entre
les valeurs facultatives et les paramètres de routage par défaut est la suivante :

Un paramètre de routage avec une valeur par défaut produit toujours une valeur.
Un paramètre facultatif a une valeur uniquement lorsqu’une valeur est fournie par
l’URL de la requête.

Les paramètres de route peuvent avoir des contraintes, qui doivent correspondre à la
valeur de route liée à partir de l’URL. L’ajout de : et d’un nom de contrainte après le
nom du paramètre de routage spécifie une contrainte inline sur un paramètre de
routage. Si la contrainte exige des arguments, ils sont fournis entre parenthèses (...)
après le nom de la contrainte. Il est possible de spécifier plusieurs contraintes inline en
ajoutant un autre : et le nom d’une autre contrainte.

Le nom de la contrainte et les arguments sont passés au service


IInlineConstraintResolver pour créer une instance de IRouteConstraint à utiliser dans le
traitement des URL. Par exemple, le modèle de routage blog/{article:minlength(10)}
spécifie une contrainte minlength avec l’argument 10 . Pour plus d’informations sur les
contraintes de route et pour obtenir la liste des contraintes fournies par le framework,
consultez la section Contraintes de route.

Les paramètres de route peuvent également avoir des transformateurs de paramètres.


Les transformateurs de paramètres transforment la valeur d’un paramètre lors de la
génération de liens et d’actions et de pages correspondantes en URL. À l’instar des
contraintes, les transformateurs de paramètre peuvent être ajoutés inline à un
paramètre de routage en ajoutant un : et le nom du transformateur après le nom du
paramètre de routage. Par exemple, le modèle de routage blog/{article:slugify}
spécifie un transformateur slugify . Pour plus d’informations sur les transformateurs de
paramètre, consultez la section Transformateurs de paramètre.

Le tableau suivant montre des exemples de modèles de route et leur comportement.

Modèle de routage Exemple d’URI en URI de requête


correspondance

hello /hello Correspond seulement au


chemin unique /hello .

{Page=Home} / Correspond à Page et le


définit sur Home .
Modèle de routage Exemple d’URI en URI de requête
correspondance

{Page=Home} /Contact Correspond à Page et le


définit sur Contact .

{controller}/{action}/{id?} /Products/List Mappe au contrôleur


Products et à l’action
List .

{controller}/{action}/{id?} /Products/Details/123 Mappe au contrôleur


Products et à l’action
Details avec id défini
sur 123).

{controller=Home}/{action=Index}/{id?} / Mappe au contrôleur


Home et à l’action Index .
id est ignoré.

{controller=Home}/{action=Index}/{id?} /Products Mappe au contrôleur


Products et à la méthode
Index . id est ignoré.

L’utilisation d’un modèle est généralement l’approche la plus simple pour le routage. Il
est également possible de spécifier des contraintes et des valeurs par défaut hors du
modèle de routage.

Segments complexes
Les segments complexes sont traités en faisant correspondre les délimiteurs littéraux de
droite à gauche de manière non gourmande. Par exemple, [Route("/a{b}c{d}")] est un
segment complexe. Les segments complexes fonctionnent d’une manière particulière
qui doit être comprise pour les utiliser correctement. L’exemple de cette section montre
pourquoi les segments complexes ne fonctionnent vraiment bien que lorsque le texte
du délimiteur n’apparaît pas dans les valeurs des paramètres. L’utilisation d’un regex,
puis l’extraction manuelle des valeurs est nécessaire pour des cas plus complexes.

2 Avertissement

Lorsque vous utilisez System.Text.RegularExpressions pour traiter une entrée non


approuvée, passez un délai d’expiration. Un utilisateur malveillant peut fournir une
entrée à RegularExpressions , provoquant une attaque par déni de service . Les
API d’infrastructure ASP.NET Core qui utilisent RegularExpressions passent un délai
d’expiration.
Il s’agit d’un résumé des étapes effectuées par le routage avec le modèle /a{b}c{d} et le
chemin d’URL /abcd . Un | est utilisé pour vous aider à visualiser le fonctionnement de
l’algorithme :

Le premier littéral, de droite à gauche, est c . Donc /abcd est recherché à partir de
la droite et trouve /ab|c|d .
Tout ce qui se trouve à droite ( d ) est désormais mis en correspondance avec le
paramètre de routage {d} .
Le littéral suivant, de droite à gauche, est a . Donc /ab|c|d est recherché à partir
de là où nous sommes partis, puis a est trouvé /|a|b|c|d .
La valeur à droite ( b ) est désormais associée au paramètre de routage {b} .
Il n’y a pas de texte restant et aucun modèle de routage restant. Il s’agit donc
d’une correspondance.

Voici un exemple de cas négatif utilisant le même modèle /a{b}c{d} et le chemin d’URL
/aabcd . Un | est utilisé pour vous aider à visualiser le fonctionnement de l’algorithme.

Ce cas n’est pas une correspondance, qui est expliquée par le même algorithme :

Le premier littéral, de droite à gauche, est c . Donc /aabcd est recherché à partir de
la droite et trouve /aab|c|d .
Tout ce qui se trouve à droite ( d ) est désormais mis en correspondance avec le
paramètre de routage {d} .
Le littéral suivant, de droite à gauche, est a . Donc /aab|c|d est recherché à partir
de là où nous sommes partis, puis a est trouvé /a|a|b|c|d .
La valeur à droite ( b ) est désormais associée au paramètre de routage {b} .
À ce stade, il reste du texte a , mais l’algorithme n’a plus de modèle de routage à
analyser. Il ne s’agit donc pas d’une correspondance.

Étant donné que l’algorithme correspondant n’est pas gourmand :

Il correspond à la plus petite quantité de texte possible dans chaque étape.


Si la valeur de délimiteur apparaît à l’intérieur des valeurs de paramètre, elle ne
correspond pas.

Les expressions régulières fournissent beaucoup plus de contrôle sur leur


comportement de correspondance.

Correspondance gourmande, également appelée correspondance maximale tente de


trouver la correspondance la plus longue possible dans le texte d’entrée qui satisfait au
modèle d’expression régulière. La correspondance non gourmande, également appelée
correspondance paresseuse, recherche la correspondance la plus courte possible dans le
texte d’entrée qui satisfait au modèle d’expression régulière.
Routage avec des caractères spéciaux
Le routage avec des caractères spéciaux peut entraîner des résultats inattendus. Par
exemple, considérez un contrôleur avec la méthode d’action suivante :

C#

[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null || todoItem.Name == null)


{
return NotFound();
}

return todoItem.Name;
}

Lorsque string id contient les valeurs encodées suivantes, des résultats inattendus
peuvent se produire :

ASCII Encoded

/ %2F

Les paramètres de routage ne sont pas toujours décodés par URL. Ce problème peut
être résolu à l’avenir. Pour plus d’informations, consultez ce problème GitHub ;

Contraintes d'itinéraire
Les contraintes de route s’exécutent quand une correspondance s’est produite pour
l’URL entrante, et le chemin de l’URL est tokenisé en valeurs de route. En général, les
contraintes de routage inspectent la valeur de route associée par le biais du modèle de
routage, et créent une décision true ou false indiquant si la valeur est acceptable.
Certaines contraintes de routage utilisent des données hors de la valeur de route pour
déterminer si la requête peut être routée. Par exemple, HttpMethodRouteConstraint
peut accepter ou rejeter une requête en fonction de son verbe HTTP. Les contraintes
sont utilisées dans le routage des requêtes et la génération des liens.

2 Avertissement
N’utilisez pas de contraintes pour la validation des entrées. Si des contraintes sont
utilisées pour la validation d’entrée, une entrée non valide génère une réponse
introuvable 404 . Une entrée non valide doit produire une demande incorrecte 400
avec un message d’erreur approprié. Les contraintes de route sont utilisées pour
lever l’ambiguïté entre des routes similaires, et non pas pour valider les entrées
d’une route particulière.

Le tableau suivant montre des exemples de contrainte de route et leur comportement


attendu :

contrainte Exemple Exemples de Notes


correspondances

int {id:int} 123456789 , Correspond à n’importe


-123456789 quel entier

bool {active:bool} true , FALSE Correspond à true ou


false . Non-respect de
la casse

datetime {dob:datetime} 2016-12-31 , 2016- Correspond à une valeur


12-31 7:32pm valide DateTime dans la
culture invariante. Voir
l’avertissement
précédent.

decimal {price:decimal} 49.99 , -1,000.01 Correspond à une valeur


valide decimal dans la
culture invariante. Voir
l’avertissement
précédent.

double {weight:double} 1.234 , -1,001.01e8 Correspond à une valeur


valide double dans la
culture invariante. Voir
l’avertissement
précédent.

float {weight:float} 1.234 , -1,001.01e8 Correspond à une valeur


valide float dans la
culture invariante. Voir
l’avertissement
précédent.

guid {id:guid} CD2C1638-1638- Correspond à une valeur


72D5-1638- Guid valide
DEADBEEF1638
contrainte Exemple Exemples de Notes
correspondances

long {ticks:long} 123456789 , Correspond à une valeur


-123456789 long valide

minlength(value) {username:minlength(4)} Rick La chaîne doit


comporter au moins 4
caractères

maxlength(value) {filename:maxlength(8)} MyFile La chaîne ne doit pas


comporter plus de 8
caractères

length(length) {filename:length(12)} somefile.txt La chaîne doit


comporter exactement
12 caractères

length(min,max) {filename:length(8,16)} somefile.txt La chaîne doit


comporter au moins 8
caractères et pas plus de
16 caractères

min(value) {age:min(18)} 19 La valeur entière doit


être au moins égale à 18

max(value) {age:max(120)} 91 La valeur entière ne doit


pas être supérieure à
120

range(min,max) {age:range(18,120)} 91 La valeur entière doit


être au moins égale à 18
mais ne doit pas être
supérieure à 120

alpha {name:alpha} Rick La chaîne doit se


composer d’un ou de
plusieurs caractères
alphabétiques ( a - z ,
non-respect de la casse).

regex(expression) {ssn:regex(^\\d{{3}}- 123-45-6789 La chaîne doit


\\d{{2}}-\\d{{4}}$)} correspondre à
l’expression régulière.
Consultez des conseils
sur la définition d’une
expression régulière.

required {name:required} Rick Utilisé pour garantir


qu’une valeur autre
qu’un paramètre est
contrainte Exemple Exemples de Notes
correspondances

présente pendant la
génération de l’URL

2 Avertissement

Lorsque vous utilisez System.Text.RegularExpressions pour traiter une entrée non


approuvée, passez un délai d’expiration. Un utilisateur malveillant peut fournir une
entrée à RegularExpressions , provoquant une attaque par déni de service . Les
API d’infrastructure ASP.NET Core qui utilisent RegularExpressions passent un délai
d’expiration.

Il est possible d’appliquer plusieurs contraintes séparées par un point-virgule à un


même paramètre. Par exemple, la contrainte suivante limite un paramètre à une valeur
entière supérieure ou égale à 1 :

C#

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

2 Avertissement

Les contraintes de routage qui vérifient que l’URL peut être convertie en type CLR
utilisent toujours la culture invariant. Par exemple, conversion en type CLR int ou
DateTime . Ces contraintes partent du principe que l’URL ne peut pas être localisé.

Les contraintes de routage fournies par le framework ne modifient pas les valeurs
stockées dans les valeurs de route. Toutes les valeurs de route analysées à partir de
l’URL sont stockées sous forme de chaînes. Par exemple, la contrainte float tente
de convertir la valeur de route en valeur float, mais la valeur convertie est utilisée
uniquement pour vérifier qu’elle peut être convertie en valeur float.

Expressions régulières dans les contraintes

2 Avertissement

Lorsque vous utilisez System.Text.RegularExpressions pour traiter une entrée non


approuvée, passez un délai d’expiration. Un utilisateur malveillant peut fournir une
entrée à RegularExpressions , provoquant une attaque par déni de service . Les
API d’infrastructure ASP.NET Core qui utilisent RegularExpressions passent un délai
d’expiration.

Les expressions régulières peuvent être spécifiées en tant que contraintes inline à l’aide
de la contrainte de routage regex(...) . Les méthodes de la famille MapControllerRoute
acceptent également un littéral d’objet de contraintes. Si ce formulaire est utilisé, les
valeurs de chaîne sont interprétées comme des expressions régulières.

Le code suivant utilise une contrainte d’expression régulière inline :

C#

app.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
() => "Inline Regex Constraint Matched");

Le code suivant utilise un littéral d’objet pour spécifier une contrainte d’expression
régulière :

C#

app.MapControllerRoute(
name: "people",
pattern: "people/{ssn}",
constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
defaults: new { controller = "People", action = "List" });

Le framework ASP.NET Core ajoute RegexOptions.IgnoreCase | RegexOptions.Compiled |


RegexOptions.CultureInvariant au constructeur d’expression régulière. Pour obtenir une

description de ces membres, consultez RegexOptions.

Les expressions régulières utilisent les délimiteurs et des jetons semblables à ceux
utilisés par le service de routage et le langage C#. Les jetons d’expression régulière
doivent être placés dans une séquence d’échappement. Pour utiliser l’expression
régulière ^\d{3}-\d{2}-\d{4}$ dans une contrainte inline, utilisez l’une des options
suivantes :

Remplacez les caractères \ fournis dans la chaîne en tant que caractères \\ dans
le fichier source C# afin d’échapper au caractère \ d’échappement de chaîne.
Littéraux de chaîne verbatim.

Pour placer en échappement les caractères de délimiteur de paramètre de route { , } ,


[ , ] , doublez les caractères dans l’expression, par exemple {{ , }} , [[ , ]] . Le tableau
suivant montre une expression régulière et la version placée en échappement :

Expression régulière Expression régulière en échappement

^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$

^[a-z]{2}$ ^[[a-z]]{{2}}$

Les expressions régulières utilisées dans le routage commencent souvent par le


caractère ^ et correspondent à la position de début de la chaîne. Les expressions se
terminent souvent par le caractère $ et correspondent à la fin de la chaîne. Les
caractères ^ et $ garantissent que l’expression régulière établit une correspondance
avec la totalité de la valeur du paramètre de route. Sans les caractères ^ et $ ,
l’expression régulière peut correspondre à n’importe quelle sous-chaîne dans la chaîne,
ce qui est souvent indésirable. Le tableau suivant contient des exemples et explique
pourquoi ils établissent ou non une correspondance :

Expression String Correspond Commentaire

[a-z]{2} hello Oui Correspondances de sous-chaînes

[a-z]{2} 123abc456 Oui Correspondances de sous-chaînes

[a-z]{2} mz Oui Correspondance avec l’expression

[a-z]{2} MZ Oui Non-respect de la casse

^[a-z]{2}$ hello Non Voir ^ et $ ci-dessus

^[a-z]{2}$ 123abc456 Non Voir ^ et $ ci-dessus

Pour plus d’informations sur la syntaxe des expressions régulières, consultez Expressions
régulières du .NET Framework.

Pour contraindre un paramètre à un ensemble connu de valeurs possibles, utilisez une


expression régulière. Par exemple, {action:regex(^(list|get|create)$)} établit une
correspondance avec la valeur de route action uniquement pour list , get ou create .
Si elle est passée dans le dictionnaire de contraintes, la chaîne ^(list|get|create)$ est
équivalente. Les contraintes passées dans le dictionnaire de contraintes qui ne
correspondent pas à l’une des contraintes connues sont également traitées comme des
expressions régulières. Les contraintes passées dans un modèle qui ne correspondent
pas à l’une des contraintes connues ne sont pas traitées comme des expressions
régulières.
Contraintes de routage personnalisées
Les contraintes de routage personnalisées peuvent être créées en implémentant
l’interface IRouteConstraint. L’interface IRouteConstraint contient une méthode unique,
Match, qui retourne true si la contrainte est satisfaite et false dans le cas contraire.

Les contraintes de routage personnalisées sont rarement nécessaires. Avant


d’implémenter une contrainte de routage personnalisée, envisagez des alternatives,
telles que la liaison de modèle.

Le dossier ASP.NET Core Contraintes fournit de bons exemples de création de


contraintes. Par exemple, GuidRouteConstraint .

Pour utiliser un IRouteConstraint personnalisé, le type de contrainte de routage doit


être inscrit avec le ConstraintMap de l’application dans le conteneur de service de
l’application. Un ConstraintMap est un dictionnaire qui mappe les clés de contrainte
d’itinéraire aux implémentations IRouteConstraint qui valident ces contraintes. Le
ConstraintMap d’une application peut être mis à jour dans Program.cs en tant qu’appel

AddRouting ou en configurant RouteOptions directement avec


builder.Services.Configure<RouteOptions> . Par exemple :

C#

builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));

La contrainte précédente est appliquée dans le code suivant :

C#

[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}

L’implémentation de NoZeroesRouteConstraint empêche l’utilisation de 0 dans un


paramètre de routage :

C#
public class NoZeroesRouteConstraint : IRouteConstraint
{
private static readonly Regex _regex = new(
@"^[1-9]*$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));

public bool Match(


HttpContext? httpContext, IRouter? route, string routeKey,
RouteValueDictionary values, RouteDirection routeDirection)
{
if (!values.TryGetValue(routeKey, out var routeValue))
{
return false;
}

var routeValueString = Convert.ToString(routeValue,


CultureInfo.InvariantCulture);

if (routeValueString is null)
{
return false;
}

return _regex.IsMatch(routeValueString);
}
}

2 Avertissement

Lorsque vous utilisez System.Text.RegularExpressions pour traiter une entrée non


approuvée, passez un délai d’expiration. Un utilisateur malveillant peut fournir une
entrée à RegularExpressions , provoquant une attaque par déni de service . Les
API d’infrastructure ASP.NET Core qui utilisent RegularExpressions passent un délai
d’expiration.

Le code précédent :

Empêche 0 dans le segment {id} de la route.


S’affiche pour fournir un exemple de base d’implémentation d’une contrainte
personnalisée. Il ne doit pas être utilisé dans une application de production.

Le code suivant est une meilleure approche pour empêcher un id contenant un 0


d’être traité :

C#
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}

return Content(id);
}

Le code précédent présente les avantages suivants sur l’approche


NoZeroesRouteConstraint :

Il ne nécessite pas de contrainte personnalisée.


Il retourne une erreur plus descriptive lorsque le paramètre de routage inclut 0 .

Les transformateurs de paramètres


Transformateurs de paramètre :

Sont exécutés lors de la génération d’un lien à l’aide de LinkGenerator.


Implémentez Microsoft.AspNetCore.Routing.IOutboundParameterTransformer.
Sont configurés à l’aide de ConstraintMap.
Prennent la valeur de routage du paramètre et la convertissent en une nouvelle
valeur de chaîne.
Aboutissent à l’utilisation de la valeur transformée dans le lien généré.

Par exemple, un transformateur de paramètre slugify personnalisé dans le modèle


d’itinéraire blog\{article:slugify} avec Url.Action(new { article = "MyTestArticle"
}) génère blog\my-test-article .

Examinez l’implémentation suivante IOutboundParameterTransformer :

C#

public class SlugifyParameterTransformer : IOutboundParameterTransformer


{
public string? TransformOutbound(object? value)
{
if (value is null)
{
return null;
}

return Regex.Replace(
value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100))
.ToLowerInvariant();
}
}

Pour utiliser un transformateur de paramètre dans un modèle d’itinéraire, configurez-le


d’abord en utilisant ConstraintMap dans Program.cs :

C#

builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));

L’infrastructure ASP.NET Core utilise des transformateurs de paramètres pour


transformer l’URI où un point de terminaison est résolu. Par exemple, les
transformateurs de paramètres transforment les valeurs de routage utilisées pour faire
correspondre un area , controller , action et page :

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");

Avec le modèle de routage précédent, l’action


SubscriptionManagementController.GetAll est mise en correspondance avec l’URI
/subscription-management/get-all . Un transformateur de paramètre ne modifie pas les

valeurs de routage utilisées pour générer un lien. Par exemple, Url.Action("GetAll",


"SubscriptionManagement") produit /subscription-management/get-all .

ASP.NET Core fournit des conventions d’API pour l’utilisation des transformateurs de
paramètre avec des routages générés :

La convention
Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention
MVC applique un transformateur de paramètres spécifié à tous les routages
d’attributs de l’application. Le transformateur de paramètre transforme les jetons
de routage d’attribut quand ils sont remplacés. Pour plus d’informations, consultez
Utiliser un transformateur de paramètre pour personnaliser le remplacement des
jetons.
Razor Pages utilise la convention d’API PageRouteTransformerConvention. Cette
convention applique un transformateur de paramètre spécifié à toutes les pages
Razor découvertes automatiquement. Le transformateur de paramètre transforme
les segments du nom de dossier et du nom de fichier des routes Razor Pages. Pour
plus d’informations, consultez Utiliser un transformateur de paramètre pour
personnaliser les routages de pages.

Informations de référence sur la génération


d’URL
Cette section contient une référence pour l’algorithme implémenté par génération
d’URL. Dans la pratique, les exemples les plus complexes de génération d’URL utilisent
des contrôleurs ou Razor Pages. Pour plus d’informations, consultez Routage dans les
contrôleurs.

Le processus de génération d’URL commence par un appel à


LinkGenerator.GetPathByAddress ou une méthode similaire. La méthode est fournie avec
une adresse, un ensemble de valeurs de routage et éventuellement des informations sur
la requête actuelle de HttpContext .

La première étape consiste à utiliser l’adresse pour résoudre un ensemble de points de


terminaison candidats à l’aide d’un IEndpointAddressScheme<TAddress> correspondant
au type de l’adresse.

Une fois que l’ensemble de candidats est trouvé par le schéma d’adresses, les points de
terminaison sont classés et traités de manière itérative jusqu’à ce qu’une opération de
génération d’URL réussisse. La génération d’URL ne vérifie pas les ambiguïtés, le
premier résultat retourné est le résultat final.

Résolution des problèmes de génération d’URL avec la


journalisation
La première étape de la résolution des problèmes de génération d’URL consiste à définir
le niveau de journalisation de Microsoft.AspNetCore.Routing sur TRACE . LinkGenerator
enregistre de nombreux détails sur son traitement, ce qui peut être utile pour résoudre
les problèmes.

Consultez Référence de génération d’URL pour plus d’informations sur la génération


d’URL.
Adresses
Les adresses sont le concept de génération d’URL utilisé pour lier un appel au
générateur de liens à un ensemble de points de terminaison candidats.

Les adresses sont un concept extensible qui comprend deux implémentations par défaut
:

Utilisation du nom du point de terminaison ( string ) comme adresse :


Fournit des fonctionnalités similaires au nom du routage de MVC.
Utilise le type de métadonnées IEndpointNameMetadata.
Résout la chaîne fournie par rapport aux métadonnées de tous les points de
terminaison inscrits.
Lève une exception au démarrage si plusieurs points de terminaison utilisent le
même nom.
Recommandé pour une utilisation à usage général en dehors des contrôleurs et
de Razor Pages.
Utilisation des valeurs de route (RouteValuesAddress) comme adresse :
Fournit des fonctionnalités similaires à la génération d’URL hérité des
contrôleurs et de Razor Pages.
Très complexe à étendre et à déboguer.
Fournit l’implémentation utilisée par IUrlHelper , l’assistance des balises,
l’assistance HTML , Résultats d’action, etc.

Le rôle du schéma d’adresses consiste à faire l’association entre l’adresse et les points de
terminaison correspondants selon des critères arbitraires :

Le schéma de noms de point de terminaison effectue une recherche de


dictionnaire de base.
Le schéma de valeurs de route a un sous-ensemble complexe de l’algorithme
défini.

Valeurs ambiantes et valeurs explicites


À partir de la requête actuelle, le routage accède aux valeurs de routage de la requête
HttpContext.Request.RouteValues actuelle. Les valeurs associées à la requête actuelle

sont appelées valeurs ambiantes. À des fins de clarté, la documentation fait référence
aux valeurs de routage transmises aux méthodes en tant que valeurs explicites.

L’exemple suivant montre les valeurs ambiantes et les valeurs explicites. Il fournit des
valeurs ambiantes à partir de la requête actuelle et des valeurs explicites :
C#

public class WidgetController : ControllerBase


{
private readonly LinkGenerator _linkGenerator;

public WidgetController(LinkGenerator linkGenerator) =>


_linkGenerator = linkGenerator;

public IActionResult Index()


{
var indexPath = _linkGenerator.GetPathByAction(
HttpContext, values: new { id = 17 })!;

return Content(indexPath);
}

// ...

Le code précédent :

Retourne /Widget/Index/17 .
Obtient LinkGenerator via DI.

Le code suivant fournit uniquement des valeurs explicites et aucune valeur ambiante :

C#

var subscribePath = _linkGenerator.GetPathByAction(


"Subscribe", "Home", new { id = 17 })!;

La méthode précédente retourne /Home/Subscribe/17

Le code suivant dans le WidgetController retourne /Widget/Subscribe/17 :

C#

var subscribePath = _linkGenerator.GetPathByAction(


HttpContext, "Subscribe", null, new { id = 17 });

Le code suivant fournit au contrôleur des valeurs ambiantes dans la requête actuelle et
des valeurs explicites :

C#

public class GadgetController : ControllerBase


{
public IActionResult Index() =>
Content(Url.Action("Edit", new { id = 17 })!);
}

Dans le code précédent :

/Gadget/Edit/17 est retourné.

Url obtient IUrlHelper.


Action génère une URL avec un chemin absolu pour une méthode d’action. L’URL
contient le nom de action spécifié et les valeurs route .

Le code suivant fournit des valeurs ambiantes à partir de la requête actuelle et des
valeurs explicites :

C#

public class IndexModel : PageModel


{
public void OnGet()
{
var editUrl = Url.Page("./Edit", new { id = 17 });

// ...
}
}

Le code précédent définit url sur /Edit/17 lorsque la page Razor Modifier contient la
directive de page suivante :

@page "{id:int}"

Si la page Modifier ne contient pas le modèle de route "{id:int}" , url est /Edit?
id=17 .

Le comportement de l'IUrlHelper de MVC ajoute une couche de complexité en plus des


règles décrites ici :

IUrlHelper fournit toujours les valeurs de routage de la requête actuelle en tant

que valeurs ambiantes.


IUrlHelper.Action copie toujours les valeurs actuelles action et controller de
routage en tant que valeurs explicites, sauf substitution par le développeur.
IUrlHelper.Page copie toujours la valeur de routage actuelle page en tant que
valeur explicite, sauf si elle est remplacée.
IUrlHelper.Page remplace toujours la valeur de route handler actuelle par null

comme valeurs explicites, sauf substitution.


Les utilisateurs sont souvent surpris par les détails comportementaux des valeurs
ambiantes, car MVC ne semble pas suivre ses propres règles. Pour des raisons
historiques et de compatibilité, certaines valeurs de routage telles que action ,
controller , page et handler ont leur propre comportement de cas spécial.

La fonctionnalité équivalente fournie par LinkGenerator.GetPathByAction et


LinkGenerator.GetPathByPage duplique ces anomalies de IUrlHelper pour la

compatibilité.

Processus de génération d’URL


Une fois l’ensemble de points de terminaison candidats trouvés, l’algorithme de
génération d’URL :

Traite les points de terminaison de manière itérative.


Retourne le premier résultat réussi.

La première étape de ce processus est appelée invalidation des valeurs de routage.


L’invalidation des valeurs de routage est le processus par lequel le routage détermine
les valeurs de routage des valeurs ambiantes à utiliser et qui doivent être ignorées.
Chaque valeur ambiante est considérée et combinée aux valeurs explicites ou ignorée.

La meilleure façon de penser au rôle des valeurs ambiantes est qu’elles tentent
d’enregistrer la saisie par les développeurs d’applications, dans certains cas courants.
Traditionnellement, les scénarios où les valeurs ambiantes sont utiles sont liés à MVC :

Lors de la liaison à une autre action dans le même contrôleur, le nom du


contrôleur n’a pas besoin d’être spécifié.
Lors de la liaison à un autre contrôleur dans la même zone, le nom de la zone n’a
pas besoin d’être spécifié.
Lors de la liaison à la même méthode d’action, les valeurs de routage n’ont pas
besoin d’être spécifiées.
Lors de la liaison à une autre partie de l’application, vous ne souhaitez pas
transporter les valeurs de routage qui n’ont aucune signification dans cette partie
de l’application.

Les appels à ou LinkGenerator qui retournent IUrlHelper sont généralement dus à


null une non-compréhension de l’invalidation de la valeur de route. Résolvez les

problèmes d’invalidation des valeurs de routage en spécifiant explicitement davantage


de valeurs de routage pour voir si cela résout le problème.

L’invalidation de la valeur de routage repose sur l’hypothèse que le schéma d’URL de


l’application est hiérarchique, avec une hiérarchie formée de gauche à droite.
Considérez le modèle de route de contrôleur de base {controller}/{action}/{id?}
pour avoir un sens intuitif de la façon dont cela fonctionne dans la pratique. Une
modification apportée à une valeur invalide toutes les valeurs de routage qui
apparaissent à droite. Cela reflète l’hypothèse sur la hiérarchie. Si l’application a une
valeur ambiante pour id , et que l’opération spécifie une valeur différente pour
controller :

id ne sera pas réutilisée, car {controller} est à gauche de {id?} .

Voici quelques exemples illustrant ce principe :

Si les valeurs explicites contiennent une valeur pour id , la valeur ambiante de id


est ignorée. Les valeurs ambiantes de controller et action peuvent être utilisées.
Si les valeurs explicites contiennent une valeur pour action , toute valeur ambiante
de action est ignorée. Les valeurs ambiantes de controller peuvent être utilisées.
Si la valeur explicite de action est différente de la valeur ambiante de action , la
valeur id ne sera pas utilisée. Si la valeur explicite de action est identique à la
valeur ambiante de action , la valeur id peut être utilisée.
Si les valeurs explicites contiennent une valeur de controller , toute valeur
ambiante de controller est ignorée. Si la valeur explicite de controller est
différente de la valeur ambiante de controller , les valeurs action et id ne seront
pas utilisées. Si la valeur explicite de controller est identique à la valeur ambiante
de controller , les valeurs action et id peuvent être utilisées.

Ce processus est encore plus compliqué à cause de l’existence de routes d’attributs et


de routes conventionnelles dédiées. Les routes conventionnelles de contrôleur tels que
{controller}/{action}/{id?} spécifient une hiérarchie à l’aide de paramètres de

routage. Pour les routages conventionnels dédiés et les routes d’attribut aux contrôleurs
et à Razor Pages :

Il existe une hiérarchie de valeurs de routage.


Elles n’apparaissent pas dans le modèle.

Dans ce cas, la génération d’URL définit le concept de valeurs requises. Les points de
terminaison créés par les contrôleurs et Razor Pages ont des valeurs requises spécifiées
qui autorisent l’invalidation de la valeur de routage à fonctionner.

Algorithme d’invalidation de valeur de routage en détail :

Les noms de valeurs requis sont combinés avec les paramètres de routage, puis
traités de gauche à droite.
Pour chaque paramètre, la valeur ambiante et la valeur explicite sont comparées :
Si la valeur ambiante et la valeur explicite sont identiques, le processus
continue.
Si la valeur ambiante est présente et que la valeur explicite ne l’est pas, la valeur
ambiante est utilisée lors de la génération de l’URL.
Si la valeur ambiante n’est pas présente et que la valeur explicite l’est, rejetez la
valeur ambiante et toutes les valeurs ambiantes suivantes.
Si la valeur ambiante et la valeur explicite sont présentes et que les deux valeurs
sont différentes, rejetez la valeur ambiante et toutes les valeurs ambiantes
suivantes.

À ce stade, l’opération de génération d’URL est prête à évaluer les contraintes de


routage. L’ensemble de valeurs acceptées est combiné aux valeurs par défaut des
paramètres, qui sont fournies aux contraintes. Si les contraintes passent toutes,
l’opération se poursuit.

Ensuite, les valeurs acceptées peuvent être utilisées pour développer le modèle de
routage. Le modèle de routage est traité :

De gauche à droite.
Chaque paramètre a sa valeur acceptée remplacée.
Avec les cas spéciaux suivants :
S’il manque une valeur aux valeurs acceptées et que le paramètre a une valeur
par défaut, la valeur par défaut est utilisée.
S’il manque une valeur aux valeurs acceptées et que le paramètre est facultatif,
le traitement se poursuit.
Si un paramètre de routage à droite d’un paramètre facultatif manquant a une
valeur, l’opération échoue.
Les paramètres par défaut contigus et les paramètres facultatifs sont réduits si
possible.

Les valeurs fournies explicitement mais qui n’ont pas de correspondance avec un
segment de la route sont ajoutées à la chaîne de requête. Le tableau suivant présente le
résultat en cas d’utilisation du modèle de routage {controller}/{action}/{id?} .

Valeurs ambiantes Valeurs explicites Résultat

controller = « Home » action = "About" /Home/About

controller = « Home » controller = "Order", action = /Order/About


"About"

controller = « Home », color = « action = "About" /Home/About


Red »
Valeurs ambiantes Valeurs explicites Résultat

controller = « Home » action = "About", color = "Red" /Home/About?


color=Red

Ordre des paramètres d’itinéraire facultatif


Les paramètres d’itinéraire facultatifs doivent être fournis après tous les littéraux et
paramètres d’itinéraire requis. Dans le code suivant, les paramètres id et name doivent
venir après le paramètre color :

C#

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers;

[Route("api/[controller]")]
public class MyController : ControllerBase
{
// GET /api/my/red/2/joe
// GET /api/my/red/2
// GET /api/my
[HttpGet("{color}/{id:int?}/{name?}")]
public IActionResult GetByIdAndOptionalName(string color, int id = 1,
string? name = null)
{
return Ok($"{color} {id} {name ?? ""}");
}
}

Problèmes liés à l’invalidation des valeurs de routage


Le code suivant montre un exemple de schéma de génération d’URL qui n’est pas pris
en charge par le routage :

C#

app.MapControllerRoute(
"default",
"{culture}/{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
"blog",
"{culture}/{**slug}",
new { controller = "Blog", action = "ReadPost" });
Dans le code précédent, le paramètre de routage culture est utilisé pour la localisation.
On veut que le paramètre culture soit toujours accepté comme valeur ambiante.
Toutefois, le paramètre culture n’est pas accepté comme valeur ambiante en raison de
la façon dont les valeurs requises fonctionnent :

Dans le modèle de routage "default" , le paramètre de routage culture est à


gauche de controller . Les modifications apportées à controller n’invalident
donc pas culture .
Dans le modèle de routage "blog" , le paramètre de routage culture est considéré
comme à droite de controller , qui apparaît dans les valeurs requises.

Analyser les chemins d’URL avec LinkParser


La classe LinkParser ajoute la prise en charge de l’analyse d’un chemin d’URL dans un
ensemble de valeurs de routage. La méthode ParsePathByEndpointName prend un nom
de point de terminaison et un chemin d’URL et retourne un ensemble de valeurs de
routage extraites du chemin d’URL.

Dans l’exemple de contrôleur suivant, l’action GetProduct utilise un modèle de routage


de api/Products/{id} et a un Name de GetProduct :

C#

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...

Dans la même classe de contrôleur, l’action AddRelatedProduct attend un chemin d’URL,


pathToRelatedProduct , qui peut être fourni en tant que paramètre de chaîne de requête

C#

[HttpPost("{id}/Related")]
public IActionResult AddRelatedProduct(
string id, string pathToRelatedProduct, [FromServices] LinkParser
linkParser)
{
var routeValues = linkParser.ParsePathByEndpointName(
nameof(GetProduct), pathToRelatedProduct);
var relatedProductId = routeValues?["id"];

// ...

Dans l’exemple précédent, l’action AddRelatedProduct extrait la valeur de route id du


chemin d’URL. Par exemple, avec un chemin d’URL de /api/Products/1 , la valeur
relatedProductId est définie sur 1 . Cette approche permet aux clients de l’API d’utiliser

des chemins d’URL lorsque vous faites référence à des ressources, sans avoir à connaître
la façon dont cette URL est structurée.

Configurer les métadonnées de point de


terminaison
Les liens suivants fournissent des informations sur la configuration des métadonnées de
point de terminaison :

Activer Cors avec le routage des points de terminaison


Exemple IAuthorizationPolicyProvider à l’aide d’un attribut personnalisé
[MinimumAgeAuthorize]

Tester l’authentification avec l’attribut [Authorize]


RequireAuthorization
Sélection du schéma avec l’attribut [Authorize]
Appliquer des stratégies à l’aide de l’attribut [Authorize]
Autorisation basée sur les rôles dans ASP.NET Core

Correspondance de l’hôte dans les routages


avec RequireHost
RequireHost applique une contrainte au routage qui nécessite l’hôte spécifié. Le
paramètre RequireHost ou [Host] peut être un :

Hôte : www.domain.com , fait correspondre www.domain.com à n’importe quel port.


Hôte avec caractère générique : *.domain.com , fait correspondre www.domain.com ,
subdomain.domain.com ou www.subdomain.domain.com sur n’importe quel port.

Port : *:5000 , fait correspondre le port 5000 avec n’importe quel hôte.
Hôte et port : www.domain.com:5000 ou *.domain.com:5000 , fait correspondre l’hôte
et le port.
Plusieurs paramètres peuvent être spécifiés à l’aide RequireHost ou [Host] . La
contrainte fait correspondre les hôtes valides pour l’un des paramètres. Par exemples,
[Host("domain.com", "*.domain.com")] fait correspondre domain.com , www.domain.com et
subdomain.domain.com .

Le code suivant utilise RequireHost pour exiger l’hôte spécifié sur le routage :

C#

app.MapGet("/", () => "Contoso").RequireHost("contoso.com");


app.MapGet("/", () => "AdventureWorks").RequireHost("adventure-works.com");

app.MapHealthChecks("/healthz").RequireHost("*:8080");

Le code suivant utilise l’attribut [Host] sur le contrôleur pour exiger l’un des hôtes
spécifiés :

C#

[Host("contoso.com", "adventure-works.com")]
public class HostsController : Controller
{
public IActionResult Index() =>
View();

[Host("example.com")]
public IActionResult Example() =>
View();
}

Lorsque l’attribut [Host] est appliqué à la fois au contrôleur et à la méthode d’action :

L’attribut de l’action est utilisé.


L’attribut du contrôleur est ignoré.

2 Avertissement

L’API qui s’appuie sur l’en-tête d’hôte , comme HttpRequest.Host et


RequireHost, est soumise à une usurpation potentielle par les clients.

Pour éviter l’usurpation d’hôte ou de port, utilisez l’une des approches suivantes :

Utilisez HttpContext.Connection (ConnectionInfo.LocalPort) où les ports sont


vérifiés.
Utilisez le filtrage d’hôtes.

Groupes de routes
La méthode d’extension MapGroup permet d’organiser des groupes de points de
terminaison avec un préfixe commun. Cela réduit le code répétitif et permet de
personnaliser des groupes entiers de points de terminaison avec un seul appel à des
méthodes comme RequireAuthorization et WithMetadata, qui ajoutent des
métadonnées de point de terminaison.

Par exemple, le code suivant crée deux groupes de points de terminaison similaires :

C#

app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");

app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();

EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext
factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;

foreach (var argument in factoryContext.MethodInfo.GetParameters())


{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}

// Skip filter if the method doesn't have a TodoDb parameter.


if (dbContextIndex < 0)
{
return next;
}

return async invocationContext =>


{
var dbContext = invocationContext.GetArgument<TodoDb>
(dbContextIndex);
dbContext.IsPrivate = true;

try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise
reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}

C#

public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)


{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);

return group;
}

Dans ce scénario, vous pouvez utiliser une adresse relative pour l’en-tête Location dans
le résultat 201 Created :

C#

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb


database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();

return TypedResults.Created($"{todo.Id}", todo);


}

Le premier groupe de points de terminaison correspond uniquement aux requêtes


précédées de /public/todos , accessibles sans authentification. Le second groupe de
points de terminaison correspond uniquement aux requêtes préfixées par
/private/todos , qui nécessitent une authentification.
La QueryPrivateTodos fabrique de filtres de point de terminaison est une fonction locale
qui modifie les paramètres TodoDb du gestionnaire de routes pour autoriser l’accès aux
données de tâche privées et les stocker.

Les groupes de routage prennent également en charge les groupes imbriqués et les
modèles de préfixe complexes avec des contraintes et des paramètres de routage. Dans
l’exemple suivant, un gestionnaire de routage mappé au groupe user peut capturer les
paramètres de routage {org} et {group} définis dans les préfixes de groupe externe.

Le préfixe peut également être vide. Cela peut être utile pour ajouter des métadonnées
ou des filtres de point de terminaison à un groupe de points de terminaison sans
modifier le modèle de routage.

C#

var all = app.MapGroup("").WithOpenApi();


var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

L’ajout de filtres ou de métadonnées à un groupe se comporte de la même façon que si


vous les ajoutiez individuellement à chaque point de terminaison avant d’ajouter des
filtres ou des métadonnées supplémentaires qui ont pu être ajoutés à un groupe interne
ou à un point de terminaison spécifique.

C#

var outer = app.MapGroup("/outer");


var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("/inner group filter");
return next(context);
});

outer.AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("/outer group filter");
return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>


{
app.Logger.LogInformation("MapGet filter");
return next(context);
});
Dans l’exemple ci-dessus, le filtre externe enregistre la requête entrante avant le filtre
interne, même si elle a été ajoutée en deuxième. Étant donné que les filtres ont été
appliqués à différents groupes, l’ordre dans lequel ils ont été ajoutés les uns par rapport
aux autres n’a pas d’importance. Les filtres d’ordre ajoutés sont importants s’ils sont
appliqués au même groupe ou au même point de terminaison spécifique.

Une requête sur /outer/inner/ journalisera les éléments suivants :

CLI .NET

/outer group filter


/inner group filter
MapGet filter

Conseils sur les performances pour le routage


Lorsqu’une application rencontre des problèmes de performances, le routage est
souvent soupçonné comme étant le problème. La raison pour laquelle le routage est
soupçonné est que les infrastructures telles que les contrôleurs et Razor Pages signalent
le temps passé à l’intérieur de l’infrastructure dans leurs messages de journalisation. En
cas de différence significative entre le temps signalé par les contrôleurs et le temps total
de la requête :

Les développeurs éliminent leur code d’application comme source du problème.


Il est courant de supposer que le routage est la cause.

Les performances du routage est testé à l’aide de milliers de points de terminaison. Il est
peu probable qu’une application classique rencontre un problème de performances
simplement en étant trop volumineuse. La cause racine la plus courante des
performances de routage lentes est généralement un intergiciel personnalisé qui se
comporte mal.

Cet exemple de code suivant illustre une technique de base pour affiner la source de
délai :

C#

var logger = app.Services.GetRequiredService<ILogger<Program>>();

app.Use(async (context, next) =>


{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();
logger.LogInformation("Time 1: {ElapsedMilliseconds}ms",
stopwatch.ElapsedMilliseconds);
});

app.UseRouting();

app.Use(async (context, next) =>


{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();

logger.LogInformation("Time 2: {ElapsedMilliseconds}ms",
stopwatch.ElapsedMilliseconds);
});

app.UseAuthorization();

app.Use(async (context, next) =>


{
var stopwatch = Stopwatch.StartNew();
await next(context);
stopwatch.Stop();

logger.LogInformation("Time 3: {ElapsedMilliseconds}ms",
stopwatch.ElapsedMilliseconds);
});

app.MapGet("/", () => "Timing Test.");

Pour le routage temporel :

Entrelacez chaque intergiciel avec une copie de l’intergiciel de minutage indiqué


dans le code précédent.
Ajoutez un identificateur unique pour mettre en corrélation les données de
minutage avec le code.

Il s’agit d’un moyen de base de limiter le délai lorsqu’il est significatif, par exemple, plus
que 10ms . Soustraire Time 2 de Time 1 signale le temps passé à l’intérieur de
l’intergiciel UseRouting .

Le code suivant utilise une approche plus compacte du code de minutage précédent :

C#

public sealed class AutoStopwatch : IDisposable


{
private readonly ILogger _logger;
private readonly string _message;
private readonly Stopwatch _stopwatch;
private bool _disposed;
public AutoStopwatch(ILogger logger, string message) =>
(_logger, _message, _stopwatch) = (logger, message,
Stopwatch.StartNew());

public void Dispose()


{
if (_disposed)
{
return;
}

_logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
_message, _stopwatch.ElapsedMilliseconds);

_disposed = true;
}
}

C#

var logger = app.Services.GetRequiredService<ILogger<Program>>();


var timerCount = 0;

app.Use(async (context, next) =>


{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});

app.UseRouting();

app.Use(async (context, next) =>


{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});

app.UseAuthorization();

app.Use(async (context, next) =>


{
using (new AutoStopwatch(logger, $"Time {++timerCount}"))
{
await next(context);
}
});
app.MapGet("/", () => "Timing Test.");

Fonctionnalités de routage potentiellement coûteuses


La liste suivante fournit un aperçu des fonctionnalités de routage relativement
coûteuses par rapport aux modèles de routage de base :

Expressions régulières : il est possible d’écrire des expressions régulières qui sont
complexes ou qui ont un temps d’exécution long avec une petite quantité
d’entrée.
Segments complexes ( {x}-{y}-{z} ) :
Sont beaucoup plus coûteux que l’analyse d’un segment de chemin d’URL
standard.
Entraînent l’allocation d’un grand nombre de sous-chaînes.
Accès aux données synchrones : de nombreuses applications complexes disposent
d’un accès à la base de données dans le cadre de leur routage. Utilisez des points
d’extensibilité tels que MatcherPolicy et EndpointSelectorContext, qui sont
asynchrones.

Conseils pour les tables de routage volumineuses


Par défaut, ASP.NET Core utilise un algorithme de routage qui échange la mémoire pour
le temps processeur. Cela a l’effet intéressant que le temps de correspondance de la
route dépend uniquement de la longueur du chemin d’accès à mettre en
correspondance et non du nombre de routes. Toutefois, cette approche peut être
potentiellement problématique dans certains cas, lorsque l’application a un grand
nombre de routes (dans les milliers) et qu’il existe un grand nombre de préfixes
variables dans les routes. Par exemple, si les routes ont des paramètres dans les
premiers segments de la route, comme {parameter}/some/literal .

Il est peu probable qu’une application rencontre un problème, sauf si :

Il existe un nombre élevé de routes dans l’application avec ce modèle.


Il existe un grand nombre de routes dans l’application.

Comment déterminer si une application s’exécute dans le problème


de la table de routage volumineuse

Il existe deux symptômes à rechercher :


L’application est lente à démarrer sur la première requête.
Notez que cela est requis, mais pas suffisant. Il existe de nombreux autres
problèmes non liés au routage qui peuvent entraîner un démarrage
d’application lent. Vérifiez la condition ci-dessous pour déterminer avec
précision que l’application se trouve dans cette situation.
L’application consomme beaucoup de mémoire au démarrage et un vidage de
la mémoire affiche un grand nombre d’instances
Microsoft.AspNetCore.Routing.Matching.DfaNode .

Comment résoudre ce problème

Plusieurs techniques et optimisations peuvent être appliquées aux routes qui améliorent
en grande partie ce scénario :

Appliquez des contraintes de routage à vos paramètres, par exemple


{parameter:int} , {parameter:guid} , {parameter:regex(\\d+)} , etc. si possible.

Cela permet à l’algorithme de routage d’optimiser en interne les structures


utilisées pour la correspondance et de réduire considérablement la mémoire
utilisée.
Dans la grande majorité des cas, cela suffit pour revenir à un comportement
acceptable.
Modifiez les routes pour déplacer des paramètres vers des segments ultérieurs
dans le modèle.
Cela réduit le nombre de « chemins » possibles pour correspondre à un point
de terminaison donné un chemin d’accès.
Utilisez une route dynamique et effectuez le mappage sur un contrôleur/page
dynamiquement.
Pour ce faire, vous pouvez utiliser MapDynamicControllerRoute et
MapDynamicPageRoute .

Intergiciel de court-circuit après routage


Lorsque le routage trouve un point de terminaison, il laisse généralement le reste du
pipeline d’intergiciels s’exécuter avant d’appeler la logique de point de terminaison. Les
services peuvent réduire l’utilisation des ressources en filtrant les requêtes connues tôt
dans le pipeline. Utilisez la méthode d’extension ShortCircuit pour laisser le routage
appeler immédiatement la logique de point de terminaison, puis mettre fin à la requête.
Par exemple, un itinéraire donné n’a peut-être pas besoin de passer par
l’authentification ou l’intergiciel CORS. L’exemple suivant montre comment court-
circuiter les requêtes qui correspondent à l’itinéraire /short-circuit :
C#

app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();

La méthode ShortCircuit(IEndpointConventionBuilder, Nullable<Int32>) peut


éventuellement prendre un code d’état.

Utilisez la méthode MapShortCircuit pour configurer le court-circuit pour plusieurs


itinéraires à la fois, en lui transmettant un tableau de paramètres ou des préfixes d’URL.
Par exemple, les navigateurs et les bots sondent souvent des serveurs pour trouver des
chemins connus comme robots.txt et favicon.ico . Si l’application n’a pas ces fichiers,
une ligne de code peut configurer les deux itinéraires :

C#

app.MapShortCircuit(404, "robots.txt", "favicon.ico");

MapShortCircuit retourne IEndpointConventionBuilder afin que des contraintes de

routage supplémentaires telles que le filtrage d’hôte puissent y être ajoutées.

Les méthodes ShortCircuit et MapShortCircuit n’affectent pas l’intergiciel placé avant


UseRouting . La tentative d’utilisation de ces méthodes avec des points de terminaison

qui ont des métadonnées [Authorize] ou [RequireCors] entraîne l’échec des requêtes
avec un InvalidOperationException . Ces métadonnées sont appliquées par ou par les
attributs [Authorize] ou [EnableCors] ou par les méthodes RequireCors ou
RequireAuthorization.

Pour voir l’effet d’un court-circuit de l’intergiciel, définissez la catégorie de journalisation


« Microsoft » sur « Informations » dans appsettings.Development.json :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

Exécutez le code ci-dessous :


C#

var app = WebApplication.Create();

app.UseHttpLogging();

app.MapGet("/", () => "No short-circuiting!");


app.MapGet("/short-circuit", () => "Short circuiting!").ShortCircuit();
app.MapShortCircuit(404, "robots.txt", "favicon.ico");

app.Run();

L’exemple suivant provient des journaux de console générés en exécutant le point de


terminaison / . Il inclut la sortie de l’intergiciel de journalisation :

info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'HTTP: GET /'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'HTTP: GET /'
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
Response:
StatusCode: 200
Content-Type: text/plain; charset=utf-8
Date: Wed, 03 May 2023 21:05:59 GMT
Server: Kestrel
Alt-Svc: h3=":5182"; ma=86400
Transfer-Encoding: chunked

L’exemple suivant provient de l’exécution du point de terminaison /short-circuit . Il ne


contient aucun élément provenant de l’intergiciel de journalisation, car l’intergiciel a été
court-circuité :

info: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[4]
The endpoint 'HTTP: GET /short-circuit' is being executed without
running additional middleware.
info: Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware[5]
The endpoint 'HTTP: GET /short-circuit' has been executed without
running additional middleware.

Conseils pour les auteurs de bibliothèques


Cette section contient des conseils pour les auteurs de bibliothèques qui s’appuient sur
le routage. Ces détails sont destinés à garantir que les développeurs d’applications ont
une bonne expérience à l’aide de bibliothèques et d’infrastructures qui étendent le
routage.

Définir des points de terminaison


Pour créer une infrastructure qui utilise le routage pour la correspondance d’URL,
commencez par définir une expérience utilisateur qui s’appuie sur UseEndpoints.

GÉNÉREZ sur IEndpointRouteBuilder. Cela permet aux utilisateurs de composer votre


infrastructure avec d’autres fonctionnalités ASP.NET Core sans confusion. Chaque
modèle ASP.NET Core inclut le routage. Supposons que le routage est présent et
familier pour les utilisateurs.

C#

// Your framework
app.MapMyFramework(...);

app.MapHealthChecks("/healthz");

RETOURNEZ un type concret scellé à partir d’un appel à MapMyFramework(...) qui


implémente IEndpointConventionBuilder. La plupart des méthodes d’infrastructure
Map... suivent ce modèle. L'interface IEndpointConventionBuilder :

Permet la composition des métadonnées.


Est ciblée par diverses méthodes d’extension.

La déclaration de votre propre type vous permet d’ajouter vos propres fonctionnalités
spécifiques à l’infrastructure au générateur. Vous pouvez encapsuler un générateur
déclaré par l’infrastructure et lui transférer les appels.

C#

// Your framework
app.MapMyFramework(...)
.RequireAuthorization()
.WithMyFrameworkFeature(awesome: true);

app.MapHealthChecks("/healthz");

ENVISAGEZ d’écrire votre propre EndpointDataSource. EndpointDataSource est la


primitive de bas niveau permettant de déclarer et de mettre à jour une collection de
points de terminaison. EndpointDataSource est une API puissante utilisée par les
contrôleurs et Razor Pages.
Les tests de routage ont un exemple de base d’une source de données sans mise à
jour.

ENVISAGEZ d’implémenter GetGroupedEndpoints. Cela donne un contrôle total sur les


conventions de groupe en cours d’exécution et les métadonnées finales sur les points
de terminaison groupés. Par exemple, cela permet aux implémentations personnalisées
EndpointDataSource d’exécuter des filtres de point de terminaisons ajoutés aux groupes.

NE TENTEZ PAS d’inscrire un EndpointDataSource par défaut. Demandez aux utilisateurs


d’inscrire votre infrastructure dans UseEndpoints. La philosophie du routage est que rien
n’est inclus par défaut et que UseEndpoints est l’endroit où inscrire des points de
terminaison.

Création d’un intergiciel intégré au routage


ENVISAGEZ de définir des types de métadonnées en tant qu’interface.

FAITES EN SORTE qu’il soit possible d’utiliser des types de métadonnées en tant
qu’attribut sur des classes et des méthodes.

C#

public interface ICoolMetadata


{
bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => true;
}

Les frameworks tels que les contrôleurs et Razor Pages prennent en charge l’application
d’attributs de métadonnées aux types et méthodes. Si vous déclarez des types de
métadonnées :

Rendez-les accessibles en tant qu’attributs.


La plupart des utilisateurs sont familiarisés avec l’application d’attributs.

La déclaration d’un type de métadonnées en tant qu’interface ajoute une autre couche
de flexibilité :

Les interfaces sont composables.


Les développeurs peuvent déclarer leurs propres types qui combinent plusieurs
stratégies.

FAITES EN SORTE qu’il soit possible de remplacer les métadonnées, comme illustré dans
l’exemple suivant :

C#

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
public void MyCool() { }

[SuppressCoolMetadata]
public void Uncool() { }
}

La meilleure façon de suivre ces instructions consiste à éviter de définir des


métadonnées de marqueur :

Ne recherchez pas simplement la présence d’un type de métadonnées.


Définissez une propriété sur les métadonnées et vérifiez la propriété.

La collection de métadonnées est triée et prend en charge la substitution par priorité.


Dans le cas des contrôleurs, les métadonnées sur la méthode d’action sont les plus
spécifiques.

FAITES EN SORTE que l’intergiciel soit utile avec et sans routage :

C#

app.UseAuthorization(new AuthorizationPolicy() { ... });

// Your framework
app.MapMyFramework(...).RequireAuthorization();

À titre d’exemple de cette recommandation, considérez l’intergiciel UseAuthorization .


L’intergiciel d’autorisation vous permet de passer une stratégie de secours. La stratégie
de secours, si elle est spécifiée, s’applique aux :

Points de terminaison sans stratégie spécifiée.


Requêtes qui ne correspondent pas à un point de terminaison.

Cela rend l’intergiciel d’autorisation utile en dehors du contexte du routage. L’intergiciel


d’autorisation peut être utilisé pour la programmation d’intergiciels traditionnels.

Déboguer les diagnostics


Pour obtenir une sortie de diagnostic de routage détaillée, définissez
Logging:LogLevel:Microsoft sur Debug . Dans l’environnement de développement,
définissez le niveau de journal dans appsettings.Development.json :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

Ressources supplémentaires
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Gérer les erreurs dans ASP.NET Core
Article • 30/11/2023

Par Tom Dykstra

Cet article aborde des approches courantes pour gérer les erreurs dans les applications
web ASP.NET Core. Consultez également Gérer les erreurs dans les API web ASP.NET
Core et Gérer les erreurs dans les applications API minimales.

Page d’exceptions du développeur


La Page d’exception du développeur affiche des informations détaillées sur les exceptions
des requêtes non prises en charge. Les applications ASP.NET Core activent la page
d’exception du développeur par défaut en présence des deux éléments suivants :

Exécution dans l’environnement de développement.


Application créée à l’aide des modèles actuels, c’est-à-dire en utilisant
WebApplication.CreateBuilder. Les applications créées à l’aide de
WebHost.CreateDefaultBuilder doivent activer la page d’exception du développeur
en appelant app.UseDeveloperExceptionPage dans Configure .

La page d’exception du développeur s’exécute au début du pipeline intergiciel, de


manière qu’elle puisse intercepter les exceptions non prises en charge levées dans
l’intergiciel qui suit.

Des informations détaillées sur les exceptions ne doivent pas être affichées
publiquement lorsque l’application s’exécute dans l’environnement de production. Pour
plus d’informations sur la configuration des environnements, consultez Utiliser plusieurs
environnements dans ASP.NET Core.

La page d’exception du développeur peut inclure les informations suivantes sur


l’exception et la requête :

Trace de pile
Paramètres de la chaîne de requête, le cas échéant
Cookie, le cas échéant
En-têtes

Il n’est pas garanti que la page d’exception du développeur fournisse des informations.
Utilisez la journalisation pour obtenir des informations complètes sur l’erreur.
Page Gestionnaire d’exceptions
Pour configurer une page de gestion des erreurs personnalisée en fonction de
l’environnement de production, appelez UseExceptionHandler. Cet intergiciel de gestion
des exceptions :

Intercepte et consigne les exceptions non prises en charge.


Réexécute la requête dans un pipeline de remplacement à l’aide du chemin d’accès
indiqué. La demande n’est pas réexécutée si la réponse a démarré. Le code généré
par le modèle réexécute la requête à l’aide du chemin d’accès /Error .

2 Avertissement

Si le pipeline de remplacement lève une exception qui lui est propre, l’intergiciel de
gestion des exception lève à nouveau l’exception d’origine.

Étant donné que cet intergiciel peut réexécuter le pipeline de requête :

Les intergiciels doivent gérer la réentrance avec la même requête. Cela signifie
normalement le nettoyage de leur état après l’appel _next ou la mise en cache de
leur traitement sur HttpContext pour éviter de le rétablir. Lorsque vous traitez le
corps de la requête, cela peut signifier une mise en mémoire tampon ou une mise
en cache des résultats, comme le lecteur de formulaire.
Pour la surcharge UseExceptionHandler(IApplicationBuilder, String) utilisée dans les
modèles, seul le chemin d’accès de la requête est modifié et les données de
routage sont effacées. Les données de requête telles que les en-têtes, la méthode
et les éléments sont toutes réutilisées telles quelles.
Les services délimités restent les mêmes.

Dans l’exemple suivant, UseExceptionHandler ajoute l’intergiciel de gestion des


exceptions dans des environnements hors développement :

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
Le modèle d’application des pages Razor fournit une page d’erreur ( .cshtml ) et une
classe PageModel ( ErrorModel ) dans le dossier Pages. Pour une application MVC, le
modèle de projet comprend une méthode d’action Error et un affichage d’erreur pour
le contrôleur Home.

L’intergiciel de gestion des exceptions réexécute la requête à l’aide de la méthode HTTP


d’origine. Si un point de terminaison de gestionnaire d’erreurs est limité à un ensemble
spécifique de méthodes HTTP, il ne s’exécute que pour ces méthodes HTTP. Par
exemple, une action de contrôleur MVC qui utilise l’attribut [HttpGet] s’exécute
uniquement pour les requêtes GET. Pour garantir que toutes les requêtes atteignent la
page de gestion des erreurs personnalisée, ne les limitez pas à un ensemble spécifique
de méthodes HTTP.

Pour gérer les exceptions différemment en fonction de la méthode HTTP d’origine :

Pour les pages Razor, créez plusieurs méthodes de gestionnaire. Par exemple,
utilisez OnGet pour gérer les exceptions GET et OnPost pour gérer les exceptions
POST.
Pour MVC, appliquez des attributs de verbe HTTP à plusieurs actions. Vous pouvez
par exemple utiliser [HttpGet] pour gérer les exceptions GET et [HttpPost] pour
gérer les exceptions POST.

Pour autoriser les utilisateurs non authentifiés à afficher la page de gestion des erreurs
personnalisée, assurez-vous qu’elle prend en charge l’accès anonyme.

Accéder à l'exception
Utilisez IExceptionHandlerPathFeature pour accéder à l’exception et au chemin d’accès
de la requête d’origine dans un gestionnaire d’erreurs. L’exemple suivant utilise
IExceptionHandlerPathFeature pour obtenir plus d’informations sur l’exception levée :

C#

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore


= true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

public string? ExceptionMessage { get; set; }

public void OnGet()


{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();

if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
ExceptionMessage = "The file was not found.";
}

if (exceptionHandlerPathFeature?.Path == "/")
{
ExceptionMessage ??= string.Empty;
ExceptionMessage += " Page: Home.";
}
}
}

2 Avertissement

Ne communiquez pas d’informations sensibles sur les erreurs aux clients. Cela
représenterait un risque de sécurité.

Expression lambda Gestionnaire d’exceptions


En dehors d’une page de gestionnaire d’exceptions personnalisée, il est possible de
fournir une expression lambda à UseExceptionHandler, ce qui permet d’accéder à
l’erreur avant de retourner la réponse.

Le code suivant utilise une expression lambda pour la gestion des exceptions :

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode =
StatusCodes.Status500InternalServerError;

// using static System.Net.Mime.MediaTypeNames;


context.Response.ContentType = Text.Plain;

await context.Response.WriteAsync("An exception was thrown.");

var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();

if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
{
await context.Response.WriteAsync(" The file was not
found.");
}

if (exceptionHandlerPathFeature?.Path == "/")
{
await context.Response.WriteAsync(" Page: Home.");
}
});
});

app.UseHsts();
}

2 Avertissement

Ne communiquez pas d’informations sensibles sur les erreurs aux clients. Cela
représenterait un risque de sécurité.

IExceptionHandler
IExceptionHandler est une interface qui donne au développeur un rappel pour la
gestion des exceptions connues dans un emplacement central.

Les implémentations IExceptionHandler sont inscrites en appelant


IServiceCollection.AddExceptionHandler<T> . La durée de vie d’une instance de
IExceptionHandler est singleton. Plusieurs implémentations peuvent être ajoutées et

elles sont appelées dans l’ordre inscrit.


Si un gestionnaire d’exceptions gère une requête, il peut renvoyer true pour arrêter le
traitement. Si une exception n’est gérée par aucun gestionnaire d’exceptions, le contrôle
revient au comportement et aux options par défaut de l’intergiciel. Différentes
métriques et journaux d’activité sont émis pour les exceptions gérées et non gérées.

L'exemple suivant montre une implémentation de IExceptionHandler :

C#

using Microsoft.AspNetCore.Diagnostics;

namespace ErrorHandlingSample
{
public class CustomExceptionHandler : IExceptionHandler
{
private readonly ILogger<CustomExceptionHandler> logger;
public CustomExceptionHandler(ILogger<CustomExceptionHandler>
logger)
{
this.logger = logger;
}
public ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
var exceptionMessage = exception.Message;
logger.LogError(
"Error Message: {exceptionMessage}, Time of occurrence
{time}",
exceptionMessage, DateTime.UtcNow);
// Return false to continue with the default behavior
// - or - return true to signal that this exception is handled
return ValueTask.FromResult(false);
}
}
}

L’exemple suivant montre comment inscrire une implémentation de IExceptionHandler


pour l’injection de dépendances :

C#

using ErrorHandlingSample;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

// Remaining Program.cs code omitted for brevity

Lorsque le code précédent est exécuté dans l'environnement de développement :

La CustomExceptionHandler est appelée en premier pour gérer une exception.


Après avoir enregistré l'exception, la méthode TryHandleException retourne false ,
de sorte que la page d’exception développeur s’affiche.

Dans d’autres environnements :

La CustomExceptionHandler est appelée en premier pour gérer une exception.


Après avoir enregistré l'exception, la méthode TryHandleException retourne false ,
de sorte que la /Errorpage s’affiche.

UseStatusCodePages
Par défaut, une application ASP.NET Core ne fournit pas une page de codes d’état pour
les codes d’état des erreurs HTTP, comme 404 – Introuvable. Lorsque l’application définit
un code d’état d’erreur HTTP 400-599 qui n’a pas de corps, elle retourne le code d’état
et un corps de réponse vide. Pour activer les gestionnaires de texte uniquement par
défaut pour les codes d’état d’erreur courants, appelez UseStatusCodePages dans
Program.cs :

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseStatusCodePages();

Appelez UseStatusCodePages avant l’intergiciel de gestion des requêtes. Par exemple,


appelez UseStatusCodePages avant l’intergiciel de fichier statique et l’intergiciel des
points de terminaison.

Quand UseStatusCodePages n’est pas utilisé, la navigation vers une URL sans point de
terminaison retourne un message d’erreur dépendant du navigateur indiquant que le
point de terminaison est introuvable. Quand UseStatusCodePages est appelé, le
navigateur retourne la réponse suivante :

Console

Status Code: 404; Not Found

UseStatusCodePages n’est généralement pas utilisé en production, car il retourne un

message qui n’est pas utile pour les utilisateurs.

7 Notes

L’intergiciel des pages de codes d’état n’intercepte pas les exceptions. Pour fournir
une page de gestion des erreurs personnalisée, utilisez la page du gestionnaire
d’exception.

UseStatusCodePages avec chaîne de format


Pour personnaliser le texte et le type de contenu de la réponse, utilisez la surcharge de
UseStatusCodePages qui prend un type de contenu et une chaîne de format :

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;


app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

Dans le code précédent, {0} est un espace réservé pour le code d’erreur.

UseStatusCodePages avec une chaîne de format n’est généralement pas utilisé en

production, car il retourne un message qui n’est pas utile pour les utilisateurs.
UseStatusCodePages avec expression lambda
Pour spécifier un code personnalisé de gestion des erreurs et d’écriture de la réponse,
utilisez la surcharge de UseStatusCodePages qui prend une expression lambda :

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>


{
// using static System.Net.Mime.MediaTypeNames;
statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

await statusCodeContext.HttpContext.Response.WriteAsync(
$"Status Code Page:
{statusCodeContext.HttpContext.Response.StatusCode}");
});

UseStatusCodePages avec une expression lambda n’est généralement pas utilisé en

production, car il retourne un message qui n’est pas utile pour les utilisateurs.

UseStatusCodePagesWithRedirects
La méthode d’extension UseStatusCodePagesWithRedirects :

Envoie un code d’état 302 - Trouvé au client.


Redirige le client vers le point de terminaison de gestion des erreurs fourni dans le
modèle d’URL. Le point de terminaison de gestion des erreurs affiche
généralement les informations d’erreur et retourne HTTP 200.

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

Le modèle d’URL peut comporter un espace réservé {0} pour le code d’état, comme
indiqué dans le code précédent. Si le modèle d’URL commence par un ~ (tilde), le ~ est
remplacé par le PathBase de l’application. Lorsque vous spécifiez un point de
terminaison dans l’application, créez une vue MVC ou une page Razor pour le point de
terminaison.

Cette méthode est généralement utilisée lorsque l’application :

Doit rediriger le client vers un autre point de terminaison, généralement dans les
cas où une autre application traite l’erreur. Pour les applications web, la barre
d’adresses du navigateur client reflète le point de terminaison redirigé.
Ne doit pas conserver ni retourner le code d’état d’origine avec la réponse de
redirection initiale.

UseStatusCodePagesWithReExecute
La méthode d’extension UseStatusCodePagesWithReExecute :

Génère le corps de la réponse en réexécutant le pipeline de requête avec un autre


chemin.
Ne modifie pas le code d’état avant ou après la réexécution du pipeline.

L’exécution du nouveau pipeline peut modifier le code d’état de la réponse, car le


nouveau pipeline dispose d’un contrôle total sur le code d’état. Si le nouveau pipeline
ne modifie pas le code d’état, le code d’état d’origine sera envoyé au client.

C#

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

Si un point de terminaison dans l’application est spécifié, créez une vue MVC ou une
page Razor pour le point de terminaison.

Cette méthode est généralement utilisée lorsque l’application doit :


Traiter la demande sans la rediriger vers un autre point de terminaison. Pour les
applications web, la barre d’adresses du navigateur client reflète le point de
terminaison demandé à l’origine.
Conserver et retourner le code d’état d’origine avec la réponse.

Le modèle d’URL doit commencer par / et peut inclure un espace réservé {0} pour le
code d’état. Pour faire passer le code d’état en tant que paramètre de chaîne de
requête, passez un deuxième argument dans UseStatusCodePagesWithReExecute .
Exemple :

C#

var app = builder.Build();


app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

Le point de terminaison qui traite l’erreur peut récupérer l’URL d’origine qui a généré
l’erreur, comme dans l’exemple suivant :

C#

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore


= true)]
public class StatusCodeModel : PageModel
{
public int OriginalStatusCode { get; set; }

public string? OriginalPathAndQuery { get; set; }

public void OnGet(int statusCode)


{
OriginalStatusCode = statusCode;

var statusCodeReExecuteFeature =
HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

if (statusCodeReExecuteFeature is not null)


{
OriginalPathAndQuery = string.Join(
statusCodeReExecuteFeature.OriginalPathBase,
statusCodeReExecuteFeature.OriginalPath,
statusCodeReExecuteFeature.OriginalQueryString);
}
}
}

Étant donné que cet intergiciel peut réexécuter le pipeline de requête :


Les intergiciels doivent gérer la réentrance avec la même requête. Cela signifie
normalement le nettoyage de leur état après l’appel _next ou la mise en cache de
leur traitement sur HttpContext pour éviter de le rétablir. Lorsque vous traitez le
corps de la requête, cela peut signifier une mise en mémoire tampon ou une mise
en cache des résultats, comme le lecteur de formulaire.
Les services délimités restent les mêmes.

Désactiver les pages de codes d’état


Pour désactiver les pages de codes d’état d’un contrôleur MVC ou d’une méthode
d’action, utilisez l’attribut [SkipStatusCodePages].

Pour désactiver les pages de codes d’état pour des requêtes spécifiques dans une
méthode de gestionnaire des pages IStatusCodePagesFeature ou dans un contrôleur
MVC, utilisez Razor :

C#

public void OnGet()


{
var statusCodePagesFeature =
HttpContext.Features.Get<IStatusCodePagesFeature>();

if (statusCodePagesFeature is not null)


{
statusCodePagesFeature.Enabled = false;
}
}

Code de gestion des exceptions


Le code dans les pages de gestion des exceptions peut également lever des exceptions.
Les pages d’erreur de production doivent être testées minutieusement en faisant
particulièrement attention à éviter de lever leurs propres exceptions.

En-têtes de réponse
Une fois les en-têtes d’une réponse envoyés :

L’application ne peut pas modifier le code d’état de la réponse.


Il est impossible d’exécuter les pages d’exception ou les gestionnaires. La réponse
doit être accomplie ou la connexion abandonnée.
Gestion des exceptions de serveur
En plus de la logique de gestion des exceptions d’une application, l’implémentation de
serveur HTTP peut gérer certaines exceptions. Si le serveur intercepte une exception
avant que les en-têtes de réponse ne soient envoyés, le serveur envoie une réponse 500
- Internal Server Error sans corps de réponse. Si le serveur intercepte une exception

une fois que les en-têtes de réponse ont été envoyés, il ferme la connexion. Les
requêtes qui ne sont pas gérées par l’application sont gérées par le serveur. Toute
exception qui se produit tandis que le serveur traite la demande est gérée par le
dispositif de gestion des exceptions du serveur. Ni les pages d’erreur personnalisées de
l’application, ni les intergiciels (middleware) de gestion des exceptions, ni les filtres n’ont
d’incidence sur ce comportement.

Gestion des exceptions de démarrage


Seule la couche d’hébergement peut gérer les exceptions qui se produisent au
démarrage de l’application. L’hôte peut être configuré pour capturer les erreurs de
démarrage et capturer les erreurs détaillées.

La couche d’hébergement ne peut afficher la page d’une erreur de démarrage capturée


que si celle-ci se produit une fois la liaison établie entre l’adresse d’hôte et le port. Si la
liaison échoue :

La couche d’hébergement journalise une exception critique.


Le processus dotnet tombe en panne.
Aucune page d’erreur ne s’affiche lorsque le serveur HTTP est Kestrel.

En cas d’exécution sur IIS (ou Azure App Service) ou IIS Express, une réponse 502.5 -
Échec du processus est retournée par le module ASP.NET Core si le processus ne peut
pas démarrer. Pour plus d’informations, consultez Résoudre les problèmes liés à
ASP.NET Core sur Azure App Service et IIS.

Page d’erreur de base de données


Le filtre d’exception de la page du développeur de la base de données
AddDatabaseDeveloperPageExceptionFilter capture les exceptions liées aux bases de
données qui peuvent être résolues par des migrations Entity Framework Core. Lorsque
ces exceptions se produisent, une réponse HTML est générée, avec les détails des
actions possibles pour résoudre le problème. Cette page n’est activée que dans
l’environnement de développement. Le code suivant ajoute le filtre d’exception de la
page du développeur de la base de données :
C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

Filtres d’exceptions
Dans les applications MVC, vous pouvez configurer les filtres d’exception globalement,
contrôleur par contrôleur ou action par action. Dans les applications des pages Razor, ils
peuvent être configurés globalement ou par modèle de page. Ces filtres gèrent toutes
les exceptions non prises en charge qui se produisent pendant l’exécution d’une action
de contrôleur ou d’un autre filtre. Pour plus d’informations, consultez Filtres dans
ASP.NET Core.

Les filtres d’exception sont utiles pour intercepter les exceptions qui se produisent dans
les actions MVC, mais n’offrent pas la même souplesse que l’intergiciel de gestion
d’exceptions intégré, UseExceptionHandler. Nous recommandons l’utilisation de
UseExceptionHandler , sauf si vous devez gérer les erreurs différemment en fonction de

l’action MVC choisie.

Erreurs d’état de modèle


Pour plus d’informations sur la gestion des erreurs d’état de modèle, voir Liaison de
modèles et Validation de modèles.

Détails du problème
Les détails du problème ne sont pas le seul format de réponse à décrire une erreur
d’API HTTP. Toutefois, ils sont couramment utilisés pour signaler des erreurs pour les API
HTTP.

Le service des détails du problème implémente l’interface IProblemDetailsService, qui


prend en charge la création de détails de problème dans ASP.NET Core. La méthode
d’extension AddProblemDetails sur IServiceCollection enregistre l’implémentation
IProblemDetailsService par défaut.

Dans les applications ASP.NET Core, l’intergiciel suivant génère des réponses HTTP sur
les détails de problème lorsque AddProblemDetails est appelé, sauf si l’en-tête HTTP de
requêteAccept n’inclut pas l’un des types de contenu pris en charge par le
IProblemDetailsWriter inscrit (par défaut : application/json ) :

ExceptionHandlerMiddleware : génère une réponse sur les détails du problème


lorsqu’un gestionnaire personnalisé n’est pas défini.
StatusCodePagesMiddleware : génère une réponse de détails de problème par
défaut.
DeveloperExceptionPageMiddlewareHTTP : génère une réponse sur les détails du
problème lors du développement lorsque l’en-tête HTTP de la requête Accept
n’inclut pas text/html .

Le code suivant configure l’application pour générer une réponse sur les détails du
problème pour toutes les réponses d’erreur de serveur et de client HTTP qui n’ont pas
encore de contenu de corps :

C#

builder.Services.AddProblemDetails();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler();
app.UseHsts();
}

app.UseStatusCodePages();

La section suivante montre comment personnaliser le corps de la réponse sur les détails
du problème.

Personnaliser les détails du problème


La création automatique d’un ProblemDetails peut être personnalisée à l’aide de l’une
des options suivantes :

1. Utilisez ProblemDetailsOptions.CustomizeProblemDetails
2. Utiliser un IProblemDetailsWriter personnalisé
3. Appeler IProblemDetailsService dans un intergiciel

Opération CustomizeProblemDetails
Les détails du problème généré peuvent être personnalisés à l’aide de
CustomizeProblemDetails et les personnalisations sont appliquées à tous les détails du
problème générés automatiquement.

Le code suivant utilise ProblemDetailsOptions pour définir CustomizeProblemDetails :

C#

var app = builder.Build();

builder.Services.AddProblemDetails(options =>
options.CustomizeProblemDetails = ctx =>
ctx.ProblemDetails.Extensions.Add("nodeId",
Environment.MachineName));

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler();
app.UseHsts();
}

app.UseStatusCodePages();

Par exemple, un résultat de point de terminaison HTTP Status 400 Bad Request génère
le corps de réponse sur les détails du problème suivant :

JSON

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "Bad Request",
"status": 400,
"nodeId": "my-machine-name"
}

IProblemDetailsWriter personnalisé

Une implémentation IProblemDetailsWriter peut être créée pour les personnalisations


avancées.

C#

public class SampleProblemDetailsWriter : IProblemDetailsWriter


{
// Indicates that only responses with StatusCode == 400
// are handled by this writer. All others are
// handled by different registered writers if available.
public bool CanWrite(ProblemDetailsContext context)
=> context.HttpContext.Response.StatusCode == 400;

public ValueTask WriteAsync(ProblemDetailsContext context)


{
// Additional customizations.

// Write to the response.


var response = context.HttpContext.Response;
return new
ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
}
}

Remarque : Lors de l’utilisation d’un IProblemDetailsWriter personnalisé, le


IProblemDetailsWriter personnalisé doit être inscrit avant d’appeler AddRazorPages,

AddControllers, AddControllersWithViewsou AddMvc :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter,
SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.


app.Use(async (context, next) =>
{
await next(context);
var mathErrorFeature = context.Features.Get<MathErrorFeature>();
if (mathErrorFeature is not null)
{
if (context.RequestServices.GetService<IProblemDetailsWriter>() is
{ } problemDetailsService)
{

if (problemDetailsService.CanWrite(new ProblemDetailsContext() {
HttpContext = context }))
{
(string Detail, string Type) details =
mathErrorFeature.MathError switch
{
MathErrorType.DivisionByZeroError => ("Divison by zero
is not defined.",
"https://en.wikipedia.org/wiki/Division_by_zero"),
_ => ("Negative or complex numbers are not valid
input.",
"https://en.wikipedia.org/wiki/Square_root")
};

await problemDetailsService.WriteAsync(new
ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = "Bad Input",
Detail = details.Detail,
Type = details.Type
}
});
}
}
}
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double
denominator) =>
{
if (denominator == 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.DivisionByZeroError
};
context.Features.Set(errorType);
return Results.BadRequest();
}

return Results.Ok(numerator / denominator);


});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
context.Features.Set(errorType);
return Results.BadRequest();
}

return Results.Ok(Math.Sqrt(radicand));
});

app.Run();

Détails du problème de l’intergiciel


Une autre approche de l’utilisation de ProblemDetailsOptions avec
CustomizeProblemDetails consiste à définir le ProblemDetails dans l’intergiciel. Une
réponse sur les détails du problème peut être écrite en appelant
IProblemDetailsService.WriteAsync :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStatusCodePages();

// Middleware to handle writing problem details to the response.


app.Use(async (context, next) =>
{
await next(context);
var mathErrorFeature = context.Features.Get<MathErrorFeature>();
if (mathErrorFeature is not null)
{
if (context.RequestServices.GetService<IProblemDetailsService>() is
{ }
problemDetailsService)
{
(string Detail, string Type) details =
mathErrorFeature.MathError switch
{
MathErrorType.DivisionByZeroError => ("Divison by zero is
not defined.",
"https://en.wikipedia.org/wiki/Division_by_zero"),
_ => ("Negative or complex numbers are not valid input.",
"https://en.wikipedia.org/wiki/Square_root")
};

await problemDetailsService.WriteAsync(new ProblemDetailsContext


{
HttpContext = context,
ProblemDetails =
{
Title = "Bad Input",
Detail = details.Detail,
Type = details.Type
}
});
}
}
});
// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double
denominator) =>
{
if (denominator == 0)
{
var errorType = new MathErrorFeature { MathError =

MathErrorType.DivisionByZeroError };
context.Features.Set(errorType);
return Results.BadRequest();
}

return Results.Ok(numerator / denominator);


});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
if (radicand < 0)
{
var errorType = new MathErrorFeature { MathError =

MathErrorType.NegativeRadicandError };
context.Features.Set(errorType);
return Results.BadRequest();
}

return Results.Ok(Math.Sqrt(radicand));
});

app.MapControllers();

app.Run();

Dans le code précédent, les points de terminaison d’API minimaux /divide et


/squareroot retournent la réponse de problème personnalisée attendue lors de l’entrée

d’erreur.

Les points de terminaison du contrôleur d’API retournent la réponse au problème par


défaut lors de l’entrée d’erreur, et non la réponse au problème personnalisée. La
réponse au problème par défaut est retournée, car le contrôleur d’API a écrit dans le flux
de réponse, Détails du problème pour les codes d’erreur d’état, avant que
IProblemDetailsService.WriteAsync ne soit appelé et la réponse n’est plus écrite.

La valeur ValuesController suivante retourne BadRequestResult, qui écrit dans le flux de


réponse et empêche donc le retour de la réponse au problème personnalisé.

C#
[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
// /api/values/divide/1/2
[HttpGet("{Numerator}/{Denominator}")]
public IActionResult Divide(double Numerator, double Denominator)
{
if (Denominator == 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.DivisionByZeroError
};
HttpContext.Features.Set(errorType);
return BadRequest();
}

return Ok(Numerator / Denominator);


}

// /api/values/squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
HttpContext.Features.Set(errorType);
return BadRequest();
}
return Ok(Math.Sqrt(radicand));
}

La valeur suivante Values3Controller retourne ControllerBase.Problem afin que le


résultat du problème personnalisé attendu soit retourné :

C#

[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
// /api/values3/divide/1/2
[HttpGet("{Numerator}/{Denominator}")]
public IActionResult Divide(double Numerator, double Denominator)
{
if (Denominator == 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.DivisionByZeroError
};
HttpContext.Features.Set(errorType);
return Problem(
title: "Bad Input",
detail: "Divison by zero is not defined.",
type: "https://en.wikipedia.org/wiki/Division_by_zero",
statusCode: StatusCodes.Status400BadRequest
);
}

return Ok(Numerator / Denominator);


}

// /api/values3/squareroot/4
[HttpGet("{radicand}")]
public IActionResult Squareroot(double radicand)
{
if (radicand < 0)
{
var errorType = new MathErrorFeature
{
MathError = MathErrorType.NegativeRadicandError
};
HttpContext.Features.Set(errorType);
return Problem(
title: "Bad Input",
detail: "Negative or complex numbers are not valid input.",
type: "https://en.wikipedia.org/wiki/Square_root",
statusCode: StatusCodes.Status400BadRequest
);
}

return Ok(Math.Sqrt(radicand));
}

Produire une charge utile ProblemDetails pour


les exceptions
Prenons l’application suivante :

C#

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

Dans les environnements hors développement, lorsqu’une exception se produit, la


réponse ProblemDetails standard qui est retournée au client est la suivante :

JSON

{
"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title":"An error occurred while processing your request.",
"status":500,"traceId":"00-b644<snip>-00"
}

Pour la plupart des applications, le code précédent est suffisant pour les exceptions.
Toutefois, la section suivante montre une manière d’obtenir des réponses plus détaillées
aux problèmes.

En dehors d’une page de gestionnaire d’exceptions personnalisée, il est possible de


fournir une expression lambda à UseExceptionHandler, L’utilisation d’une expression
lambda permet d’accéder à l’erreur et d’écrire une réponse sur les détails du problème
avec IProblemDetailsService.WriteAsync :

C#

using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode =
StatusCodes.Status500InternalServerError;
context.Response.ContentType = Text.Plain;

var title = "Bad Input";


var detail = "Invalid input";
var type = "https://errors.example.com/badInput";

if (context.RequestServices.GetService<IProblemDetailsService>()
is
{ } problemDetailsService)
{
var exceptionHandlerFeature =
context.Features.Get<IExceptionHandlerFeature>();

var exceptionType = exceptionHandlerFeature?.Error;


if (exceptionType != null &&
exceptionType.Message.Contains("infinity"))
{
title = "Argument exception";
detail = "Invalid input";
type = "https://errors.example.com/argumentException";
}

await problemDetailsService.WriteAsync(new
ProblemDetailsContext
{
HttpContext = context,
ProblemDetails =
{
Title = title,
Detail = detail,
Type = type
}
});
}
});
});
}

app.MapControllers();
app.Run();
2 Avertissement

Ne communiquez pas d’informations sensibles sur les erreurs aux clients. Cela
représenterait un risque de sécurité.

Une autre approche à la génération des détails d’un problème consiste à utiliser le
package NuGet tiers Hellang.Middleware.ProblemDetails qui peut être utilisé pour
mapper des exceptions et des erreurs client aux détails du problème.

Ressources supplémentaires
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Résoudre les problèmes liés à ASP.NET Core sur Azure App Service et IIS
Résolution des problèmes courants pour Azure App Service et IIS avec ASP.NET
Core
Gérer les erreurs dans les API web ASP.NET Core
Gérer les erreurs dans les applications API minimales.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Effectuer des requêtes HTTP en utilisant
IHttpClientFactory dans ASP.NET Core
Article • 30/11/2023

Par Kirk Larkin , Steve Gordon , Glenn Condron et Ryan Nowak .

Une IHttpClientFactory peut être inscrite et utilisée pour configurer et créer des
instances de HttpClient dans une application. IHttpClientFactory offre les avantages
suivants :

Fournit un emplacement central pour le nommage et la configuration d’instance


de HttpClient logiques. Par exemple, un client nommé github peut être inscrit et
configuré pour accéder à GitHub . Un client par défaut peut être inscrit pour
l’accès général.
Codifie le concept d’intergiciel (middleware) sortant via la délégation de
gestionnaires dans HttpClient . Fournit des extensions pour les intergiciels basés
sur Polly afin de tirer parti de la délégation de gestionnaires dans HttpClient .
Gère les pools et la durée de vie des instances HttpClientMessageHandler sous-
jacentes. La gestion automatique évite les problèmes courants liés au système DNS
(Domain Name System) qui se produisent lors de la gestion manuelle des durées
de vie HttpClient .
Ajoute une expérience de journalisation configurable (via ILogger ) pour toutes les
requêtes envoyées via des clients créés par la fabrique.

L’exemple de code de cette version de rubrique utilise System.Text.Json pour désérialiser


le contenu JSON retourné dans les réponses HTTP. Pour les exemples qui utilisent
Json.NET et ReadAsAsync<T> , utilisez le sélecteur de version pour sélectionner une

version 2.x de cette rubrique.

Modèles de consommation
Vous pouvez utiliser IHttpClientFactory dans une application de plusieurs façons :

Utilisation de base
Clients nommés
Clients typés
Clients générés

La meilleure approche dépend des exigences de l’application.


Utilisation de base
Inscrivez IHttpClientFactory en appelant AddHttpClient dans Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddHttpClient();

Une IHttpClientFactory peut être demandée à l’aide de l’injection de dépendances (DI).


Le code suivant utilise IHttpClientFactory pour créer une instance HttpClient :

C#

public class BasicModel : PageModel


{
private readonly IHttpClientFactory _httpClientFactory;

public BasicModel(IHttpClientFactory httpClientFactory) =>


_httpClientFactory = httpClientFactory;

public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

public async Task OnGet()


{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ HeaderNames.Accept, "application/vnd.github.v3+json" },
{ HeaderNames.UserAgent, "HttpRequestsSample" }
}
};

var httpClient = _httpClientFactory.CreateClient();


var httpResponseMessage = await
httpClient.SendAsync(httpRequestMessage);

if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();

GitHubBranches = await JsonSerializer.DeserializeAsync


<IEnumerable<GitHubBranch>>(contentStream);
}
}
}

L’utilisation de IHttpClientFactory comme dans l’exemple précédent est un bon moyen


de refactoriser une application existante. Cela n’a aucun impact sur la façon dont
HttpClient est utilisé. Aux endroits où des instances HttpClient sont créées dans une

application existante, remplacez ces occurrences par des appels à CreateClient.

Clients nommés
Les clients nommés sont un bon choix dans les cas suivants :

L’application nécessite de nombreuses utilisations distinctes de HttpClient .


De nombreuses instances HttpClient ont une configuration différente.

Spécifiez la configuration d’un HttpClient nommé pendant son inscription dans


Program.cs :

C#

builder.Services.AddHttpClient("GitHub", httpClient =>


{
httpClient.BaseAddress = new Uri("https://api.github.com/");

// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});

Dans le code précédent, le client est configuré avec :

L’adresse de base https://api.github.com/ .


Deux en-têtes requis pour fonctionner avec l’API GitHub.

CreateClient
Chaque fois que CreateClient est appelé :

Une nouvelle instance de HttpClient est créée.


L’action de configuration est appelée.

Pour créer un client nommé, passez son nom dans CreateClient :


C#

public class NamedClientModel : PageModel


{
private readonly IHttpClientFactory _httpClientFactory;

public NamedClientModel(IHttpClientFactory httpClientFactory) =>


_httpClientFactory = httpClientFactory;

public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

public async Task OnGet()


{
var httpClient = _httpClientFactory.CreateClient("GitHub");
var httpResponseMessage = await httpClient.GetAsync(
"repos/dotnet/AspNetCore.Docs/branches");

if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();

GitHubBranches = await JsonSerializer.DeserializeAsync


<IEnumerable<GitHubBranch>>(contentStream);
}
}
}

Dans le code précédent, la requête n’a pas besoin de spécifier un nom d’hôte. Le code
peut simplement passer le chemin, car l’adresse de base configurée pour le client est
utilisée.

Clients typés
Clients typés :

Fournissent les mêmes fonctionnalités que les clients nommés, sans qu’il soit
nécessaire d’utiliser des chaînes comme clés.
Bénéficie de l’aide d’IntelliSense et du compilateur lors de l’utilisation des clients.
Fournissent un emplacement unique pour configurer et interagir avec un
HttpClient particulier. Par exemple, un client typé unique peut être utilisé :

Pour un point de terminaison principal unique.


Pour encapsuler toute la logique traitant du point de terminaison.
Pour travailler avec l’injection de dépendances et injecter là où c’est nécessaire
dans l’application.

Un client typé accepte un paramètre HttpClient dans son constructeur :


C#

public class GitHubService


{
private readonly HttpClient _httpClient;

public GitHubService(HttpClient httpClient)


{
_httpClient = httpClient;

_httpClient.BaseAddress = new Uri("https://api.github.com/");

// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
}

public async Task<IEnumerable<GitHubBranch>?>


GetAspNetCoreDocsBranchesAsync() =>
await _httpClient.GetFromJsonAsync<IEnumerable<GitHubBranch>>(
"repos/dotnet/AspNetCore.Docs/branches");
}

Dans le code précédent :

La configuration est déplacée dans le client typé.


L’instance HttpClient fournie est stockée en tant que champ privé.

Vous pouvez créer des méthodes spécifiques à l’API qui exposent des fonctionnalités de
HttpClient . Par exemple, la méthode encapsule le code GetAspNetCoreDocsBranches

pour récupérer des branches GitHub de documentation.

Le code suivant appelle AddHttpClient dans Program.cs pour inscrire une classe cliente
typée GitHubService :

C#

builder.Services.AddHttpClient<GitHubService>();

Le client typé est inscrit comme étant transitoire avec injection de dépendances. Dans le
code précédent, AddHttpClient inscrit GitHubService en tant que service temporaire.
Cette inscription utilise une méthode de fabrique pour :

1. Créez une instance de HttpClient .


2. Créer une instance de GitHubService en passant l’instance de HttpClient à son
constructeur.

Le client typé peut être injecté et utilisé directement :

C#

public class TypedClientModel : PageModel


{
private readonly GitHubService _gitHubService;

public TypedClientModel(GitHubService gitHubService) =>


_gitHubService = gitHubService;

public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

public async Task OnGet()


{
try
{
GitHubBranches = await
_gitHubService.GetAspNetCoreDocsBranchesAsync();
}
catch (HttpRequestException)
{
// ...
}
}
}

Vous pouvez également spécifier la configuration d’un client typé lors de l’inscription
dans Program.cs au lieu de le faire dans le constructeur du client typé :

C#

builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");

// ...
});

Clients générés
IHttpClientFactory peut être utilisé en combinaison avec des bibliothèques tierces,

comme Refit . Refit est une bibliothèque REST pour .NET. Il convertit les API REST en
interfaces dynamiques. Appelez AddRefitClient pour générer une implémentation
dynamique d’une interface, qui utilise HttpClient pour effectuer les appels HTTP
externes.

Une interface personnalisée représente l’API externe :

C#

public interface IGitHubClient


{
[Get("/repos/dotnet/AspNetCore.Docs/branches")]
Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}

Appelez AddRefitClient pour générer l’implémentation dynamique, puis appelez


ConfigureHttpClient pour configurer le HttpClient sous-jacent :

C#

builder.Services.AddRefitClient<IGitHubClient>()
.ConfigureHttpClient(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");

// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});

Utilisez la DI pour accéder à l’implémentation dynamique de IGitHubClient :

C#

public class RefitModel : PageModel


{
private readonly IGitHubClient _gitHubClient;

public RefitModel(IGitHubClient gitHubClient) =>


_gitHubClient = gitHubClient;

public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

public async Task OnGet()


{
try
{
GitHubBranches = await
_gitHubClient.GetAspNetCoreDocsBranchesAsync();
}
catch (ApiException)
{
// ...
}
}
}

Effectuer des requêtes POST, PUT et DELETE


Dans les exemples précédents, toutes les requêtes HTTP utilisent le verbe HTTP GET.
HttpClient prend également en charge d’autres verbes HTTP, notamment :

POST
PUT
DELETE
PATCH

Pour obtenir la liste complète des verbes HTTP pris en charge, consultez HttpMethod.

L’exemple suivant montre comment effectuer une requête HTTP POST :

C#

public async Task CreateItemAsync(TodoItem todoItem)


{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json); // using static System.Net.Mime.MediaTypeNames;

using var httpResponseMessage =


await _httpClient.PostAsync("/api/TodoItems", todoItemJson);

httpResponseMessage.EnsureSuccessStatusCode();
}

Dans le code précédent, la méthode CreateItemAsync :

Sérialise le paramètre TodoItem au format JSON à l’aide de System.Text.Json .


Crée une instance de StringContent pour empaqueter le JSON sérialisé pour l’envoi
dans le corps de la requête HTTP.
Appelle PostAsync pour envoyer le contenu JSON à l’URL spécifiée. Il s’agit d’une
URL relative qui est ajoutée à HttpClient.BaseAddress.
Appelle EnsureSuccessStatusCode pour lever une exception si le code d’état de la
réponse n’indique pas la réussite.
HttpClient prend également en charge d’autres types de contenu. Par exemple :

MultipartContent et StreamContent. Pour obtenir la liste complète du contenu pris en


charge, consultez HttpContent.

L’exemple suivant montre une requête HTTP PUT :

C#

public async Task SaveItemAsync(TodoItem todoItem)


{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json);

using var httpResponseMessage =


await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}",
todoItemJson);

httpResponseMessage.EnsureSuccessStatusCode();
}

Le code précédent est similaire à l’exemple POST La méthode SaveItemAsync appelle


PutAsync au lieu de PostAsync .

L’exemple suivant montre une requête HTTP DELETE :

C#

public async Task DeleteItemAsync(long itemId)


{
using var httpResponseMessage =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");

httpResponseMessage.EnsureSuccessStatusCode();
}

Dans le code précédent, la méthode DeleteItemAsync appelle DeleteAsync. Étant donné


que les requêtes HTTP DELETE ne contiennent généralement aucun corps, la méthode
DeleteAsync ne fournit pas de surcharge qui accepte une instance de HttpContent .

Pour en savoir plus sur l’utilisation de différents verbes HTTP avec HttpClient , consultez
HttpClient.

Middleware pour les requêtes sortantes


HttpClient intègre le concept de délégation des gestionnaires qui peuvent être liés

ensemble pour les requêtes HTTP sortantes. IHttpClientFactory :

Simplifie la définition des gestionnaires à appliquer pour chaque client nommé.


Il prend en charge l’inscription et le chaînage de plusieurs gestionnaires pour créer
un pipeline d’intergiciels pour les requêtes sortantes. Chacun de ces gestionnaires
peut effectuer un travail avant et après la requête sortante. Ce modèle :
Est similaire au pipeline d’intergiciels entrants dans ASP.NET Core.
Fournit un mécanisme pour gérer les problèmes transversaux liés aux requêtes
HTTP, par exemple :
mise en cache
gestion des erreurs
sérialisation
journalisation

Pour créer un gestionnaire de délégation :

Dérivez de DelegatingHandler.
Remplacez SendAsync. Exécutez du code avant de passer la requête au
gestionnaire suivant dans le pipeline :

C#

public class ValidateHeaderHandler : DelegatingHandler


{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"The API key header X-API-KEY is required.")
};
}

return await base.SendAsync(request, cancellationToken);


}
}

Le code précédent vérifie si l’en-tête X-API-KEY se trouve dans la requête. Si X-API-KEY


est manquant, BadRequest est retourné.

Plusieurs gestionnaires peuvent être ajoutés à la configuration d’un HttpClient avec


Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessage
Handler :

C#

builder.Services.AddTransient<ValidateHeaderHandler>();

builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<ValidateHeaderHandler>();

Dans le code précédent, le ValidateHeaderHandler est inscrit avec une injection de


dépendances. Une fois inscrit, AddHttpMessageHandler peut être appelé en passant en
entrée le type pour le gestionnaire.

Vous pouvez inscrire plusieurs gestionnaires dans l’ordre où ils doivent être exécutés.
Chaque gestionnaire wrappe le gestionnaire suivant jusqu’à ce que le dernier
HttpClientHandler exécute la requête :

C#

builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();

builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
.AddHttpMessageHandler<SampleHandler1>()
.AddHttpMessageHandler<SampleHandler2>();

Dans le code précédent, SampleHandler1 s’exécute en premier, avant SampleHandler2 .

Utiliser l’intergiciel de requêtes sortantes


Lorsque IHttpClientFactory crée un gestionnaire de délégation, il utilise l’injection de
dépendances pour remplir les paramètres du constructeur du gestionnaire.
IHttpClientFactory crée une étendue de DI distincte pour chaque gestionnaire, ce qui

peut entraîner un comportement surprenant lorsqu’un gestionnaire consomme un


service délimité.

Par exemple, considérez l’interface suivante et son implémentation, qui représente une
tâche en tant qu’opération avec un identificateur, OperationId :

C#

public interface IOperationScoped


{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

Comme son nom l’indique, IOperationScoped est inscrit avec la DI à l’aide d’une durée
de vie étendue :

C#

builder.Services.AddScoped<IOperationScoped, OperationScoped>();

Le gestionnaire de délégation suivant consomme et utilise IOperationScoped pour


définir l’en-tête X-OPERATION-ID pour la requête sortante :

C#

public class OperationHandler : DelegatingHandler


{
private readonly IOperationScoped _operationScoped;

public OperationHandler(IOperationScoped operationScoped) =>


_operationScoped = operationScoped;

protected override async Task<HttpResponseMessage> SendAsync(


HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationScoped.OperationId);

return await base.SendAsync(request, cancellationToken);


}
}

Dans le Téléchargement de HttpRequestsSample , accédez à /Operation et actualisez


la page. La valeur de l’étendue de la requête change pour chaque requête, mais la
valeur d’étendue du gestionnaire change uniquement toutes les 5 secondes.

Les gestionnaires peuvent dépendre des services de n’importe quelle étendue. Les
services dont dépendent les gestionnaires sont supprimés lorsque le gestionnaire est
supprimé.

Utilisez l’une des approches suivantes pour partager l’état de chaque requête avec les
gestionnaires de messages :

Passez des données dans le gestionnaire en utilisant HttpRequestMessage.Options.


Utilisez IHttpContextAccessor pour accéder à la requête en cours.
Créez un objet de stockage AsyncLocal<T> personnalisé pour passer les données.

Utiliser les gestionnaires Polly


IHttpClientFactory s’intègre à la bibliothèque tierce Polly . Polly est une bibliothèque
complète de gestion des erreurs transitoires et de résilience pour .NET. Elle permet aux
développeurs de formuler facilement et de façon thread-safe des stratégies, comme
Retry (Nouvelle tentative), Circuit Breaker (Disjoncteur), Timeout (Délai d’attente),
Bulkhead Isolation (Isolation par cloisonnement) et Fallback (Alternative de repli).

Des méthodes d’extension sont fournies pour permettre l’utilisation de stratégies Polly
avec les instances configurées de HttpClient . Les extensions Polly prennent en charge
l’ajout de gestionnaires basés sur Polly aux clients. Polly nécessite le package NuGet
Microsoft.Extensions.Http.Polly .

Gérer les erreurs temporaires


Les erreurs se produisent généralement lorsque des appels HTTP externes sont
temporaires. AddTransientHttpErrorPolicy permet à une stratégie d’être définie pour
gérer les erreurs temporaires. Les stratégies configurées avec
AddTransientHttpErrorPolicy gèrent les réponses suivantes :

HttpRequestException
HTTP 5xx
HTTP 408

AddTransientHttpErrorPolicy fournit l’accès à un objet PolicyBuilder configuré pour

gérer les erreurs représentant une erreur temporaire possible :

C#

builder.Services.AddHttpClient("PollyWaitAndRetry")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3, retryNumber => TimeSpan.FromMilliseconds(600)));

Dans le code précédent, une stratégie WaitAndRetryAsync est définie. Les requêtes qui
ont échoué sont retentées jusqu’à trois fois avec un délai de 600 ms entre les tentatives.

Sélectionner dynamiquement des stratégies


Des méthodes d’extension sont fournies pour ajouter des gestionnaires basés sur Polly,
par exemple AddPolicyHandler. La surcharge de AddPolicyHandler suivante inspecte la
requête pour déterminer la stratégie à appliquer :

C#

var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(


TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));

builder.Services.AddHttpClient("PollyDynamic")
.AddPolicyHandler(httpRequestMessage =>
httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy :
longTimeoutPolicy);

Dans le code précédent, si la requête sortante est un HTTP GET, un délai d’attente de
10 secondes est appliqué. Pour toutes les autres méthodes HTTP, un délai d’attente de
30 secondes est utilisé.

Ajouter plusieurs gestionnaires Polly


Il est courant d’imbriquer les stratégies Polly :

C#

builder.Services.AddHttpClient("PollyMultiple")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.RetryAsync(3))
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

Dans l’exemple précédent :

Deux gestionnaires sont ajoutés.


Le premier gestionnaire utilise AddTransientHttpErrorPolicy pour ajouter une
stratégie de nouvelle tentative. Les requêtes qui ont échoué sont retentées jusqu’à
trois fois.
Le deuxième appel à AddTransientHttpErrorPolicy ajoute une stratégie de
disjoncteur. Les requêtes externes supplémentaires sont bloquées pendant 30
secondes si 5 tentatives successives échouent. Les stratégies de disjoncteur sont
avec état. Tous les appels effectués via ce client partagent le même état du circuit.

Ajouter des stratégies à partir du Registre Polly


Une approche de la gestion des stratégies régulièrement utilisées consiste à les définir
une seule fois et à les inscrire avec un PolicyRegistry . Par exemple :

C#

var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(


TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));

var policyRegistry = builder.Services.AddPolicyRegistry();

policyRegistry.Add("Regular", timeoutPolicy);
policyRegistry.Add("Long", longTimeoutPolicy);

builder.Services.AddHttpClient("PollyRegistryRegular")
.AddPolicyHandlerFromRegistry("Regular");

builder.Services.AddHttpClient("PollyRegistryLong")
.AddPolicyHandlerFromRegistry("Long");

Dans le code précédent :

Deux stratégies, Regular et Long , sont ajoutées au registre Polly.


AddPolicyHandlerFromRegistry configure des clients nommés individuels pour
utiliser ces stratégies à partir du registre Polly.

Pour plus d’informations sur les intégrations IHttpClientFactory et Polly, consultez le


Wiki Polly .

HttpClient et gestion de la durée de vie


Une nouvelle instance HttpClient est retournée à chaque fois que CreateClient est
appelé sur IHttpClientFactory . Un HttpMessageHandler est créé par client nommé. La
fabrique gère les durées de vie des instances HttpMessageHandler .

IHttpClientFactory regroupe dans un pool les instances de HttpMessageHandler créées

par la fabrique pour réduire la consommation des ressources. Une instance de


HttpMessageHandler peut être réutilisée à partir du pool quand vous créez une instance

de HttpClient si sa durée de vie n’a pas expiré.

Le regroupement de gestionnaires en pools est souhaitable, car chaque gestionnaire


gère habituellement ses propres connexions HTTP sous-jacentes. La création de plus de
gestionnaires que nécessaire peut entraîner des délais de connexion. Certains
gestionnaires conservent aussi les connexions indéfiniment ouvertes, ce qui peut
empêcher le gestionnaire de réagir aux changements du DNS (Domain Name System).

La durée de vie par défaut d’un gestionnaire est de deux minutes. La valeur par défaut
peut être remplacée pour chaque client nommé :

C#

builder.Services.AddHttpClient("HandlerLifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));

Les instances HttpClient peuvent généralement être traitées en tant qu’objets .NET ne
nécessitant pas une suppression. La suppression annule les requêtes sortantes et
garantit que l’instance HttpClient donnée ne peut pas être utilisée après avoir appelé
Dispose. IHttpClientFactory effectue le suivi et libère les ressources utilisées par les
instances HttpClient .

Le fait de conserver une seule instance HttpClient active pendant une longue durée est
un modèle commun utilisé avant le lancement de IHttpClientFactory . Ce modèle
devient inutile après la migration vers IHttpClientFactory .

Alternatives à IHttpClientFactory
L’utilisation de IHttpClientFactory dans une application avec injection de dépendances
évite :

Les problèmes d’épuisement des ressources en regroupant les instances


HttpMessageHandler .

Les problèmes de DNS obsolète en permutant les instances HttpMessageHandler à


intervalles réguliers.

Il existe d’autres façons de résoudre les problèmes précédents à l’aide d’une instance
SocketsHttpHandler de longue durée.

Créez une instance de SocketsHttpHandler lorsque l’application démarre et


utilisez-la pour la durée de vie de l’application.
Configurez PooledConnectionLifetime sur une valeur appropriée en fonction des
temps d’actualisation DNS.
Créez des instances HttpClient à l’aide de new HttpClient(handler,
disposeHandler: false) si nécessaire.
Les approches précédentes résolvent les problèmes de gestion des ressources que
IHttpClientFactory résout de la même manière.

Le SocketsHttpHandler partage les connexions entre les instances HttpClient . Ce


partage empêche l’épuisement des sockets.
Le SocketsHttpHandler permute les connexions en fonction de
PooledConnectionLifetime pour éviter les problèmes de DNS obsolète.

Journalisation
Les clients créés via IHttpClientFactory enregistrent les messages de journalisation
pour toutes les requêtes. Activez le niveau d’informations approprié dans la
configuration de journalisation pour voir les messages de journalisation par défaut. Une
journalisation supplémentaire, comme celle des en-têtes des requêtes, est incluse
seulement au niveau de trace.

La catégorie de journal utilisée pour chaque client comprend le nom du client. Par
exemple, un client nommé MyNamedClient journalise les messages avec la catégorie
« System.Net.Http.HttpClient.MyNamedClient.LogicalHandler ». Les messages avec le
suffixe LogicalHandler se produisent en dehors du pipeline du gestionnaire de requêtes.
Lors d’une requête, les messages sont journalisés avant que d’autres gestionnaires du
pipeline l’aient traitée. Lors d’une réponse, les messages sont journalisés une fois que
tous les autres gestionnaires du pipeline ont reçu la réponse.

La journalisation se produit également à l’intérieur du pipeline du gestionnaire de


requêtes. Dans l’exemple MyNamedClient, ces messages sont enregistrés avec la
catégorie de journal « System.Net.Http.HttpClient.MyNamedClient.ClientHandler ». Pour
la requête, cela se produit après que tous les autres gestionnaires ont été exécutés et
immédiatement avant l’envoi de la requête. Lors de la réponse, cette journalisation
inclut l’état de la réponse avant qu’elle repasse à travers le pipeline de gestionnaires.

L’activation de la journalisation à l’extérieur et à l’intérieur du pipeline permet


l’inspection des changements apportés par les autres gestionnaires du pipeline. Cela
peut comprendre des changements apportés aux en-têtes des requêtes ou au code
d’état de la réponse.

L’ajout du nom du client dans la catégorie de journalisation permet de filtrer le journal


pour des clients nommés spécifiques.

Configurer le HttpMessageHandler
Il peut être nécessaire de contrôler la configuration du HttpMessageHandler interne
utilisé par un client.

Un IHttpClientBuilder est retourné quand vous ajoutez des clients nommés ou typés.
La méthode d’extension ConfigurePrimaryHttpMessageHandler peut être utilisée pour
définir un délégué. Le délégué est utilisé pour créer et configurer le HttpMessageHandler
principal utilisé par ce client :

C#

builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
AllowAutoRedirect = true,
UseDefaultCredentials = true
});

Cookies
Le regroupement des instances HttpMessageHandler engendre le partage d’objets
CookieContainer . Le partage d’objets CookieContainer imprévus entraîne souvent un

code incorrect. Pour les applications qui nécessitent des cookie, tenez compte de ce qui
suit :

Désactivation de la gestion de cookie automatique


Éviter IHttpClientFactory

Appelez ConfigurePrimaryHttpMessageHandler pour désactiver la gestion de cookie


automatique :

C#

builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});

Utiliser IHttpClientFactory dans une application


console
Dans une application console, ajoutez les références de package suivantes au projet :

Microsoft.Extensions.Hosting
Microsoft.Extensions.Http

Dans l’exemple suivant :

IHttpClientFactory et GitHubService sont inscrits dans le conteneur de service dans


de l’hôte générique.
GitHubService est demandé à la DI, qui demande à son tour une instance de

IHttpClientFactory .
GitHubService utilise IHttpClientFactory pour créer une instance de HttpClient ,

qu’elle utilise pour récupérer des branches GitHub de documentation.

C#

using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var host = new HostBuilder()


.ConfigureServices(services =>
{
services.AddHttpClient();
services.AddTransient<GitHubService>();
})
.Build();

try
{
var gitHubService = host.Services.GetRequiredService<GitHubService>();
var gitHubBranches = await
gitHubService.GetAspNetCoreDocsBranchesAsync();

Console.WriteLine($"{gitHubBranches?.Count() ?? 0} GitHub Branches");

if (gitHubBranches is not null)


{
foreach (var gitHubBranch in gitHubBranches)
{
Console.WriteLine($"- {gitHubBranch.Name}");
}
}
}
catch (Exception ex)
{
host.Services.GetRequiredService<ILogger<Program>>()
.LogError(ex, "Unable to load branches from GitHub.");
}
public class GitHubService
{
private readonly IHttpClientFactory _httpClientFactory;

public GitHubService(IHttpClientFactory httpClientFactory) =>


_httpClientFactory = httpClientFactory;

public async Task<IEnumerable<GitHubBranch>?>


GetAspNetCoreDocsBranchesAsync()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ "Accept", "application/vnd.github.v3+json" },
{ "User-Agent", "HttpRequestsConsoleSample" }
}
};

var httpClient = _httpClientFactory.CreateClient();


var httpResponseMessage = await
httpClient.SendAsync(httpRequestMessage);

httpResponseMessage.EnsureSuccessStatusCode();

using var contentStream =


await httpResponseMessage.Content.ReadAsStreamAsync();

return await JsonSerializer.DeserializeAsync


<IEnumerable<GitHubBranch>>(contentStream);
}
}

public record GitHubBranch(


[property: JsonPropertyName("name")] string Name);

Intergiciel de propagation d’en-tête


La propagation d’en-tête est un intergiciel ASP.NET Core pour propager les en-têtes
HTTP de la requête entrante vers les requêtes HttpClient sortantes. Pour utiliser la
propagation d’en-tête :

Installez le package Microsoft.AspNetCore.HeaderPropagation .

Configurez le pipeline de HttpClient et d’intergiciel dans Program.cs :

C#
// Add services to the container.
builder.Services.AddControllers();

builder.Services.AddHttpClient("PropagateHeaders")
.AddHeaderPropagation();

builder.Services.AddHeaderPropagation(options =>
{
options.Headers.Add("X-TraceId");
});

var app = builder.Build();

// Configure the HTTP request pipeline.


app.UseHttpsRedirection();

app.UseHeaderPropagation();

app.MapControllers();

Effectuez des requêtes sortantes à l’aide de l’instance HttpClient configurée, qui


inclut les en-têtes ajoutés.

Ressources supplémentaires
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Utilisez HttpClientFactory pour implémenter des requêtes HTTP résilientes
Implémenter de nouvelles tentatives d’appel HTTP avec interruption exponentielle
avec des stratégies Polly et HttpClientFactory
Implémenter le modèle Disjoncteur
Comment sérialiser et désérialiser JSON dans .NET

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Fichiers statiques dans ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Les fichiers statiques, comme les fichiers HTML, CSS, images et JavaScript, sont des
ressources qu’une application ASP.NET Core délivre directement aux clients, par défaut.

Délivrer des fichiers statiques


Les fichiers statiques sont stockés dans le répertoire racine web du projet. Le répertoire
par défaut est {content root}/wwwroot , mais il peut être modifié avec la méthode
UseWebRoot. Pour plus d’informations, consultez Racine de contenu et Racine web.

La méthode CreateBuilder définit le répertoire actif comme racine du contenu :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Les fichiers statiques sont accessibles via un chemin relatif à la racine web. Par exemple,
les modèles de projet Application web contiennent plusieurs dossiers dans le dossier
wwwroot :

wwwroot
css
js

lib

Envisagez de créer le dossier wwwroot/images et d’ajouter le fichier


wwwroot/images/MyImage.jpg . Le format URI pour accéder à un fichier dans le dossier

images est https://<hostname>/images/<image_file_name> . Par exemple,


https://localhost:5001/images/MyImage.jpg

Délivrer des fichiers à la racine web


Les modèles d’application web par défaut appellent la méthode UseStaticFiles dans
Program.cs , ce qui permet de délivrer les fichiers statiques :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

La surcharge de la méthode UseStaticFiles sans paramètres marque les fichiers à la


racine web comme étant délivrables. Le balisage suivant référence
wwwroot/images/MyImage.jpg :

HTML

<img src="~/images/MyImage.jpg" class="img" alt="My image" />


Dans le balisage précédent, le caractère tilde ~ pointe vers la racine web.

Délivrer des fichiers en dehors de la racine web


Considérez une hiérarchie de répertoires dans laquelle les fichiers statiques à délivrer se
trouvent en dehors de la racine web :

wwwroot

css

images
js

MyStaticFiles
images

red-rose.jpg

Une demande peut accéder au fichier red-rose.jpg en configurant l’intergiciel de


fichiers statiques comme suit :

C#

using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath,
"MyStaticFiles")),
RequestPath = "/StaticFiles"
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Dans le code précédent, la hiérarchie de répertoires MyStaticFiles est exposée


publiquement via le segment d’URI StaticFiles. Une demande à
https://<hostname>/StaticFiles/images/red-rose.jpg délivre le fichier red-rose.jpg .

Le balisage suivant référence MyStaticFiles/images/red-rose.jpg :

HTML

<img src="~/StaticFiles/images/red-rose.jpg" class="img" alt="A red rose" />

Pour délivrer des fichiers à partir de plusieurs emplacements, consultez Délivrer des
fichiers à partir de plusieurs emplacements.

Définir des en-têtes de réponse HTTP


Un objet StaticFileOptions peut être utilisé pour définir des en-têtes de réponse HTTP.
En plus de configurer la possibilité de délivrer des fichiers statiques à partir de la racine
web, le code suivant définit l’en-tête Cache-Control :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
var cacheMaxAgeOneWeek = (60 * 60 * 24 * 7).ToString();
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers.Append(
"Cache-Control", $"public, max-age={cacheMaxAgeOneWeek}");
}
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Le code précédent rend les fichiers statiques accessibles publiquement dans le cache
local pendant une semaine (604800 secondes).

Autorisations des fichiers statiques


Les modèles ASP.NET Core appellent UseStaticFiles avant d’appeler UseAuthorization. La
plupart des applications suivent ce modèle. Quand l’intergiciel de fichiers statiques est
appelé avant l’intergiciel d’autorisation :

Aucune vérification d’autorisation n’est effectuée sur les fichiers statiques.


Les fichiers statiques délivrés par l’intergiciel de fichiers statiques, tels que ceux
sous wwwroot , sont accessibles publiquement.

Pour délivrer des fichiers statiques en fonction d’une autorisation :

Stockez-les en dehors de wwwroot .


Appelez UseStaticFiles , en spécifiant un chemin, après avoir appelé
UseAuthorization .

Définissez la stratégie d’autorisation de secours.

C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders;
using StaticFileAuth.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath,
"MyStaticFiles")),
RequestPath = "/StaticFiles"
});

app.MapRazorPages();

app.Run();

Dans le code précédent, la stratégie d’autorisation de secours exige que tous les
utilisateurs soient authentifiés. Les points de terminaison tels que les contrôleurs, Razor
Pages, etc., qui spécifient leurs propres exigences d’autorisation, n’utilisent pas la
stratégie d’autorisation de secours. Par exemple, Razor Pages, les contrôleurs ou les
méthodes d’action avec [AllowAnonymous] ou [Authorize(PolicyName="MyPolicy")]
utilisent l’attribut d’autorisation appliqué plutôt que la stratégie d’autorisation de
secours.

RequireAuthenticatedUser ajoute DenyAnonymousAuthorizationRequirement à


l’instance actuelle, ce qui impose l’authentification de l’utilisateur actuel.

Les ressources statiques sous wwwroot sont accessibles publiquement, car l’intergiciel de
fichiers statiques par défaut ( app.UseStaticFiles(); ) est appelé avant
UseAuthentication . Les ressources statiques figurant dans le dossier MyStaticFiles

nécessitent une authentification. Cet exemple de code illustre ceci.

Une autre approche pour traiter les fichiers en fonction de l’autorisation consiste à :

Les stocker en dehors de wwwroot et de tout répertoire accessible à l’intergiciel de


fichiers statiques.

Les délivrer via une méthode d’action à laquelle une autorisation est appliquée et à
retourner un objet FileResult :

C#

[Authorize]
public class BannerImageModel : PageModel
{
private readonly IWebHostEnvironment _env;

public BannerImageModel(IWebHostEnvironment env) =>


_env = env;

public PhysicalFileResult OnGet()


{
var filePath = Path.Combine(
_env.ContentRootPath, "MyStaticFiles", "images", "red-
rose.jpg");

return PhysicalFile(filePath, "image/jpeg");


}
}

L’approche précédente nécessite une page ou un point de terminaison par fichier. Le


code suivant retourne des fichiers ou charge des fichiers pour les utilisateurs
authentifiés :

C#
app.MapGet("/files/{fileName}", IResult (string fileName) =>
{
var filePath = GetOrCreateFilePath(fileName);

if (File.Exists(filePath))
{
return TypedResults.PhysicalFile(filePath, fileDownloadName: $"
{fileName}");
}

return TypedResults.NotFound("No file found with the supplied file


name");
})
.WithName("GetFileByName")
.RequireAuthorization("AuthenticatedUsers");

// IFormFile uses memory buffer for uploading. For handling large file use
streaming instead.
// https://learn.microsoft.com/aspnet/core/mvc/models/file-uploads#upload-
large-files-with-streaming
app.MapPost("/files", async (IFormFile file, LinkGenerator linker,
HttpContext context) =>
{
// Don't rely on the file.FileName as it is only metadata that can
be manipulated by the end-user
// Take a look at the `Utilities.IsFileValid` method that takes an
IFormFile and validates its signature within the AllowedFileSignatures

var fileSaveName = Guid.NewGuid().ToString("N") +


Path.GetExtension(file.FileName);
await SaveFileWithCustomFileName(file, fileSaveName);

context.Response.Headers.Append("Location",
linker.GetPathByName(context, "GetFileByName", new { fileName =
fileSaveName}));
return TypedResults.Ok("File Uploaded Successfully!");
})
.RequireAuthorization("AdminsOnly");

app.Run();

Pour obtenir l’exemple complet, consultez le dossier GitHub StaticFileAuth .

Exploration de répertoires
La navigation dans les répertoires permet de lister les répertoires dans les répertoires
spécifiés.

L’exploration des répertoires est désactivée par défaut pour des raisons de sécurité. Pour
plus d’informations, consultez Considérations relatives à la sécurité des fichiers
statiques.

Activez l’exploration des répertoires avec AddDirectoryBrowser et UseDirectoryBrowser :

C#

using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDirectoryBrowser();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

var fileProvider = new


PhysicalFileProvider(Path.Combine(builder.Environment.WebRootPath,
"images"));
var requestPath = "/MyImages";

// Enable displaying browser links.


app.UseStaticFiles(new StaticFileOptions
{
FileProvider = fileProvider,
RequestPath = requestPath
});

app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
FileProvider = fileProvider,
RequestPath = requestPath
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();
Le code précédent permet l’exploration des répertoires du dossier wwwroot/images en
utilisant l’URL https://<hostname>/MyImages , avec des liens vers chaque fichier et
dossier :

AddDirectoryBrowser ajoute les services requis par l’intergiciel de navigation de


répertoire, notamment HtmlEncoder. Ces services peuvent être ajoutés par d’autres
appels, tels que AddRazorPages, mais nous vous recommandons d’appeler
AddDirectoryBrowser pour garantir que les services sont ajoutés dans toutes les

applications.

Délivrer des documents par défaut


La définition d’une page par défaut fournit aux visiteurs un point de départ sur un site.
Pour délivrer un fichier par défaut à partir de wwwroot sans exiger que l’URL de la
demande inclue le nom du fichier, appelez la méthode UseDefaultFiles :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseDefaultFiles();

app.UseStaticFiles();
app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

UseDefaultFiles doit être appelé avant UseStaticFiles pour délivrer le fichier par

défaut. UseDefaultFiles est un module de réécriture d’URL qui ne délivre pas le fichier.

Avec UseDefaultFiles , les demandes effectuées sur un dossier dans wwwroot


recherchent :

default.htm

default.html
index.htm

index.html

Le premier fichier trouvé dans la liste est délivré comme si la demande incluait le nom
du fichier. L’URL du navigateur continue de refléter l’URI demandé.

Le code suivant change le nom de fichier par défaut en mydefault.html :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

var options = new DefaultFilesOptions();


options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("mydefault.html");
app.UseDefaultFiles(options);

app.UseStaticFiles();

app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

UseFileServer pour les documents par défaut


UseFileServer combine les fonctionnalités de UseStaticFiles , de UseDefaultFiles et
éventuellement de UseDirectoryBrowser .

Appelez app.UseFileServer pour activer la possibilité de délivrer des fichiers statiques et


le fichier par défaut. L’exploration des répertoires n’est pas activée :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseFileServer();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Le code suivant active la possibilité de délivrer des fichiers statiques, le fichier par défaut
et l’exploration des répertoires :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseFileServer(enableDirectoryBrowsing: true);

app.UseRouting();

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Considérez la hiérarchie de répertoires suivante :

wwwroot
css

images

js
MyStaticFiles

images
MyImage.jpg

default.html

Le code suivant active la possibilité de délivrer des fichiers statiques, le fichier par défaut
et l’exploration des répertoires de MyStaticFiles :

C#

using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDirectoryBrowser();

var app = builder.Build();


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseFileServer(new FileServerOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath,
"MyStaticFiles")),
RequestPath = "/StaticFiles",
EnableDirectoryBrowsing = true
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

AddDirectoryBrowser doit être appelé quand la valeur de la propriété


EnableDirectoryBrowsing est true .

En utilisant la hiérarchie de fichiers et le code précédents, les URL sont résolues comme
suit :

URI Response

https://<hostname>/StaticFiles/images/MyImage.jpg MyStaticFiles/images/MyImage.jpg

https://<hostname>/StaticFiles MyStaticFiles/default.html

Si aucun fichier nommé par défaut n’existe dans le répertoire MyStaticFiles,


https://<hostname>/StaticFiles retourne la liste des répertoires avec des liens

interactifs :
UseDefaultFiles et UseDirectoryBrowser effectuent une redirection côté client à partir de
l’URI cible sans / de fin, vers l’URI cible avec un / de fin. Par exemple, de
https://<hostname>/StaticFiles à https://<hostname>/StaticFiles/ . Les URL relatives

au sein du répertoire StaticFiles ne sont pas valides sans barre oblique de fin ( / ) à moins
que l’option RedirectToAppendTrailingSlash de DefaultFilesOptions soit utilisée.

FileExtensionContentTypeProvider
La classe FileExtensionContentTypeProvider contient une propriété Mappings qui agit
comme un mappage des extensions de fichiers à des types de contenu MIME. Dans
l’exemple suivant, plusieurs extensions de fichiers sont mappées à des types MIME
connus. L’extension .rtf est remplacée et l’extension .mp4 est supprimée :

C#

using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
// Set up custom content types - associating file extension to MIME type
var provider = new FileExtensionContentTypeProvider();
// Add new mappings
provider.Mappings[".myapp"] = "application/x-msdownload";
provider.Mappings[".htm3"] = "text/html";
provider.Mappings[".image"] = "image/png";
// Replace an existing mapping
provider.Mappings[".rtf"] = "application/x-msdownload";
// Remove MP4 videos.
provider.Mappings.Remove(".mp4");

app.UseStaticFiles(new StaticFileOptions
{
ContentTypeProvider = provider
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Consultez Types de contenu MIME .

Types de contenu non standard


L’intergiciel de fichiers statiques comprend près de 400 types de contenu de fichier. Si
l’utilisateur demande un fichier d’un type inconnu, l’intergiciel de fichiers statiques
transmet la demande à l’intergiciel suivant dans le pipeline. Si aucun intergiciel ne gère
la requête, une réponse 404 introuvable est retournée. Si l’exploration des répertoires
est activée, un lien vers le fichier est affiché dans la liste de répertoires.

Le code suivant permet de délivrer des types inconnus et rend le fichier inconnu en tant
qu’image :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "image/png"
});

app.UseAuthorization();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

Avec le code précédent, une requête pour un fichier avec un type de contenu inconnu
est retournée en tant qu’image.

2 Avertissement

L’activation de ServeUnknownFileTypes pose un problème de sécurité. Il est


désactivé par défaut et son utilisation est déconseillée.
FileExtensionContentTypeProvider fournit une alternative plus sûre pour délivrer
des fichiers avec des extensions non standard.

Délivrer des fichiers à partir de plusieurs


emplacements
Considérez la page Razor suivante qui affiche le fichier /MyStaticFiles/image3.png :

CSHTML

@page

<p> Test /MyStaticFiles/image3.png</p>

<img src="~/image3.png" class="img" asp-append-version="true" alt="Test">

UseStaticFiles et UseFileServer correspondent par défaut au fournisseur de fichiers

pointant sur wwwroot . Des instances supplémentaires de UseStaticFiles et


UseFileServer peuvent être fournies avec d’autres fournisseurs de fichiers pour délivrer
des fichiers à partir d’autres emplacements. L’exemple suivant appelle UseStaticFiles
deux fois pour délivrer des fichiers à partir de wwwroot et de MyStaticFiles :

C#

app.UseStaticFiles(); // Serve files from wwwroot


app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"))
});

Utilisation du code précédent :

Le fichier /MyStaticFiles/image3.png est affiché.


Le Tag Helper d’imageAppendVersion n’est pas appliqué, car les Tag Helpers
dépendent de WebRootFileProvider. WebRootFileProvider n’a pas été mis à jour
pour inclure le dossier MyStaticFiles .

Le code suivant met à jour WebRootFileProvider , qui permet au Tag Helper d’image de
fournir une version :

C#

var webRootProvider = new


PhysicalFileProvider(builder.Environment.WebRootPath);
var newPathProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"));

var compositeProvider = new CompositeFileProvider(webRootProvider,


newPathProvider);

// Update the default provider.


app.Environment.WebRootFileProvider = compositeProvider;

app.UseStaticFiles();

Considérations de sécurité pour les fichiers statiques

2 Avertissement

UseDirectoryBrowser et UseStaticFiles peuvent entraîner une fuite de secrets. La

désactivation de l’exploration de répertoires est fortement recommandée en


production. Examinez attentivement les répertoires qui sont activés via
UseStaticFiles ou UseDirectoryBrowser . L’ensemble du répertoire et de ses sous-

répertoires deviennent accessibles publiquement. Stockez les fichiers qui peuvent


être délivrés au public dans un répertoire dédié, comme <content_root>/wwwroot .
Séparez ces fichiers des vues MVC, de Razor Pages, des fichiers de configuration,
etc.

Les URL pour le contenu exposé avec UseDirectoryBrowser et UseStaticFiles sont


soumises aux restrictions de respect de la casse et de caractères du système de
fichiers sous-jacent. Par exemple, Windows ne respecte pas la casse, mais macOS
et Linux la respectent.

Les applications ASP.NET Core hébergées dans IIS utilisent le module ASP.NET
Core pour transférer toutes les requêtes à l’application, notamment les requêtes de
fichiers statiques. Le gestionnaire de fichiers statiques IIS n’est pas utilisé et n’a
aucune chance de gérer les demandes.

Effectuez les étapes suivantes dans le Gestionnaire des services Internet (IIS) pour
supprimer le gestionnaire de fichiers statiques d’IIS au niveau du serveur ou du site
web :

1. Accédez à la fonctionnalité Modules.


2. Sélectionnez StaticFileModule dans la liste.
3. Cliquez sur Supprimer dans l’encadré Actions.

2 Avertissement

Si le gestionnaire de fichiers statiques d’IIS est activé et que le module ASP.NET


Core est incorrectement configuré, les fichiers statiques peuvent être délivrés. Cela
se produit par exemple si le fichier web.config n’est pas déployé.

Placez les fichiers de code, y compris .cs et .cshtml , en dehors de la racine web
du projet d’application. Par conséquent, une séparation logique est créée entre le
contenu côté client et le code basé sur le serveur de l’application. Ceci empêche la
fuite de code côté serveur.

Délivrez des fichiers en dehors de wwwroot en


mettant à jour
IWebHostEnvironment.WebRootPath
Quand IWebHostEnvironment.WebRootPath est défini sur un dossier autre que
wwwroot :

Dans l’environnement de développement, les ressources statiques trouvées dans


wwwroot et dans la mise à jour de IWebHostEnvironment.WebRootPath sont délivrées

à partir de wwwroot .
Dans tout environnement autre que celui de développement, les ressources
statiques en double sont délivrées à partir du dossier
IWebHostEnvironment.WebRootPath mis à jour.

Considérez une application web créée avec le modèle web vide :

Contenant un fichier Index.html dans wwwroot et wwwroot-custom .

Avec le fichier Program.cs mis à jour suivant qui définit WebRootPath = "wwwroot-
custom" :

C#

var builder = WebApplication.CreateBuilder(new WebApplicationOptions


{
Args = args,
// Look for static files in "wwwroot-custom"
WebRootPath = "wwwroot-custom"
});

var app = builder.Build();

app.UseDefaultFiles();
app.UseStaticFiles();

app.Run();

Dans le code précédent, les demandes adressées à / :

Dans l’environnement de développement, retournent wwwroot/Index.html


Dans tout environnement autre que celui de développement, retournent wwwroot-
custom/Index.html

Pour vous assurer que les ressources à partir de wwwroot-custom sont retournées, utilisez
l’une des approches suivantes :

Supprimez les ressources nommées en double dans wwwroot .

Définissez "ASPNETCORE_ENVIRONMENT" dans Properties/launchSettings.json sur


n’importe quelle valeur autre que "Development" .
Désactivez complètement les ressources web statiques en définissant
<StaticWebAssetsEnabled>false</StaticWebAssetsEnabled> dans le fichier projet.

AVERTISSEMENT, la désactivation des ressources web statiques désactive les


bibliothèques de classes Razor.

Ajoutez le code JSON suivant au fichier projet :

XML

<ItemGroup>
<Content Remove="wwwroot\**" />
</ItemGroup>

Le code suivant met à jour IWebHostEnvironment.WebRootPath vers une valeur de non-


développement, garantissant que le contenu en double est retourné à partir de
wwwroot-custom plutôt que wwwroot :

C#

var builder = WebApplication.CreateBuilder(new WebApplicationOptions


{
Args = args,
// Examine Hosting environment: logging value
EnvironmentName = Environments.Staging,
WebRootPath = "wwwroot-custom"
});

var app = builder.Build();

app.Logger.LogInformation("ASPNETCORE_ENVIRONMENT: {env}",
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"));

app.Logger.LogInformation("app.Environment.IsDevelopment(): {env}",
app.Environment.IsDevelopment().ToString());

app.UseDefaultFiles();
app.UseStaticFiles();

app.Run();

Ressources supplémentaires
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Middleware
Introduction à ASP.NET Core
6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Choisir une interface utilisateur web
ASP.NET Core
Article • 06/12/2023

ASP.NET Core est un framework d’interface utilisateur complet. Choisissez les


fonctionnalités à combiner en fonction des besoins d’interface utilisateur web de
l’application.

ASP.NET Core Blazor


Blazor est un framework d’interface utilisateur web complet et est recommandé pour la
plupart des scénarios d’interface utilisateur web.

Avantages de l’utilisation de Blazor :

Modèle de composants réutilisables.


Rendu efficace des composants basés sur les différences.
Rendu flexible des composants depuis le serveur ou le client via WebAssembly.
Construire des composants d’interface utilisateur web interactifs et riches en C#.
Rendre des composants de façon statique depuis le serveur.
Améliorer progressivement les composants rendus du serveur pour faciliter la
navigation et la gestion des formulaires, et pour permettre le rendu en streaming.
Partager du code pour une logique commune sur le client et sur le serveur.
Interopérer avec JavaScript.
Intégrer des composants à des applications MVC, Razor Pages ou JavaScript
existantes.

Pour une présentation complète de Blazor, de son architecture et de ses avantages,


consultez Blazor ASP.NET Core et Modèles d’hébergement Blazor ASP.NET Core. Pour
commencer votre première application Blazor, consultez Créer votre première
application Blazor .

ASP.NET Core Razor Pages


Razor Pages est un modèle basé sur des pages pour la création d’interfaces utilisateur
web rendue par le serveur. L’interface utilisateur des pages Razor sont rendues
dynamiquement sur le serveur pour générer le code HTML et CSS de la page en réponse
à une demande du navigateur. La page arrive sur le client prête à s’afficher. La prise en
charge de Razor Pages repose sur ASP.NET Core MVC.
Avantages de Razor Pages :

Création et mise à jour rapides d’interface utilisateur. Le code de la page est


intégré à la page, tandis que les préoccupations d’interface utilisateur et de
logique métier sont gérées séparément.
Possibilité de tests et s’adapte aux applications volumineuses.
Organisation des pages ASP.NET Core plus simple qu’avec ASP.NET MVC :
La logique propre à l’affichage et les modèles d’affichage peuvent être
maintenus ensemble dans leur propre espace de noms et répertoire.
Les groupes de pages associées peuvent être conservés dans leur propre espace
de noms et répertoire.

Pour vous lancer dans votre première application ASP.NET Core Razor Pages, consultez
Tutoriel : Bien démarrer avec Razor Pages dans ASP.NET Core. Pour obtenir une
présentation complète d’ASP.NET Core Razor Pages, de son architecture et de ses
avantages, consultez Introduction à Razor Pages dans ASP.NET Core.

ASP.NET Core MVC


ASP.NET MVC rend l’interface utilisateur sur le serveur et utilise un modèle
d’architecture MVC (Modèle-Vue-Contrôleur). Le modèle MVC divise une application en
trois groupes de composants principaux : les modèles, les vues et les contrôleurs. Les
demandes utilisateur sont routées vers un contrôleur. Le contrôleur est chargé de
travailler avec le modèle pour effectuer des actions utilisateur ou récupérer les résultats
des requêtes. Le contrôleur choisit la vue à afficher à l’utilisateur et lui fournit les
données du modèle dont elle a besoin.

Avantages d’ASP.NET Core MVC :

Repose sur un modèle scalable et mature permettant de créer des applications


web de grande envergure.
Nette séparation des préoccupations pour une flexibilité maximale.
La séparation des responsabilités MVC est l’assurance que le modèle métier peut
évoluer sans être étroitement couplé aux détails d’implémentation de bas niveau.

Pour commencer avec ASP.NET Core MVC, consultez Bien démarrer avec ASP.NET Core
MVC. Pour obtenir une vue d’ensemble de l’architecture d’ASP.NET Core MVC et de ses
avantages, consultez Vue d’ensemble d’ASP.NET Core MVC.

Application monopage (SPA) ASP.NET Core


avec des frameworks JavaScript front-end
Créer une logique côté client pour des applications ASP.NET Core en utilisant des
frameworks JavaScript courants, comme Angular , React et Vue . ASP.NET Core
fournit des modèles de projet pour Angular, React et Vue, et il peut aussi être utilisé
avec d’autres frameworks JavaScript.

Avantages de l’application monopage (SPA) ASP.NET Core avec des frameworks


JavaScript, en plus des avantages de rendu client cités précédemment :

L’environnement d’exécution JavaScript accompagne déjà le navigateur.


Communauté importante et écosystème mature.
Créer une logique côté client pour des applications ASP.NET Core en utilisant des
frameworks JS courants, comme Angular, React et Vue.

Inconvénients :

Davantage de langages de codage, de frameworks et d’outils nécessaires.


Partage de code difficile en vue de dupliquer une partie de la logique.

Pour commencer, consultez :

Créer une application ASP.NET Core avec Angular


Créer une application ASP.NET Core avec React
Créer une application ASP.NET Core avec Vue
JavaScript et TypeScript dans Visual Studio

Choisir une solution hybride : ASP.NET Core


MVC ou Razor Pages plus Blazor
MVC, Razor Pages et Blazor font partie du framework ASP.NET Core et ont été conçus
pour être utilisés ensemble. Les composants Razor peuvent être intégrés dans les
applications Razor Pages et MVC. Quand une vue ou une page est affichée, les
composants peuvent dans le même temps faire l’objet d’un prérendu.

Avantages de MVC ou Razor Pages plus Blazor, en plus des avantages de MVC ou Razor
Pages :

Le prérendu exécute des composants Razor sur le serveur et les affiche dans une
vue ou une page, ce qui améliore le temps de chargement perçu de l’application.
Ajoute de l’interactivité aux vues ou pages existantes avec l’assistance au balisage
de composant.

Pour commencer à utiliser ASP.NET Core MVC ou Razor Pages plus Blazor, consultez
Intégration des composants ASP.NET Core Razor.
Étapes suivantes
Pour plus d'informations, consultez les pages suivantes :

ASP.NET Core Blazor


Modèles d’hébergement ASP.NET Core Blazor
Intégrer des composants Razor ASP.NET Core aux applications ASP.NET Core
Comparer les services gRPC avec les API HTTP

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Introduction à Razor Pages dans
ASP.NET Core
Article • 30/11/2023

Par Rick Anderson , Dave Brock et Kirk Larkin

Razor Pages peut rendre le codage des scénarios orientés page plus faciles et plus
productifs qu’en utilisant des contrôleurs et des vues.

Si vous cherchez un didacticiel qui utilise l’approche Model-View-Controller, consultez


Bien démarrer avec ASP.NET Core MVC.

Ce document fournit une introduction à Razor Pages. Il ne s’agit pas d’un didacticiel pas
à pas. Si certaines sections vous semblent trop avancées, consultez Bien démarrer avec
Razor Pages. Pour une vue d’ensemble d’ASP.NET Core, consultez Introduction à
ASP.NET Core.

Prérequis
Visual Studio

Visual Studio 2022 avec la charge de travail Développement web et


ASP.NET.
SDK .NET 6.0

Créer un projet Razor Pages


Visual Studio

Pour obtenir des instructions sur la création d’un projet Razor Pages, consultez Bien
démarrer avec Razor Pages.

Razor Pages
Razor Pages est activé dans Program.cs :

C#
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Dans le code précédent :

AddRazorPages ajoute des services pour Razor Pages à l’application.


MapRazorPages ajoute des points de terminaison pour Razor Pages à
IEndpointRouteBuilder.

Considérez une page de base :

CSHTML

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

Le code précédent ressemble beaucoup à un fichier de vue Razor utilisé dans une
application ASP.NET Core avec des contrôleurs et des vues. Ce qui le rend différent est
la directive @page. @page fait du fichier une action MVC, ce qui signifie qu’il gère les
demandes directement, sans passer par un contrôleur. @page doit être la première
directive Razor sur une page. @page affecte le comportement d’autres constructions
Razor. Les noms de fichier Razor Pages ont un suffixe .cshtml .

Une page similaire, utilisant une classe PageModel , est illustrée dans les deux fichiers
suivants. Le fichier Pages/Index2.cshtml :
CSHTML

@page
@using RazorPagesIntro.Pages
@model Index2Model

<h2>Separate page model</h2>


<p>
@Model.Message
</p>

Le modèle de page Pages/Index2.cshtml.cs :

C#

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;

namespace RazorPagesIntro.Pages
{
public class Index2Model : PageModel
{
public string Message { get; private set; } = "PageModel in C#";

public void OnGet()


{
Message += $" Server time is { DateTime.Now }";
}
}
}

Par convention, le fichier de classe PageModel a le même nom que le fichier de page
Razor, .cs y étant ajouté. Par exemple, la page Razor précédente est
Pages/Index2.cshtml . Le fichier contenant la classe PageModel est nommé

Pages/Index2.cshtml.cs .

Les associations des chemins d’URL aux pages sont déterminées par l’emplacement de
la page dans le système de fichiers. Le tableau suivant montre un chemin de page Razor
et l’URL correspondante :

Nom et chemin de fichier URL correspondante

/Pages/Index.cshtml / ou /Index

/Pages/Contact.cshtml /Contact

/Pages/Store/Contact.cshtml /Store/Contact
Nom et chemin de fichier URL correspondante

/Pages/Store/Index.cshtml /Store ou /Store/Index

Remarques :

Le runtime recherche les fichiers Razor Pages dans le dossier Pages par défaut.
Index est la page par défaut quand une URL n’inclut pas de page.

Écrire un formulaire de base


Razor Pages est conçu pour que les modèles courants utilisés avec les navigateurs web
soient faciles à implémenter lors de la création d’une application. La liaison de modèle,
les Tag Helpers et les assistances HTML fonctionnent avec les propriétés définies dans
une classe de page Razor. Considérez une page qui implémente un formulaire « Nous
contacter » de base pour le modèle Contact :

Pour les exemples de ce document, le DbContext est initialisé dans le fichier


Program.cs .

La base de données en mémoire nécessite le package NuGet


Microsoft.EntityFrameworkCore.InMemory .

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();
app.MapRazorPages();

app.Run();

Le modèle de données :

C#

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
public class Customer
{
public int Id { get; set; }

[Required, StringLength(10)]
public string? Name { get; set; }
}
}

Le contexte de la base de données :

C#

using Microsoft.EntityFrameworkCore;

namespace RazorPagesContacts.Data
{
public class CustomerDbContext : DbContext
{
public CustomerDbContext (DbContextOptions<CustomerDbContext>
options)
: base(options)
{
}

public DbSet<RazorPagesContacts.Models.Customer> Customer =>


Set<RazorPagesContacts.Models.Customer>();
}
}

Le fichier de vue Pages/Customers/Create.cshtml :

CSHTML

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<p>Enter a customer name:</p>

<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>

Le modèle de page Pages/Customers/Create.cshtml.cs :

C#

public class CreateModel : PageModel


{
private readonly Data.CustomerDbContext _context;

public CreateModel(Data.CustomerDbContext context)


{
_context = context;
}

public IActionResult OnGet()


{
return Page();
}

[BindProperty]
public Customer? Customer { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

if (Customer != null) _context.Customer.Add(Customer);


await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}
}

Par convention, la classe PageModel se nomme <PageName>Model et se trouve dans le


même espace de noms que la page.

La classe PageModel permet de séparer la logique d’une page de sa présentation. Elle


définit des gestionnaires de page pour les demandes envoyées à la page et les données
utilisées pour l’afficher. Cette séparation permet :
La gestion des dépendances de page via l’injection de dépendances.
Test des unités

La page a une méthode de gestionnaire OnPostAsync , qui s’exécute sur les requêtes POST
(quand un utilisateur poste le formulaire). Vous pouvez ajouter des méthodes de
gestionnaire pour n’importe quel verbe HTTP. Les gestionnaires les plus courants sont :

OnGet pour initialiser l’état nécessaire pour la page. Dans le code précédent, la

méthode OnGet affiche la page Razor CreateModel.cshtml .


OnPost pour gérer les envois de formulaire.

Le suffixe de nommage Async est facultatif, mais il est souvent utilisé par convention
pour les fonctions asynchrones. Le code précédent est typique de Razor Pages.

Si vous êtes familiarisé avec les applications ASP.NET utilisant des contrôleurs et des
vues :

Le code de OnPostAsync dans l’exemple précédent est similaire à du code


généralement utilisé dans un contrôleur.
La plupart des primitives MVC comme la liaison de modèle, la validation et les
résultats des actions fonctionnent de la même façon avec les contrôleurs et Razor
Pages.

La méthode OnPostAsync précédente :

C#

[BindProperty]
public Customer? Customer { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

if (Customer != null) _context.Customer.Add(Customer);


await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}

Le flux de base de OnPostAsync :

Vérifiez s’il y a des erreurs de validation.


S’il n’y a aucune erreur, enregistrez les données et redirigez.
S’il y a des erreurs, réaffichez la page avec des messages de validation. Dans de
nombreux cas, les erreurs de validation seraient détectées sur le client et jamais
envoyées au serveur.

Le fichier de vue Pages/Customers/Create.cshtml :

CSHTML

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>

Le HTML rendu à partir de Pages/Customers/Create.cshtml :

HTML

<p>Enter a customer name:</p>

<form method="post">
Name:
<input type="text" data-val="true"
data-val-length="The field Name must be a string with a maximum
length of 10."
data-val-length-max="10" data-val-required="The Name field is
required."
id="Customer_Name" maxlength="10" name="Customer.Name" value=""
/>
<input type="submit" />
<input name="__RequestVerificationToken" type="hidden"
value="<Antiforgery token here>" />
</form>

Dans le code précédent, le formulaire est publié :

Avec des données valides :

La méthode de gestionnaire OnPostAsync appelle la méthode d’assistance


RedirectToPage. RedirectToPage retourne une instance de RedirectToPageResult.
RedirectToPage :
Est un résultat de l’action.
Est similaire à RedirectToAction ou à RedirectToRoute (utilisé dans les
contrôleurs et les vues).
Est personnalisé pour les pages. Dans l’exemple précédent, il redirige vers la
page Index racine ( /Index ). RedirectToPage est détaillé dans la section
Génération d’URL pour les pages.

Avec les erreurs de validation passées au serveur :


La méthode de gestionnaire OnPostAsync appelle la méthode d’assistance Page.
Page retourne une instance de PageResult. Le retour de Page est similaire à la

façon dont les actions dans les contrôleurs retournent View . PageResult est le
type de retour par défaut pour une méthode de gestionnaire. Une méthode de
gestionnaire qui retourne void restitue la page.
Dans l’exemple précédent, la publication du formulaire sans valeur fait que
ModelState.IsValid retourne false. Dans cet exemple, aucune erreur de validation
n’est affichée sur le client. La gestion des erreurs de validation est traitée plus
loin dans ce document.

C#

[BindProperty]
public Customer? Customer { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

if (Customer != null) _context.Customer.Add(Customer);


await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}

Avec les erreurs de validation détectées par la validation côté client :


Les données ne sont pas publiées sur le serveur.
La validation côté client est expliquée plus loin dans ce document.

La propriété Customer utilise l’attribut [BindProperty] pour accepter la liaison de


modèle :

C#
[BindProperty]
public Customer? Customer { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

if (Customer != null) _context.Customer.Add(Customer);


await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}

[BindProperty] ne doit pas être utilisé sur les modèles contenant des propriétés qui ne

doivent pas être modifiées par le client. Pour plus d’informations, consultez
Surpublication.

Par défaut, Razor Pages lie les propriétés seulement avec des verbes non- GET . La liaison
à des propriétés supprime la nécessité d’écrire du code pour convertir des données
HTTP dans le type du modèle. Elle réduit la quantité de code en utilisant la même
propriété pour afficher les champs de formulaire ( <input asp-for="Customer.Name"> ) et
accepter l’entrée.

2 Avertissement

Pour des raisons de sécurité, vous devez choisir de lier les données de requête GET
aux propriétés du modèle de page. Vérifiez l’entrée utilisateur avant de la mapper à
des propriétés. Le choix de la liaison GET convient pour les scénarios qui s’appuient
sur des valeurs de routage ou de chaîne de requête.

Pour lier une propriété sur des requêtes GET , définissez la propriété SupportsGet de
l’attribut [BindProperty] sur true :

C#

[BindProperty(SupportsGet = true)]

Pour plus d’informations, consultez ASP.NET Core Community Standup: Bind on


GET discussion (YouTube) .

Examen du fichier de vue Pages/Customers/Create.cshtml :


CSHTML

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>

Dans le code précédent, le Tag Helper d’entrée <input asp-for="Customer.Name" />


lie l’élément HTML <input> à l’expression de modèle Customer.Name .
@addTagHelper rend les Tag Helpers disponibles.

La page d’accueil
Index.cshtml est la page d’accueil :

CSHTML

@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts home page</h1>


<form method="post">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
@if (Model.Customers != null)
{
foreach (var contact in Model.Customers)
{
<tr>
<td> @contact.Id </td>
<td>@contact.Name</td>
<td>
<!-- <snippet_Edit> -->
<a asp-page="./Edit" asp-route-
id="@contact.Id">Edit</a> |
<!-- </snippet_Edit> -->
<!-- <snippet_Delete> -->
<button type="submit" asp-page-handler="delete" asp-
route-id="@contact.Id">delete</button>
<!-- </snippet_Delete> -->
</td>
</tr>
}
}
</tbody>
</table>
<a asp-page="Create">Create New</a>
</form>

La classe PageModel associée ( Index.cshtml.cs ) :

C#

public class IndexModel : PageModel


{
private readonly Data.CustomerDbContext _context;
public IndexModel(Data.CustomerDbContext context)
{
_context = context;
}

public IList<Customer>? Customers { get; set; }

public async Task OnGetAsync()


{
Customers = await _context.Customer.ToListAsync();
}

public async Task<IActionResult> OnPostDeleteAsync(int id)


{
var contact = await _context.Customer.FindAsync(id);

if (contact != null)
{
_context.Customer.Remove(contact);
await _context.SaveChangesAsync();
}

return RedirectToPage();
}
}

Le fichier Index.cshtml contient le balisage suivant :

CSHTML
<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |

Le <a /a> Tag Helper d’ancre a utilisé l’attribut asp-route-{value} pour générer un lien
vers la page Edit. Le lien contient des données d’itinéraire avec l’ID de contact. Par
exemple, https://localhost:5001/Edit/1 Les Tag Helpers permettent au code côté
serveur de participer à la création et au rendu des éléments HTML dans les fichiers
Razor.

Le fichier Index.cshtml contient également le balisage pour créer un bouton Delete


(Supprimer) pour chaque contact client :

CSHTML

<button type="submit" asp-page-handler="delete" asp-route-


id="@contact.Id">delete</button>

Le HTML rendu :

HTML

<button type="submit" formaction="/Customers?


id=1&amp;handler=delete">delete</button>

Quand le bouton Delete est rendu en HTML, son action de formulaire inclut des
paramètres pour :

L’ID du contact client spécifié par l’attribut asp-route-id .


Le handler spécifié par l’attribut asp-page-handler .

Quand le bouton est sélectionné, une demande POST de formulaire est envoyée au
serveur. Par convention, le nom de la méthode de gestionnaire est sélectionné en
fonction de la valeur du paramètre handler conformément au schéma
OnPost[handler]Async .

Étant donné que le handler est delete dans cet exemple, la méthode de gestionnaire
OnPostDeleteAsync est utilisée pour traiter la demande POST . Si asp-page-handler est

défini sur une autre valeur, comme remove , une méthode de gestionnaire avec le nom
OnPostRemoveAsync est sélectionnée.

C#

public async Task<IActionResult> OnPostDeleteAsync(int id)


{
var contact = await _context.Customer.FindAsync(id);

if (contact != null)
{
_context.Customer.Remove(contact);
await _context.SaveChangesAsync();
}

return RedirectToPage();
}

La méthode OnPostDeleteAsync :

Obtient l’ id de la chaîne de requête.


Interroge la base de données pour le contact client avec FindAsync .
Si le contact client est trouvé, il est supprimé et la base de données est mise à jour.
Appelle RedirectToPage pour rediriger vers la page Index racine ( /Index ).

Le fichier Edit.cshtml
CSHTML

@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel

@{
ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Customer</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<input type="hidden" asp-for="Customer!.Id" />
<div class="form-group">
<label asp-for="Customer!.Name" class="control-label">
</label>
<input asp-for="Customer!.Name" class="form-control" />
<span asp-validation-for="Customer!.Name" class="text-
danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>

<div>
<a asp-page="./Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

La première ligne contient la directive @page "{id:int}" . La contrainte de routage "


{id:int}" indique à la page qu’elle doit accepter les requêtes qui contiennent des

données de routage int . Si une requête à la page ne contient de données d’itinéraire


qui peuvent être converties en int , le runtime retourne une erreur HTTP 404
(introuvable). Pour que l’ID soit facultatif, ajoutez ? à la contrainte d’itinéraire :

CSHTML

@page "{id:int?}"

Le fichier Edit.cshtml.cs :

C#

public class EditModel : PageModel


{
private readonly RazorPagesContacts.Data.CustomerDbContext _context;

public EditModel(RazorPagesContacts.Data.CustomerDbContext context)


{
_context = context;
}

[BindProperty]
public Customer? Customer { get; set; }

public async Task<IActionResult> OnGetAsync(int? id)


{
if (id == null)
{
return NotFound();
}

Customer = await _context.Customer.FirstOrDefaultAsync(m => m.Id ==


id);

if (Customer == null)
{
return NotFound();
}
return Page();
}

// To protect from overposting attacks, enable the specific properties


you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}

if (Customer != null)
{
_context.Attach(Customer).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CustomerExists(Customer.Id))
{
return NotFound();
}
else
{
throw;
}
}
}

return RedirectToPage("./Index");
}

private bool CustomerExists(int id)


{
return _context.Customer.Any(e => e.Id == id);
}
}

Validation
Les règles de validation :

Sont spécifiées de manière déclarative dans la classe de modèle.


Sont appliquées partout dans l’application.
L’espace de noms System.ComponentModel.DataAnnotations fournit un ensemble
d’attributs de validation intégrés qui sont appliqués de façon déclarative à une classe ou
à une propriété. DataAnnotations contient également des attributs de mise en forme,
comme [DataType], qui aident à effectuer la mise en forme et ne fournissent aucune
validation.

Considérons le modèle Customer :

C#

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
public class Customer
{
public int Id { get; set; }

[Required, StringLength(10)]
public string? Name { get; set; }
}
}

En utilisant le fichier de vue suivant Create.cshtml :

CSHTML

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<span asp-validation-for="Customer!.Name"></span>
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-
unobtrusive/jquery.validate.unobtrusive.js"></script>

Le code précédent :

Inclut jQuery et des scripts de validation jQuery.


Utilise le <div /> et <span /> Tag Helpers pour permettre :
La validation côté client.
Le rendu des erreurs de validation.

Génère le code HTML suivant :

HTML

<p>Enter a customer name:</p>

<form method="post">
Name:
<input type="text" data-val="true"
data-val-length="The field Name must be a string with a
maximum length of 10."
data-val-length-max="10" data-val-required="The Name field
is required."
id="Customer_Name" maxlength="10" name="Customer.Name"
value="" />
<input type="submit" />
<input name="__RequestVerificationToken" type="hidden"
value="<Antiforgery token here>" />
</form>

<script src="/lib/jquery/dist/jquery.js"></script>
<script src="/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="/lib/jquery-validation-
unobtrusive/jquery.validate.unobtrusive.js"></script>

La publication du formulaire Create (Créer) sans valeur pour le nom affiche le message
d’erreur « The Name field is required. » (Le champ Nom est requis.) sur le formulaire. Si
JavaScript est activé sur le client, le navigateur affiche l’erreur sans publier sur le serveur.

L’attribut [StringLength(10)] génère data-val-length-max="10" sur le HTML rendu.


data-val-length-max empêche les navigateurs d’autoriser une entrée supérieure à la

longueur maximale spécifiée. Si un outil comme Fiddler est utilisé pour modifier et
refaire la publication :

Avec le nom d’une longueur supérieure à 10.


Le message d’erreur « The field Name must be a string with a maximum length of
10. » (Le nom du champ doit être une chaîne dont la longueur maximale est de
10.) est retourné.

Considérez le modèle Movie suivant :

C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }

[StringLength(60, MinimumLength = 3)]


[Required]
public string Title { get; set; }

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; }
}
}

Les attributs de validation spécifient le comportement à appliquer sur les propriétés du


modèle auxquelles ils sont appliqués :

Les attributs Required et MinimumLength indiquent qu’une propriété doit avoir une
valeur, mais rien n’empêche un utilisateur d’entrer un espace pour satisfaire à cette
validation.

L’attribut RegularExpression sert à limiter les caractères pouvant être entrés. Dans
le code précédent, « Genre » :
Doit utiliser seulement des lettres.
La première lettre doit être une majuscule. Les espaces, les chiffres et les
caractères spéciaux ne sont pas autorisés.

L’expression RegularExpression « Rating » :


Nécessite que le premier caractère soit une lettre majuscule.
Autorise les caractères spéciaux et les chiffres aux emplacements qui suivent.
« PG-13 » est valide pour une évaluation, mais échoue pour un « Genre ».

L’attribut Range limite une valeur à une plage spécifiée.

L’attribut StringLength définit la longueur maximale d’une propriété de chaîne, et


éventuellement sa longueur minimale.

Les types valeur (tels que decimal , int , float et DateTime ) sont obligatoires par
nature et n’ont pas besoin de l’attribut [Required] .

La page Create pour le Movie modèle montre les erreurs avec des valeurs non valides :

Pour plus d'informations, consultez les pages suivantes :

Ajouter une validation à l’application Movie


Validation de modèle dans ASP.NET Core.

Isolation CSS
Isolez les styles CSS en pages, vues et composants individuels afin de réduire ou
d’éviter :

Les dépendances sur les styles globaux qui peuvent être difficiles à gérer.
Les conflits de style dans du contenu imbriqué.

Pour ajouter un fichier CSS délimité pour une page ou une vue, placez les styles CSS
dans un fichier .cshtml.css compagnon correspondant au nom du fichier .cshtml .
Dans l’exemple suivant, un fichier Index.cshtml.css fournit des styles CSS qui sont
appliqués seulement à la page ou à la vue Index.cshtml .

Pages/Index.cshtml.css (Razor Pages) ou Views/Index.cshtml.css (MVC) :

css

h1 {
color: red;
}

L’isolation CSS se produit au moment de la build. Le framework réécrit les sélecteurs


CSS pour qu’ils correspondent au balisage rendu par les pages ou les vues de
l’application. Les styles CSS réécrits sont regroupés et produits sous la forme d’une
ressource statique. {APP ASSEMBLY}.styles.css L’espace réservé {APP ASSEMBLY} est le
nom de l’assembly du projet. Un lien vers les styles CSS regroupés est placé dans la
disposition de l’application.

Dans le contenu <head> du fichier Pages/Shared/_Layout.cshtml de l’application (Razor


Pages) ou de Views/Shared/_Layout.cshtml (MVC), ajoutez ou vérifiez la présence du lien
vers les styles CSS regroupés :

HTML

<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />

Dans l’exemple suivant, le nom de l’assembly de l’application est WebApp :

HTML
<link rel="stylesheet" href="WebApp.styles.css" />

Les styles définis dans un fichier CSS délimité sont appliqués seulement à la sortie
rendue du fichier correspondant. Dans l’exemple précédent, les déclarations CSS h1
définies ailleurs dans l’application ne sont pas en conflit avec le style de titre de Index .
Les règles d’héritage et de cascade des styles CSS restent en vigueur pour les fichiers
CSS délimités. Par exemple, les styles appliqués directement à un élément <h1> du
fichier Index.cshtml remplacent les styles du fichier CSS délimité dans
Index.cshtml.css .

7 Notes

Pour garantir l’isolation du style CSS lors du regroupement, l’importation de CSS


dans des blocs de code Razor n’est pas prise en charge.

L’isolation CSS s’applique seulement aux éléments HTML. L’isolation CSS n’est pas
prise en charge pour les Tag Helpers.

Dans le fichier CSS regroupé, chaque page, vue ou composant Razor est associé à un
identificateur d’étendue au format b-{STRING} , où l’espace réservé {STRING} est une
chaîne de dix caractères générée par le framework. L’exemple suivant fournit le style
pour l’élément <h1> précédent dans la page Index d’une application Razor Pages :

css

/* /Pages/Index.cshtml.rz.scp.css */
h1[b-3xxtam6d07] {
color: red;
}

Dans la page Index où le style CSS est appliqué à partir du fichier regroupé,
l’identificateur d’étendue est ajouté en tant qu’attribut HTML :

HTML

<h1 b-3xxtam6d07>

L’identificateur est unique pour une application. Au moment de la build, un bundle de


projet est créé avec la convention {STATIC WEB ASSETS BASE PATH}/Project.lib.scp.css ,
où l’espace réservé {STATIC WEB ASSETS BASE PATH} est le chemin de base des
ressources web statiques.
Si d’autres projets sont utilisés, comme des packages NuGet ou des bibliothèques de
classes Razor, le fichier regroupé :

Fait référence aux styles en utilisant des importations CSS.


N’est pas publié en tant que ressource web statique de l’application qui
consomme les styles.

Prise en charge des préprocesseurs CSS


Les préprocesseurs CSS sont utiles pour améliorer le développement CSS en utilisant
des fonctionnalités comme les variables, l’imbrication, les modules, les mixins et
l’héritage. Bien que l’isolation CSS ne prenne pas en charge nativement les
préprocesseurs CSS comme Sass ou Less, l’intégration de préprocesseurs CSS se fait
sans problème dès lors que la compilation du préprocesseur se produit avant que le
framework réécrive les sélecteurs CSS lors du processus de build. Par exemple, avec
Visual Studio, configurez la compilation du préprocesseur existant en tant que tâche
Avant la build dans l’Explorateur d’exécuteur de tâches Visual Studio.

De nombreux packages NuGet tiers, comme AspNetCore.SassCompiler , peuvent


compiler des fichiers SASS/SCSS au début du processus de build avant que l’isolation
CSS ne se produise, et aucune configuration supplémentaire n’est requise.

Configuration de l’isolation CSS


L’isolation CSS permet la configuration de certains scénarios avancés, comme quand il
existe des dépendances sur des outils ou des workflows existants.

Personnaliser le format de l’identificateur d’étendue


Dans cette section, l’espace réservé {Pages|Views} est Pages pour les applications Razor
Pages ou Views pour les applications MVC.

Par défaut, les identificateurs d’étendue utilisent le format b-{STRING} , où l’espace


réservé {STRING} est une chaîne de dix caractères générée par le framework. Pour
personnaliser le format de l’identificateur d’étendue, mettez à jour le fichier projet avec
un modèle souhaité :

XML

<ItemGroup>
<None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-
identifier" />
</ItemGroup>

Dans l’exemple précédent, le CSS généré pour Index.cshtml.css change son


identificateur d’étendue de b-{STRING} en custom-scope-identifier .

Utilisez des identificateurs d’étendue pour mettre en œuvre l’héritage avec des fichiers
CSS délimités. Dans l’exemple de fichier projet suivant, un fichier BaseView.cshtml.css
contient des styles communs entre les vues. Un fichier DerivedView.cshtml.css hérite de
ces styles.

XML

<ItemGroup>
<None Update="{Pages|Views}/BaseView.cshtml.css" CssScope="custom-scope-
identifier" />
<None Update="{Pages|Views}/DerivedView.cshtml.css" CssScope="custom-
scope-identifier" />
</ItemGroup>

Utilisez l’opérateur de caractère générique ( * ) pour partager des identificateurs


d’étendue entre plusieurs fichiers :

XML

<ItemGroup>
<None Update="{Pages|Views}/*.cshtml.css" CssScope="custom-scope-
identifier" />
</ItemGroup>

Changer le chemin de base pour les ressources web


statiques
Le fichier CSS délimité est généré à la racine de l’application. Dans le fichier projet,
utilisez la propriété StaticWebAssetBasePath pour changer le chemin par défaut.
L’exemple suivant place le fichier CSS délimité et le reste des ressources de l’application
dans le chemin _content :

XML

<PropertyGroup>
<StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>
Désactiver le regroupement automatique
Pour ne pas accepter la façon dont l’infrastructure publie et charge des fichiers délimités
au moment de l’exécution, utilisez la propriété DisableScopedCssBundling . Lors de
l’utilisation de cette propriété, d’autres outils ou processus sont chargés de prendre les
fichiers CSS isolés du répertoire obj , et de les publier et de les charger au moment de
l’exécution :

XML

<PropertyGroup>
<DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>

Prise en charge de la bibliothèque de classes


Razor (RCL)
Quand une bibliothèque de classes Razor(RCL) fournit des styles isolés, l’attribut href
de la balise <link> pointe vers {STATIC WEB ASSET BASE PATH}/{PACKAGE
ID}.bundle.scp.css , où les espaces réservés sont :

{STATIC WEB ASSET BASE PATH} : le chemin de base de la ressource web statique.

{PACKAGE ID} : l’identificateur de package de la bibliothèque. L’identificateur de

package est défini par défaut sur le nom de l’assembly du projet si l’identificateur
de package n’est pas spécifié dans le fichier projet.

Dans l’exemple suivant :

Le chemin de base de la ressource web statique est _content/ClassLib .


Le nom de l’assembly de la bibliothèque de classes est ClassLib .

Pages/Shared/_Layout.cshtml (Razor Pages) ou Views/Shared/_Layout.cshtml (MVC) :

HTML

<link href="_content/ClassLib/ClassLib.bundle.scp.css" rel="stylesheet">

Pour plus d’informations sur les bibliothèques de classes Razor, consultez les articles
suivants :

Interface utilisateur Razor réutilisable dans les bibliothèques de classes avec


ASP.NET Core
Consommer des composants Razor ASP.NET Core d’une bibliothèque de classes
Razor (RCL)

Pour plus d’informations sur l’isolation CSS de Blazor, consultez Isolation CSS Blazor
d’ASP.NET Core.

Gérer les requêtes HEAD avec un gestionnaire


OnGet de secours
Les demandes HEAD vous permettent de récupérer les en-têtes pour une ressource
spécifique. Contrairement aux requêtes GET , les requêtes HEAD ne retournent pas un
corps de réponse.

En règle générale, un gestionnaire OnHead est créé et appelé pour les requêtes HEAD :

C#

Razor Pages se rabat sur un appel du gestionnaire OnGet si aucun gestionnaire OnHead
n’est défini.

XSRF/CSRF et Razor Pages


Les pages Razor sont protégées par la validation antifalsification. FormTagHelper injecte
des jetons antifalsification dans les éléments de formulaire HTML.

Utilisation de dispositions, de lignes de code


partiellement exécutées, de modèles et de Tag
Helpers avec Razor Pages
Les pages fonctionnent avec toutes les fonctionnalités du moteur de vue Razor. Les
dispositions, les lignes de code partiellement exécutées, les modèles, les Tag Helpers,
_ViewStart.cshtml et _ViewImports.cshtml fonctionnent de la même façon que pour les

vues Razor conventionnelles.

Nous allons nettoyer un peu cette page en tirant parti de certaines de ces
fonctionnalités.

Ajoutez une page de disposition à Pages/Shared/_Layout.cshtml :


CSHTML

<!DOCTYPE html>
<html>
<head>
<title>RP Sample</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
<a asp-page="/Index">Home</a>
<a asp-page="/Customers/Create">Create</a>
<a asp-page="/Customers/Index">Customers</a> <br />

@RenderBody()
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-
unobtrusive/jquery.validate.unobtrusive.js"></script>
</body>
</html>

La disposition :

Contrôle la disposition de chaque page (à moins que la page ne refuse la


disposition).
Importe des structures HTML telles que JavaScript et les feuilles de style.
Le contenu de la page Razor est rendu là où @RenderBody() est appelé.

Pour plus d’informations, consultez Page de disposition.

La propriété Layout est définie dans Pages/_ViewStart.cshtml :

CSHTML

@{
Layout = "_Layout";
}

La disposition est dans le dossier Pages/Shared. Les pages recherchent d’autres vues
(dispositions, modèles, partiels) hiérarchiquement, en commençant dans le même
dossier que la page active. Une disposition dans le dossier Pages/Shared peut être
utilisée depuis n’importe quelle page Razor sous le dossier Pages.

Le fichier de disposition doit être placé dans le dossier Pages/Shared.

Nous vous recommandons de ne pas placer le fichier de disposition dans le dossier


Views/Shared. Views/Shared est un modèle de vues MVC. Les pages Razor sont censées
s’appuyer sur la hiérarchie des dossiers, et non pas sur les conventions des chemins.
La recherche des vues depuis une page Razor inclut le dossier Pages. Les dispositions,
les modèles et les lignes de code partiellement exécutées que vous utilisez avec les
contrôleurs MVC et les vues Razor conventionnelles fonctionnent normalement.

Ajoutez un fichier Pages/_ViewImports.cshtml :

CSHTML

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@namespace est expliqué plus loin dans le didacticiel. La directive @addTagHelper permet

de bénéficier des Tag Helpers intégrés dans toutes les pages du dossier Pages.

La directive @namespace définie sur une page :

CSHTML

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

<h2>Name space</h2>
<p>
@Model.Message
</p>

La directive @namespace définit l’espace de noms pour la page. La directive @model n’a
pas besoin d’inclure l’espace de noms.

Quand la directive @namespace est contenue dans _ViewImports.cshtml , l’espace de


noms spécifié fournit le préfixe de l’espace de noms généré dans la Page qui importe la
directive @namespace . Le reste de l’espace de noms généré (la partie suffixe) est le
chemin relatif avec le point comme séparateur entre le dossier contenant
_ViewImports.cshtml et le dossier contenant la page.

Par exemple, la classe PageModel de Pages/Customers/Edit.cshtml.cs définit


explicitement l’espace de noms :

C#

namespace RazorPagesContacts.Pages
{
public class EditModel : PageModel
{
private readonly AppDbContext _db;

public EditModel(AppDbContext db)


{
_db = db;
}

// Code removed for brevity.

Le fichier Pages/_ViewImports.cshtml définit l’espace de noms suivant :

CSHTML

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

L’espace de noms généré pour la page Razor Pages/Customers/Edit.cshtml est identique


à la classe PageModel .

@namespace fonctionne également avec les vues Razor conventionnelles.

Considérez le fichier de vue Pages/Customers/Create.cshtml :

CSHTML

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<form method="post">
<div asp-validation-summary="ModelOnly"></div>
<span asp-validation-for="Customer!.Name"></span>
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-
unobtrusive/jquery.validate.unobtrusive.js"></script>

Le fichier de vue Pages/Customers/Create.cshtml mis à jour avec _ViewImports.cshtml et


le fichier de disposition précédent :

CSHTML
@page
@model CreateModel

<p>Enter a customer name:</p>

<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>

Dans le code précédent, _ViewImports.cshtml a importé l’espace de noms et des Tag


Helpers. Le fichier de disposition a importé les fichiers JavaScript.

Le projet de démarrage Razor Pages contient le


Pages/_ValidationScriptsPartial.cshtml , qui connecte la validation côté client.

Pour plus d’informations sur les vues partielles, consultez Vues partielles dans ASP.NET
Core.

Génération d’URL pour les pages


La page Create , illustrée précédemment, utilise RedirectToPage :

C#

public class CreateModel : PageModel


{
private readonly Data.CustomerDbContext _context;

public CreateModel(Data.CustomerDbContext context)


{
_context = context;
}

public IActionResult OnGet()


{
return Page();
}

[BindProperty]
public Customer? Customer { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}
if (Customer != null) _context.Customer.Add(Customer);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}
}

L’application a la structure de fichiers/dossiers suivante :

Pages/

Index.cshtml

Privacy.cshtml

/Customers
Create.cshtml
Edit.cshtml

Index.cshtml

Les pages Pages/Customers/Create.cshtml et Pages/Customers/Edit.cshtml redirigent


vers Pages/Customers/Index.cshtml après réussite. La chaîne ./Index est un nom de
page relatif utilisé pour accéder à la page précédente. Il est utilisé pour générer des URL
vers la page Pages/Customers/Index.cshtml . Par exemple :

Url.Page("./Index", ...)
<a asp-page="./Index">Customers Index Page</a>

RedirectToPage("./Index")

Le nom de page absolu /Index est utilisé pour générer des URL vers la page
Pages/Index.cshtml . Par exemple :

Url.Page("/Index", ...)
<a asp-page="/Index">Home Index Page</a>

RedirectToPage("/Index")

Le nom de la page est le chemin de la page à partir du dossier racine /Pages avec un /
devant (par exemple, /Index ). Les exemples de génération d’URL précédents offrent des
options améliorées et des capacités fonctionnelles par rapport au codage en dur d’une
URL. La génération d’URL utilise le routage et peut générer et encoder des paramètres
en fonction de la façon dont l’itinéraire est défini dans le chemin de destination.
La génération d’URL pour les pages prend en charge les noms relatifs. Le tableau suivant
montre la page Index sélectionnée en utilisant différents paramètres RedirectToPage
dans Pages/Customers/Create.cshtml .

RedirectToPage(x) Page

RedirectToPage("/Index") Pages/Index

RedirectToPage("./Index"); Pages/Customers/Index

RedirectToPage("../Index") Pages/Index

RedirectToPage("Index") Pages/Customers/Index

RedirectToPage("Index") , RedirectToPage("./Index") et RedirectToPage("../Index")

sont des noms relatifs. Le paramètre RedirectToPage est combiné avec le chemin de la
page active pour calculer le nom de la page de destination.

La liaison de nom relatif est utile lors de la création de sites avec une structure
complexe. Quand des noms relatifs sont utilisés pour lier des pages dans un dossier :

Le renommage d’un dossier ne rompt pas les liens relatifs.


Les liens ne sont pas rompus, car ils n’incluent pas le nom du dossier.

Pour rediriger vers une page située dans une autre Zone, spécifiez la zone :

C#

RedirectToPage("/Index", new { area = "Services" });

Pour plus d’informations, consultez Zones dans ASP.NET Core et Conventions des routes
et des applications Razor dans ASP.NET Core.

Attribut ViewData
Des données peuvent être passées à une page avec ViewDataAttribute. Les valeurs des
propriétés ayant l’attribut [ViewData] sont stockées dans le ViewDataDictionary et
chargées depuis celui-ci.

Dans l’exemple suivant, AboutModel applique l’attribut [ViewData] à la propriété Title :

C#

public class AboutModel : PageModel


{
[ViewData]
public string Title { get; } = "About";

public void OnGet()


{
}
}

Dans la page À propos de, accédez à la propriété Title en tant que propriété de
modèle :

CSHTML

<h1>@Model.Title</h1>

Dans la disposition, le titre est lu à partir du dictionnaire ViewData :

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...

TempData
ASP.NET Core expose le TempData. Cette propriété stocke les données jusqu’à ce
qu’elles soient lues. Vous pouvez utiliser les méthodes Keep et Peek pour examiner les
données sans suppression. TempData est utile pour la redirection, quand des données
sont nécessaires pour plusieurs requêtes.

Le code suivant définit la valeur de Message à l’aide de TempData :

C#

public class CreateDotModel : PageModel


{
private readonly AppDbContext _db;

public CreateDotModel(AppDbContext db)


{
_db = db;
}

[TempData]
public string Message { get; set; }
[BindProperty]
public Customer Customer { get; set; }

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
Message = $"Customer {Customer.Name} added";
return RedirectToPage("./Index");
}
}

Le balisage suivant dans le fichier Pages/Customers/Index.cshtml affiche la valeur de


Message en utilisant TempData .

CSHTML

<h3>Msg: @Model.Message</h3>

Le modèle de page Pages/Customers/Index.cshtml.cs applique l’attribut [TempData] à la


propriété Message .

C#

[TempData]
public string Message { get; set; }

Pour plus d’informations, consultez TempData.

Plusieurs gestionnaires par page


La page suivante génère un balisage pour deux gestionnaires en utilisant le Tag Helper
asp-page-handler :

CSHTML

@page
@model CreateFATHModel

<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<!-- <snippet_Handlers> -->
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC"
/>
<!-- </snippet_Handlers> -->
</form>
</body>
</html>

Le formulaire dans l’exemple précédent contient deux boutons d’envoi, chacun utilisant
FormActionTagHelper pour envoyer à une URL différente. L’attribut asp-page-handler est

un complément de asp-page . asp-page-handler génère des URL qui envoient à chacune


des méthodes de gestionnaire définies par une page. asp-page n’est pas spécifié, car
l’exemple établit une liaison à la page active.

Le modèle de page :

C#

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages.Customers
{
public class CreateFATHModel : PageModel
{
private readonly AppDbContext _db;

public CreateFATHModel(AppDbContext db)


{
_db = db;
}

[BindProperty]
public Customer Customer { get; set; }

public async Task<IActionResult> OnPostJoinListAsync()


{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}

public async Task<IActionResult> OnPostJoinListUCAsync()


{
if (!ModelState.IsValid)
{
return Page();
}
Customer.Name = Customer.Name?.ToUpperInvariant();
return await OnPostJoinListAsync();
}
}
}

Le code précédent utilise des méthodes de gestionnaire nommées. Pour créer des
méthodes de gestionnaire nommées, il faut prendre le texte dans le nom après On<HTTP
Verb> et avant Async (le cas échéant). Dans l’exemple précédent, les méthodes de page

sont OnPostJoinListAsync et OnPostJoinListUCAsync. Avec OnPost et Async supprimés,


les noms des gestionnaires sont JoinList et JoinListUC .

CSHTML

<input type="submit" asp-page-handler="JoinList" value="Join" />


<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />

Avec le code précédent, le chemin d’URL qui envoie à OnPostJoinListAsync est


https://localhost:5001/Customers/CreateFATH?handler=JoinList . Le chemin d’URL qui

envoie à OnPostJoinListUCAsync est https://localhost:5001/Customers/CreateFATH?


handler=JoinListUC .

Itinéraires personnalisés
Utilisez la directive @page pour :

Spécifier une route personnalisée vers une page. Par exemple, la route vers la page
À propos peut être définie sur /Some/Other/Path avec @page "/Some/Other/Path" .
Ajouter des segments à la route par défaut d’une page. Par exemple, un segment
« item » peut être ajouté à la route par défaut d’une page avec @page "item" .
Ajouter des paramètres à la route par défaut d’une page. Par exemple, un
paramètre d’ID, id , peut être nécessaire pour une page avec @page "{id}" .
Un chemin relatif racine désigné par un tilde ( ~ ) au début du chemin est pris en charge.
Par exemple, @page "~/Some/Other/Path" est identique à @page "/Some/Other/Path" .

Si vous ne voulez pas avoir la chaîne de requête ?handler=JoinList dans l’URL, changez
la route pour placer le nom du gestionnaire dans la partie « chemin » de l’URL. La route
peut être personnalisée en ajoutant un modèle de route placé entre des guillemets
doubles après la directive @page .

CSHTML

@page "{handler?}"
@model CreateRouteModel

<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC"
/>
</form>
</body>
</html>

Avec le code précédent, le chemin d’URL qui envoie à OnPostJoinListAsync est


https://localhost:5001/Customers/CreateFATH/JoinList . Le chemin d’URL qui envoie à
OnPostJoinListUCAsync est https://localhost:5001/Customers/CreateFATH/JoinListUC .

Le ? suivant handler signifie que le paramètre d’itinéraire est facultatif.

Emplacement des fichiers JavaScript (JS)


La colocation de fichiers JavaScript (JS) pour les pages, les vues et les composants Razor
est un moyen pratique d’organiser les scripts dans une application.

Colocalisez des fichiers JS à l’aide des conventions d’extension de nom de fichier


suivantes :

Pages des applications Razor Pages et vues des applications MVC : .cshtml.js .
Exemples :
Pages/Index.cshtml.js pour la page Index d’une application Razor Pages dans
Pages/Index.cshtml .

Views/Home/Index.cshtml.js pour la vue Index d’une application MVC dans


Views/Home/Index.cshtml .

Composants Razor des applications Blazor : .razor.js . Exemple : Index.razor.js


pour le composant Index .

Les fichiers JS colocalisés sont accessibles publiquement à l’aide du chemin vers le


fichier dans le projet :

Pages, vues et composants à partir d’un fichier de scripts colocalisé dans


l’application :

{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

L’espace réservé {PATH} correspond au chemin vers la page, la vue ou le


composant.
L’espace réservé {PAGE, VIEW, OR COMPONENT} correspond à la page, à la vue ou
au composant.
L’espace réservé {EXTENSION} correspond à l’extension de la page, de la vue ou
du composant, razor ou cshtml .

Exemple de Razor Pages :

Un fichier JS pour la page Index est placé dans le dossier Pages


( Pages/Index.cshtml.js ) en regard de la page Index ( Pages/Index.cshtml ). Dans la
page Index , le script est référencé au niveau du chemin dans le dossier Pages :

razor

@section Scripts {
<script src="~/Pages/Index.cshtml.js"></script>
}

Quand l’application est publiée, l’infrastructure déplace automatiquement le script


vers la racine web. Dans l’exemple précédent, le script est déplacé vers
bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js ,

où l’espace réservé {TARGET FRAMEWORK MONIKER} est le moniker de framework cible


(TFM). Aucune modification n’est requise pour l’URL relative du script dans la page
Index .

Exemple Blazor :
Un fichier JS pour le composant Index est placé à côté du composant Index
( Index.razor ). Dans le composant Index , le script est référencé à son chemin.

Index.razor.js :

JavaScript

export function showPrompt(message) {


return prompt(message, 'Type anything here');
}

Dans la méthode OnAfterRenderAsync du composant Index ( Index.razor ) :

razor

module = await JS.InvokeAsync<IJSObjectReference>(


"import", "./Components/Pages/Index.razor.js");

Quand l’application est publiée, l’infrastructure déplace automatiquement le script


vers la racine web. Dans l’exemple précédent, le script est déplacé vers
bin\Release\{TARGET FRAMEWORK

MONIKER}\publish\wwwroot\Components\Pages\Index.razor.js , où l’espace réservé


{TARGET FRAMEWORK MONIKER} est le moniker de framework cible (TFM). Aucune

modification n’est requise pour l’URL relative du script dans le composant Index .

Pour les scripts fournis par une bibliothèque de classes (RCL) Razor :

_content/{PACKAGE ID}/{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

L’espace réservé {PACKAGE ID} est l’identificateur de package de la bibliothèque


RCL (ou le nom de la bibliothèque pour une bibliothèque de classes référencée
par l’application).
L’espace réservé {PATH} correspond au chemin vers la page, la vue ou le
composant. Si un composant Razor se trouve à la racine de la bibliothèque RCL,
le segment de chemin n’est pas inclus.
L’espace réservé {PAGE, VIEW, OR COMPONENT} correspond à la page, à la vue ou
au composant.
L’espace réservé {EXTENSION} correspond à l’extension de la page, de la vue ou
du composant, razor ou cshtml .

Dans l’exemple d’application Blazor suivant :


L’identificateur de package de la bibliothèque RCL est AppJS .
Les scripts d’un module sont chargés pour le composant Index ( Index.razor ).
Le composant Index se trouve dans le dossier Pages du dossier Components du
RCL.

C#

var module = await JS.InvokeAsync<IJSObjectReference>("import",


"./_content/AppJS/Components/Pages/Index.razor.js");

Configuration et paramètres avancés


La configuration et les paramètres des sections suivantes ne sont pas nécessaires pour la
plupart des applications.

Pour configurer les options avancées, utilisez la surcharge AddRazorPages qui configure
RazorPagesOptions :

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
options.RootDirectory = "/MyPages";
options.Conventions.AuthorizeFolder("/MyPages/Admin");
});

builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();
app.Run();

Utilisez RazorPagesOptions pour définir le répertoire racine pour les pages ou ajouter
des conventions de modèle d’application pour les pages. Pour plus d’informations sur
les conventions, consultez Conventions des autorisations de Razor Pages.

Pour précompiler des vues, consultez Compilation des vues Razor.

Spécifier que les pages Razor se trouvent à la racine du


contenu
Par défaut, les pages Razor sont placées à la racine du répertoire /Pages. Ajoutez
WithRazorPagesAtContentRoot pour spécifier que vos pages Razor se trouvent à la
racine du contenu (ContentRootPath) de l’application :

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesAtContentRoot();

builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();
Spécifier que les pages Razor se trouvent dans un
répertoire racine personnalisé
Ajoutez WithRazorPagesRoot pour spécifier que vos pages Razor se trouvent dans un
répertoire racine personnalisé de l’application (fournissez un chemin relatif) :

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesRoot("/path/to/razor/pages");

builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Ressources supplémentaires
Consultez Bien démarrer avec Razor Pages, qui s’appuie sur cette introduction.
Attribut d’autorisation et Razor Pages
Télécharger ou visualiser un exemple de code
Vue d’ensemble d’ASP.NET Core
Informations de référence sur la syntaxe Razor pour ASP.NET Core
Zones dans ASP.NET Core
Tutoriel : Bien démarrer avec Razor Pages dans ASP.NET Core
Conventions des autorisations de Razor Pages dans ASP.NET Core
Conventions des routes et des applications pour Razor Pages dans ASP.NET Core
Tests unitaires de pages Razor dans ASP.NET Core
Vues partielles dans ASP.NET Core

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriel : Créer une application web
Razor Pages avec ASP.NET Core
Article • 09/02/2024

Cette série de tutoriels explique les bases de la création d’une application web Razor
Pages.

Pour obtenir une présentation plus avancée destinée aux développeurs qui connaissent
bien les contrôleurs et les vues, consultez Présentation de Razor Pages dans ASP.NET
Core.

Si vous débutez dans le développement ASP.NET Core et que vous ne savez pas quelle
solution d’interface utilisateur web ASP.NET Core convient le mieux à vos besoins,
consultez Choisir une interface utilisateur ASP.NET Core.

Cette série comprend les tutoriels suivants :

1. Créer un projet d’application web Razor Pages


2. Ajouter un modèle à une application Razor Pages
3. Générer la structure de pagesRazor
4. Utiliser une base de données
5. Mettre à jour des pagesRazor
6. Ajouter une recherche
7. Ajouter un nouveau champ
8. Ajouter la validation

À la fin, vous disposez d’une application qui peut afficher et gérer une base de données
de films.
6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Tutoriel : Bien démarrer avec Razor
Pages dans ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

C’est le premier d’une série de tutoriels, qui décrit les principes fondamentaux liés à la
génération d’une application web de Razor Pages dans ASP.NET Core.

Pour obtenir une présentation plus avancée destinée aux développeurs qui connaissent
bien les contrôleurs et les vues, consultez Présentation de Razor Pages. Pour une
présentation vidéo, consultez Entity Framework Core pour les débutants .

Si vous débutez dans le développement ASP.NET Core et que vous ne savez pas quelle
solution d’interface utilisateur web ASP.NET Core convient le mieux à vos besoins,
consultez Choisir une interface utilisateur ASP.NET Core.

À la fin de ce tutoriel, vous disposerez d’une application web Razor Pages qui gère une
base de données de films.

Prérequis
Visual Studio

Préversion de Visual Studio 2022 avec la charge de travail ASP.NET et


développement web.
Créer une application web Razor Pages
Visual Studio

Ouvrez Visual Studio et sélectionnez Nouveau projet.

Dans la boîte de dialogue Créer un projet, sélectionnez Application web


ASP.NET Core (Razor Pages)>Suivant.

Dans la boîte de dialogue Configurer votre nouveau projet, entrez


RazorPagesMovie pour Nom du projet. Il est important de nommer le projet

RazorPagesMovie, en respectant la casse, pour que les espaces de noms


correspondent quand vous copiez et collez l’exemple de code.

Sélectionnez Suivant.

Dans la boîte de dialogue Informations supplémentaires :


Sélectionnez .NET 8.0 (prise en charge à long terme).
Vérifiez que l’option ne pas utiliser les instructions de niveau supérieur est
décochée.

Sélectionnez Créer.
Le projet de démarrage suivant est créé :
Pour obtenir d’autres approches pour créer le projet, consultez Créer un projet dans
Visual Studio.

Exécuter l'application
Visual Studio

Sélectionnez RazorPagesMovie dans l’Explorateur de solutions, puis appuyez sur


Ctrl+F5 pour l’exécuter sans le débogueur.

Visual Studio affiche la boîte de dialogue suivante lorsqu’un projet n’est pas encore
configuré pour utiliser SSL :

Sélectionnez Oui si vous faites confiance au certificat SSL d’IIS Express.

La boîte de dialogue suivante s’affiche :


Sélectionnez Oui si vous acceptez d’approuver le certificat de développement.

Pour plus d’informations sur l’approbation du navigateur Firefox, consultez Erreur


de certificat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.

Visual Studio :

Exécute l’application, qui lance le serveur Kestrel.


Lance le navigateur par défaut sur https://localhost:<port> , qui affiche
l’interface utilisateur des applications. <port> est le port aléatoire attribué lors
de la création de l’application.

Examiner les fichiers projet


Les sections suivantes contiennent une vue d’ensemble des principaux dossiers et
fichiers projet que vous allez utiliser dans les tutoriels suivants.

Dossier Pages
Contient les pages Razor et les fichiers de prise en charge. Chaque page Razor est une
paire de fichiers :

Fichier .cshtml qui a un balisage HTML avec du code C# avec la syntaxe Razor.
Un fichier .cshtml.cs qui contient du code C# gérant les événements de page.

Les fichiers de prise en charge ont des noms commençant par un trait de soulignement.
Par exemple, le fichier _Layout.cshtml configure des éléments d’interface utilisateur
communs à toutes les pages. _Layout.cshtml définit le menu de navigation en haut de
la page et la mention de copyright au bas de la page. Pour plus d’informations,
consultez Disposition dans ASP.NET Core.

Dossier racine
Contient les ressources statiques, comme les fichiers HTML, les fichiers JavaScript et les
fichiers CSS. Pour plus d’informations, consultez Fichiers statiques dans ASP.NET Core.

appsettings.json

Contient les données de configuration, comme les chaînes de connexion. Pour plus
d’informations, consultez Configuration dans ASP.NET Core.
Program.cs
Contient le code suivant :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Les lignes de code suivantes dans ce fichier créent un WebApplicationBuilder avec des
valeurs par défaut préconfigurées, ajoutent la prise en charge de Razor Pages au
conteneur d’injection de dépendances (DI) et créent l’application :

C#

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();

var app = builder.Build();

La page des exceptions de développeur est activée par défaut et fournit des
informations utiles sur les exceptions. Les applications de production ne doivent pas
être exécutées en mode développement, car la page des exceptions de développeur
peut divulguer des informations sensibles.

Le code suivant définit le point de terminaison d’exception sur /Error et active le


protocole HSTS (HTTP Strict Transport Security) lorsque l’application ne s’exécute pas en
mode développement :

C#

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

Par exemple, le code précédent s’exécute lorsque l’application est en mode production
ou test. Pour plus d’informations, consultez Utiliser plusieurs environnements dans
ASP.NET Core.

Le code suivant active différents intergiciels :

app.UseHttpsRedirection(); : redirige les requêtes HTTP vers HTTPS.


app.UseStaticFiles(); : permet de fournir des fichiers statiques, notamment

HTML, CSS, images et JavaScript. Pour plus d’informations, consultez Fichiers


statiques dans ASP.NET Core.
app.UseRouting(); : ajoute la correspondance d’itinéraire au pipeline d’intergiciels.

Pour plus d’informations, consultez Routage dans ASP.NET Core


app.MapRazorPages(); : configure le routage des points de terminaison pour Razor

Pages.
app.UseAuthorization(); : autorise un utilisateur à accéder à des ressources

sécurisées. Cette application n’utilise pas l’autorisation. Par conséquent, cette ligne
peut être supprimée.
app.Run(); : exécute l’application.

Résolution des problèmes avec l’exemple


terminé
Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code
au projet terminé. Afficher ou télécharger le projet terminé (comment télécharger).
Étapes suivantes
Suivant : Ajouter un modèle

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 2, ajouter un modèle à une
application de pages Razor dans
ASP.NET Core
Article • 30/11/2023

Dans ce tutoriel, des classes sont ajoutées pour la gestion des films dans une base de
données. Les classes de modèle de l’application utilisent Entity Framework Core (EF
Core) pour travailler avec la base de données. EF Core est un mappeur relationnel
d’objets (O/RM) qui simplifie l’accès aux données. Vous écrivez d’abord les classes du
modèle, puis EF Core crée la base de données.

Les classes de modèle portent le nom de classes OCT (« Objet CLR Traditionnel »), car
elles n’ont pas de dépendances envers EF Core. Elles définissent les propriétés des
données stockées dans la base de données.

Ajouter un modèle de données


Visual Studio

1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet


RazorPagesMovie>Ajouter>Nouveau dossier. Nommez le dossier Models .

2. Cliquez avec le bouton droit sur le dossier Models . Sélectionnez


Ajouter>Classe. Nommez la classe Movie.

3. Ajoutez les propriétés suivantes à la classe Movie :

C#

using System.ComponentModel.DataAnnotations;

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }
public string? Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
public decimal Price { get; set; }
}

La classe Movie contient :

Le champ ID est nécessaire à la base de données pour la clé primaire.

Attribut [DataType] qui spécifie le type de données dans la propriété


ReleaseDate . Avec cet attribut :

L’utilisateur n’est pas obligé d’entrer les informations de temps dans le


champ de date.
Seule la date est affichée, pas les informations de temps.

La présence du point d’interrogation après string indique que la propriété


peut accepter les valeurs Null. Pour plus d’informations, consultez Types
référence pouvant accepter la valeur Null.

Les DataAnnotations sont traitées dans un prochain didacticiel.

Générez le projet pour vérifier qu’il n’y a pas d’erreur de compilation.

Générer automatiquement le modèle de film


Dans cette section, le modèle de film est généré automatiquement. Autrement dit, l’outil
de génération de modèles automatique génère des pages pour les opérations de
création, de lecture, de mise à jour et de suppression (CRUD) pour le modèle de film.

Visual Studio

1. Créez le dossier Pages/Movies :


a. Cliquez avec le bouton droit sur le dossier Pages>Ajouter>Nouveau
dossier.
b. Nommez le dossier Movies.

2. Cliquez avec le bouton droit sur le dossier Pages/Movies>Ajouter>Nouvel


élément généré automatiquement.
3. Dans la boîte de dialogue Ajouter un nouveau modèle automatique,
sélectionnez Pages Razor utilisant Entity Framework (CRUD)>Ajouter.
4. Renseignez la boîte de dialogue Ajouter des pages Razor avec Entity
Framework (CRUD) :
a. Dans la liste déroulante Classe de modèle, sélectionnez Film
(RazorPagesMovie.Models).
b. Dans la ligne Classe du contexte de données, sélectionnez le signe +
(plus).
i. Dans la boîte de dialogue Ajouter un contexte de données, le nom de la
classe RazorPagesMovie.Data.RazorPagesMovieContext est généré.
ii. Dans la liste déroulante Fournisseur de base de données, sélectionnez
SQL Server.
c. Sélectionnez Ajouter.
Le fichier appsettings.json est mis à jour avec la chaîne de connexion utilisée pour
se connecter à une base de données locale.

Fichiers créés et mis à jour


Le processus de génération de modèles automatique crée les fichiers suivants :

Pages/Movies : Create, Delete, Details, Edit, Index.


Data/RazorPagesMovieContext.cs

Les fichiers créés sont expliqués dans le tutoriel suivant.

Le processus de génération de modèles automatique ajoute le code en surbrillance


suivant au fichier Program.cs :

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));

var app = builder.Build();

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Les modifications Program.cs sont expliquées plus loin dans ce tutoriel.

Créer le schéma de base de données initial à


l’aide de la fonctionnalité de migration d’EF
La fonctionnalité de migration dans Entity Framework Core permet de :

Créer le schéma de base de données initial.


Mettre à jour de façon incrémentielle le schéma de base de données afin de le
garder synchronisé avec le modèle de données de l’application. Les données
existantes dans la base de données sont conservées.

Visual Studio

Dans cette section, la fenêtre Console du gestionnaire de package est utilisée


pour :

Ajouter une migration initiale


Mettez à jour la base de données avec la migration initiale.

1. Dans le menu Outils, sélectionnez Gestionnaire de package NuGet>Console


du gestionnaire de package.

2. Dans la console du Gestionnaire de package, entrez les commandes


suivantes :

PowerShell

Add-Migration InitialCreate
Update-Database

La commande Add-Migration génère le code nécessaire à la création du


schéma de base de données initial. Le schéma est basé sur le modèle spécifié
dans DbContext . L’argument InitialCreate est utilisé pour nommer la
migration. Vous pouvez utiliser n’importe quel nom, mais par convention, un
nom décrivant la migration est sélectionné.

La commande Update-Database exécute la méthode Up dans des migrations


qui n’ont pas été appliquées. Dans ce cas, la commande exécute la méthode
Up dans le fichier Migrations/<time-stamp>_InitialCreate.cs , ce qui entraîne

la création de la base de données.

L’avertissement suivant s’affiche et sera traité dans une étape ultérieure :

Aucun type n’a été spécifié pour la colonne décimale 'Price' sur le type d’entité
'Movie'. Les valeurs sont tronquées en mode silencieux si elles ne sont pas
compatibles avec la précision et l’échelle par défaut. Spécifiez explicitement le type
de colonne SQL Server capable d’accueillir toutes les valeurs en utilisant
’HasColumnType()’.

Contexte de données RazorPagesMovieContext :

Dérive de Microsoft.EntityFrameworkCore.DbContext.
Spécifie les entités qui sont incluses dans le modèle de données.
Coordonne les fonctionnalités EF Core, telles que Créer, Lire, Mettre à jour et
Supprimer, pour le modèle Movie .

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Data
{
public class RazorPagesMovieContext : DbContext
{
public RazorPagesMovieContext
(DbContextOptions<RazorPagesMovieContext> options)
: base(options)
{
}

public DbSet<RazorPagesMovie.Models.Movie> Movie { get; set; } =


default!;
}
}

Le code précédent crée une propriété DbSet<Movie> pour le jeu d’entités. Dans la
terminologie Entity Framework, un jeu d’entités correspond généralement à une table
de base de données. Une entité correspond à une ligne dans la table.

Le nom de la chaîne de connexion est transmis au contexte en appelant une méthode


sur un objet DbContextOptions. Pour le développement local, le système de
configuration lit la chaîne de connexion à partir du fichier appsettings.json .

Tester l’application
1. Exécutez l’application et ajoutez /Movies à l’URL dans le navigateur
( http://localhost:port/movies ).

Si vous voyez le message d’erreur suivant :

Console

SqlException: Cannot open database "RazorPagesMovieContext-GUID"


requested by the login. The login failed.
Login failed for user 'User-name'.

Vous avez manqué l’étape des migrations.

2. Testez le lien Créer nouveau.

7 Notes
Vous ne pourrez peut-être pas entrer de virgules décimales dans le champ
Price . Pour prendre en charge la validation jQuery pour les paramètres
régionaux autres que « Anglais » qui utilisent une virgule (« , ») comme
décimale et des formats de date autres que le format « Anglais (États-Unis »),
l’application doit être localisée. Pour obtenir des instructions sur la
localisation, consultez ce problème GitHub .

3. Testez les liens Modifier, Détails et Supprimer.

Le prochain didacticiel décrit les fichiers créés par la génération de modèles


automatique.

Examiner le contexte inscrit avec l’injection de


dépendances
ASP.NET Core comprend l’injection de dépendances. Des services, tels que le contexte
de base de données EF Core, sont inscrits avec l’injection de dépendance au démarrage
de l’application. Ces services sont affectés aux composants qui les nécessitent (par
exemple les Pages Razor) par le biais de paramètres de constructeur. Le code de
constructeur qui obtient une instance de contexte de base de données est indiqué plus
loin dans le tutoriel.

L’outil de génération de modèles automatique a créé automatiquement un contexte de


base de données et l’a inscrit dans le conteneur d’injection de dépendances. Le code
mis en surbrillance suivant est ajouté au fichier Program.cs par le générateur de
modèles :

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Résolution des problèmes avec l’exemple


terminé
Si vous rencontrez un problème que vous ne pouvez pas résoudre, comparez votre code
au projet terminé. Afficher ou télécharger le projet terminé (comment télécharger).

Étapes suivantes
Précédent : Bien démarrer

Suivant : Pages Razor obtenues par génération de modèles automatique

6 Collaborer avec nous sur


GitHub
La source de ce contenu se
trouve sur GitHub, où vous
pouvez également créer et
examiner les problèmes et les
demandes de tirage. Pour plus
d’informations, consultez notre
guide du contributeur.
ASP.NET Core feedback
ASP.NET Core is an open source
project. Select a link to provide
feedback:

 Ouvrir un problème de
documentation

 Indiquer des commentaires sur


le produit
Partie 3, pages Razor obtenues par
génération de modèles automatique
dans ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Ce tutoriel décrit les pages Razor créées par génération de modèles automatique dans
le tutoriel précédent.

Pages Create, Delete, Details et Edit


Examinez le modèle de page Pages/Movies/Index.cshtml.cs :

C#

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies;

public class IndexModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

public IList<Movie> Movie { get;set; } = default!;

public async Task OnGetAsync()


{
if (_context.Movie != null)
{
Movie = await _context.Movie.ToListAsync();
}
}
}

Les pages Razor sont dérivées de PageModel. Par convention, la classe dérivée de
PageModel s’appelle PageNameModel . Par exemple, la page Index est nommée IndexModel .
Le constructeur utilise l’injection de dépendances pour ajouter RazorPagesMovieContext
à la page :

C#

public class IndexModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

Consultez Code asynchrone pour plus d’informations sur la programmation asynchrone


avec Entity Framework.

Quand une demande GET est effectuée pour la page, la méthode OnGetAsync retourne
une liste de films à la page Razor. Dans une page Razor, OnGetAsync ou OnGet est
appelée pour initialiser l’état de la page. Dans ce cas, OnGetAsync obtient une liste de
films et les affiche.

Si OnGet retourne void ou que OnGetAsync retourne Task , aucune instruction return
n’est utilisée. Par exemple, examinez la page Privacy :

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPagesMovie.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;

public PrivacyModel(ILogger<PrivacyModel> logger)


{
_logger = logger;
}

public void OnGet()


{
}
}
}
Lorsque le type de retour est IActionResult ou Task<IActionResult> , une instruction de
retour doit être spécifiée. Par exemple, la méthode Pages/Movies/Create.cshtml.cs
OnPostAsync :

C#

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_context.Movie.Add(Movie);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}

Examinez la page Razor Pages/Movies/Index.cshtml :

CSHTML

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.Id">Details</a>
|
<a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Razor peut passer du code HTML à du code C# ou au balisage spécifique à Razor.


Quand un symbole @ est suivi d’un mot clé réservé Razor, il est converti en balisage
spécifique à Razor. Sinon, il est converti en C#.

Directive @page
La directive Razor @page transforme le fichier en action MVC, ce qui lui permet de traiter
des demandes. @page doit être la première directive Razor sur une page. @page et
@model sont des exemples de transition vers un balisage spécifique à Razor. Consultez

Syntaxe Razor pour plus d’informations.

Directive @model
CSHTML

@page
@model RazorPagesMovie.Pages.Movies.IndexModel
La directive @model spécifie le type du modèle passé à la page Razor. Dans l’exemple
précédent, la ligne @model rend la classe dérivée de PageModel accessible à la page
Razor. Le modèle est utilisé dans les HTML Helpers @Html.DisplayNameFor et
@Html.DisplayFor de la page.

Examinez l’expression lambda utilisée dans le HTML Helper suivant :

CSHTML

@Html.DisplayNameFor(model => model.Movie[0].Title)

Le HTML Helper DisplayNameFor inspecte la propriété Title référencée dans


l’expression lambda pour déterminer le nom d’affichage. L’expression lambda est
inspectée plutôt qu’évaluée. Cela signifie qu’il n’existe pas de violation d’accès quand
model , model.Movie ou model.Movie[0] a la valeur null ou est vide. Quand l’expression

lambda est évaluée, par exemple avec @Html.DisplayFor(modelItem => item.Title) , les
valeurs de propriété du modèle sont évaluées.

La page de disposition
Sélectionnez les liens de menu RazorPagesMovie, Home et Privacy. Chaque page
affiche la même disposition de menu. La disposition du menu est implémentée dans le
fichier Pages/Shared/_Layout.cshtml .

Ouvrez et examinez le fichier Pages/Shared/_Layout.cshtml .

Les modèles de Disposition permettent que la disposition du conteneur HTML soit :

spécifiée à un seul endroit ;


appliquée à plusieurs pages du site.

Recherchez la ligne @RenderBody() . RenderBody est un espace réservé dans lequel toutes
les vues propres à la page s’affichent, encapsulées dans la page de disposition. Par
exemple, sélectionnez le lien Privacy et la vue Pages/Privacy.cshtml est affichée à
l’intérieur de la méthode RenderBody .

ViewData et disposition
Examinez le balisage suivant à partir du fichier Pages/Movies/Index.cshtml :

CSHTML
@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

Le balisage précédent en surbrillance est un exemple de conversion de Razor vers C#.


Les caractères { et } délimitent un bloc de code C#.

La classe de base PageModel contient une propriété de dictionnaire ViewData qui peut
être utilisée pour transmettre des données à une vue. Des objets sont ajoutés au
dictionnaire ViewData à l’aide d’un modèle clé valeur. Dans l’exemple précédent, la
propriété Title est ajoutée au dictionnaire ViewData .

La propriété Title est utilisée dans le fichier Pages/Shared/_Layout.cshtml . Le balisage


suivant montre les premières lignes du fichier _Layout.cshtml .

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"
/>
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
<link rel="stylesheet" href="~/RazorPagesMovie.styles.css" asp-append-
version="true" />

La ligne @*Markup removed for brevity.*@ est un commentaire Razor. Contrairement


aux commentaires HTML <!-- --> , les commentaires Razor ne sont pas envoyés au
client. Consultez Documents web MDN : Prise en main du code HTML pour plus
d’informations.

Mettre à jour la disposition


1. Changez l’élément <title> dans le fichier Pages/Shared/_Layout.cshtml pour
afficher Movie au lieu de RazorPagesMovie.

CSHTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-
scale=1.0" />
<title>@ViewData["Title"] - Movie</title>

2. Recherchez l’élément anchor suivant dans le fichier Pages/Shared/_Layout.cshtml .

CSHTML

<a class="navbar-brand" asp-area="" asp-


page="/Index">RazorPagesMovie</a>

3. Remplacez l’élément précédent par la balise suivante :

CSHTML

<a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>

L’élément anchor précédent est un Tag Helper. Dans le cas présent, il s’agit du Tag
Helper d’ancre. L’attribut et la valeur du Tag Helper asp-page="/Movies/Index"
créent un lien vers la page Razor /Movies/Index . La valeur de l’attribut asp-area est
vide : la zone n’est donc pas utilisée dans le lien. Pour plus d’informations,
consultez Zones.

4. Enregistrez les modifications et testez l’application en sélectionnant le lien


RpMovie. Consultez le fichier _Layout.cshtml dans GitHub si vous rencontrez des
problèmes.

5. Testez les liens Home, RpMovie, Create, Edit et Delete. Chaque page définit le
titre, que vous pouvez voir dans l’onglet du navigateur. Quand vous définissez un
signet pour une page, le titre est affecté au signet.

7 Notes

Vous ne pourrez peut-être pas entrer de virgules décimales dans le champ Price .
Pour prendre en charge la validation jQuery pour les paramètres régionaux
autres que l’anglais qui utilisent une virgule (« , ») comme point décimal, et des
formats de date autres que l’anglais des États-Unis, vous devez prendre des
mesures pour mondialiser l’application. Consultez cette page GitHub problème
4076 pour savoir comment ajouter une virgule décimale.

La propriété Layout est définie dans le fichier Pages/_ViewStart.cshtml :

CSHTML

@{
Layout = "_Layout";
}

Le balisage précédent définit le fichier de disposition Pages/Shared/_Layout.cshtml pour


tous les fichiers Razor du dossier Pages. Pour plus d’informations, consultez Disposition.

Le modèle de page Create


Examinez le modèle de page Pages/Movies/Create.cshtml.cs :

C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext
_context;

public CreateModel(RazorPagesMovie.Data.RazorPagesMovieContext
context)
{
_context = context;
}

public IActionResult OnGet()


{
return Page();
}

[BindProperty]
public Movie Movie { get; set; } = default!;

// To protect from overposting attacks, see


https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || _context.Movie == null || Movie ==
null)
{
return Page();
}

_context.Movie.Add(Movie);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}
}
}

La méthode OnGet initialise l’état nécessaire pour la page. La page Create n’ayant aucun
état à initialiser, Page est retourné. Plus loin dans le tutoriel, un exemple d’état
d’initialisation OnGet est affiché. La méthode Page crée un objet PageResult qui affiche
la page Create.cshtml .

La propriété Movie utilise l’attribut [BindProperty] pour adhérer à la liaison de modèles.


Quand le formulaire Create publie les valeurs de formulaire, le runtime ASP.NET Core lie
les valeurs publiées au modèle Movie .

La méthode OnPostAsync est exécutée quand la page publie les données de formulaire :

C#

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_context.Movie.Add(Movie);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");
}

S’il existe des erreurs liées au modèle, le formulaire est réaffiché, ainsi que toutes les
données de formulaire publiées. La plupart des erreurs de modèle peuvent être
interceptées côté client avant la publication du formulaire. Voici un exemple d’erreur de
modèle : la publication pour le champ de date d’une valeur qui ne peut pas être
convertie en date. La validation côté client et la validation du modèle sont présentées
plus loin dans le tutoriel.
S’il n’y a pas d’erreurs de modèle :

Les données sont enregistrées.


Le navigateur est redirigé vers la page Index.

Page Razor Create


Examinez le fichier de page Razor Pages/Movies/Create.cshtml :

CSHTML

@page
@model RazorPagesMovie.Pages.Movies.CreateModel

@{
ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label">
</label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="text-
danger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger">
</span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary"
/>
</div>
</form>
</div>
</div>

<div>
<a asp-page="Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Visual Studio

Visual Studio affiche la balise suivante dans une police différenciée en gras utilisée
pour les Tag Helpers :

<form method="post">

<div asp-validation-summary="ModelOnly" class="text-danger"></div>


<label asp-for="Movie.Title" class="control-label"></label>

<input asp-for="Movie.Title" class="form-control" />

<span asp-validation-for="Movie.Title" class="text-danger"></span>


L’élément <form method="post"> est un Tag Helper de formulaire. Le Tag Helper de
formulaire inclut automatiquement un jeton de protection contre les falsifications.

Le moteur de génération de modèles automatique crée le balisage Razor pour chaque


champ du modèle, sauf l’ID, de la manière suivante :

CSHTML

<div asp-validation-summary="ModelOnly" class="text-danger"></div>


<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>

Les Tag Helpers de validation ( <div asp-validation-summary et <span asp-validation-


for ) affichent des erreurs de validation. La validation est traitée de manière plus

détaillée plus loin dans cette série.

Le Tag Helper d’étiquette ( <label asp-for="Movie.Title" class="control-label">


</label> ) génère la légende de l’étiquette et l’attribut [for] pour la propriété Title .
Le Tag Helper d’entrée ( <input asp-for="Movie.Title" class="form-control"> ) utilise les
attributs DataAnnotations et produit les attributs HTML nécessaires à la validation
jQuery côté client.

Pour plus d’informations sur les Tag Helpers, comme <form method="post"> , consultez
Tag Helpers dans ASP.NET Core.

Étapes suivantes
Précédent : Ajouter un modèle Suivant : Utiliser une base de données

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 4 de la série de tutoriels sur Pages
Razor
Article • 30/11/2023

Par Joe Audette

L’objet RazorPagesMovieContext gère la tâche de connexion à la base de données et de


mappage d’objets Movie à des enregistrements de la base de données. Le contexte de
base de données est inscrit auprès du conteneur d’injection de dépendances dans
Program.cs :

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.


builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));

var app = builder.Build();

Le système de configuration d’ASP.NET Core lit la clé ConnectionString . Pour un


développement local, la configuration obtient la chaîne de connexion à partir du fichier
appsettings.json .

Visual Studio

La chaîne de connexion générée est similaire à la valeur JSON suivante :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"RazorPagesMovieContext": "Server=
(localdb)\\mssqllocaldb;Database=RazorPagesMovieContext-
bc;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}

Quand l’application est déployée sur un serveur de test ou de production, une variable
d’environnement peut être utilisée pour définir la chaîne de connexion à un serveur de
base de données de test ou de production. Pour plus d’informations, consultez
Configuration.

Visual Studio

Base de données locale SQL Server Express


LocalDB est une version allégée du moteur de base de données SQL Server Express,
qui est ciblée pour le développement de programmes. LocalDB démarre à la
demande et s’exécute en mode utilisateur, ce qui n’implique aucune configuration
complexe. Par défaut, la base de données LocalDB crée des fichiers *.mdf dans le
répertoire C:\Users\<user>\ .

1. Dans le menu Affichage, ouvrez l’Explorateur d’objets SQL Server (SSOX).


2. Faites un clic droit sur la table Movie et sélectionnez Concepteur de vues :
Notez l’icône de clé en regard de ID . Par défaut, EF crée une propriété
nommée ID pour la clé primaire.

3. Faites un clic droit sur la table Movie et sélectionnez Afficher les données :

Amorcer la base de données


Créez une classe nommée SeedData dans le dossier Modèles avec le code suivant :

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;

namespace RazorPagesMovie.Models;

public static class SeedData


{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new RazorPagesMovieContext(
serviceProvider.GetRequiredService<
DbContextOptions<RazorPagesMovieContext>>()))
{
if (context == null || context.Movie == null)
{
throw new ArgumentNullException("Null
RazorPagesMovieContext");
}

// Look for any movies.


if (context.Movie.Any())
{
return; // DB has been seeded
}

context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M
},

new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},

new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},

new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}

Si la base de données contient des films, l’initialiseur de valeur initiale retourne une
valeur et aucun film n’est ajouté.

C#

if (context.Movie.Any())
{
return;
}

Ajouter l’initialiseur de valeur initiale


Mettez à jour Program.cs avec le code en surbrillance suivant :

Visual Studio

C#

using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
using RazorPagesMovie.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));

var app = builder.Build();

using (var scope = app.Services.CreateScope())


{
var services = scope.ServiceProvider;

SeedData.Initialize(services);
}

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Dans le code précédent, Program.cs a été modifié pour effectuer les opérations
suivantes :

Obtenir une instance de contexte de base de données à partir du conteneur


d’injection de dépendance.
Appelez la méthode seedData.Initialize en lui transmettant le contexte de base
de données de l’instance.
Supprimer le contexte une fois la méthode seed terminée. L’instruction using
garantit que le contexte est supprimé.

L’exception suivante se produit lorsque Update-Database n’a pas été exécuté :

SqlException: Cannot open database "RazorPagesMovieContext-" requested by the

login. The login failed. Login failed for user 'user name'.

Tester l’application
Supprimez tous les enregistrements dans la base de données pour que la méthode seed
s’exécute. Arrêtez et démarrez l’application pour amorcer la base de données. Si la base
de données n’est pas amorcée, placez un point d’arrêt et if (context.Movie.Any())
parcourez le code.

L’application affiche les données de départ :


Étapes suivantes
Précédent : Pages Razor générées automatiquement

Suivant : Mettre à jour les pages

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 5, Mettre à jour les pages
générées dans une application ASP.NET
Core
Article • 30/11/2023

L’application de gestion des films générée est un bon début, mais la présentation n’est
pas idéale. ReleaseDate doit être écrit en deux mots, Release Date.

Mettre le modèle à niveau


Mettez à jour Models/Movie.cs avec le code mis en évidence suivant :

C#

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;

[Column(TypeName = "decimal(18, 2)")]


public decimal Price { get; set; }
}

Dans le code précédent :

L’annotation de données [Column(TypeName = "decimal(18, 2)")] permet à Entity


Framework Core de mapper correctement Price en devise dans la base de
données. Pour plus d’informations, consultez Types de données.
L’attribut [Display] spécifie le nom complet d’un champ. Dans le code précédent,
Release Date au lieu de ReleaseDate .

l’attribut [DataType] spécifie le type de données ( Date ). Les informations de temps


stockées dans le champ ne s’affichent pas.

DataAnnotations est abordé dans le tutoriel suivant.

Accédez à Pages/Movies, puis placez le curseur sur un lien Modifier pour afficher l’URL
cible.
Les liens Edit, Details, et Delete sont générés par le Tag Helper d’ancre dans le fichier
Pages/Movies/Index.cshtml .

CSHTML

@foreach (var item in Model.Movie) {


<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a>
|
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Les Tag Helpers permettent au code côté serveur de participer à la création et au rendu
des éléments HTML dans les fichiers Razor.

Dans le code précédent, le Tag Helper d’ancre génère dynamiquement la valeur


d’attribut HTML href dans la page Razor (l’itinéraire est relatif), asp-page et l’ID de
l’itinéraire ( asp-route-id ). Pour plus d’informations, consultez Génération d’URL pour
Pages.

Utilisez Afficher la Source dans un navigateur pour examiner le balisage généré. Une
partie du code HTML généré est affichée ci-dessous :

HTML

<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
Les liens générés dynamiquement transmettent l’ID de film avec une chaîne de
requête . Par exemple, ?id=1 dans https://localhost:5001/Movies/Details?id=1 .

Ajouter un modèle d’itinéraire


Mettez à jour les Pages Razor Edit, Details et Delete pour utiliser le modèle de route
{id:int} . Remplacez la directive de chacune de ces pages ( @page "{id:int}" ) par

@page . Exécuter l’application, puis affichez le code source.

Le code HTML généré ajoute l’ID à la partie de chemin de l’URL :

HTML

<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>

Une requête à la page avec le modèle d’itinéraire {id:int} qui n’inclut pas l’entier
retourne une erreur HTTP 404 (introuvable). Par exemple,
https://localhost:5001/Movies/Details retourne une erreur 404. Pour que l’ID soit

facultatif, ajoutez ? à la contrainte d’itinéraire :

CSHTML

@page "{id:int?}"

Testez le comportement de @page "{id:int?}" :

1. Définissez la directive de page dans Pages/Movies/Details.cshtml sur @page "


{id:int?}" .

2. Définissez un point d’arrêt dans public async Task<IActionResult>


OnGetAsync(int? id) , dans Pages/Movies/Details.cshtml.cs .

3. Accédez à https://localhost:5001/Movies/Details/ .

Avec la directive @page "{id:int}" , le point d’arrêt n’est jamais atteint. Le moteur de
routage retourne l’erreur HTTP 404. Avec @page "{id:int?}" , la méthode OnGetAsync
retourne NotFound (HTTP 404) :

C#
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}

Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);

if (Movie == null)
{
return NotFound();
}
return Page();
}

Passer en revue la gestion des exceptions d’accès


concurrentiel
Passez en revue la méthode OnPostAsync dans le fichier Pages/Movies/Edit.cshtml.cs :

C#

public async Task<IActionResult> OnPostAsync()


{
if (!ModelState.IsValid)
{
return Page();
}

_context.Attach(Movie).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}

return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.Id == id);
}

Le code précédent détecte les exceptions d’accès concurrentiel quand un client


supprime le film et que l’autre client envoie les modifications apportées au film.

Pour tester le bloc catch :

1. Définissez un point d’arrêt sur catch (DbUpdateConcurrencyException) .


2. Sélectionnez Edit (Modifier) pour un film, apportez des modifications, mais ne
cliquez pas sur Save (Enregistrer).
3. Dans une autre fenêtre de navigateur, sélectionnez le lien Delete du même film,
puis supprimez le film.
4. Dans la fenêtre de navigateur précédente, postez les modifications apportées au
film.

Dans le code destiné à la production, il est nécessaire de détecter les conflits d’accès
concurrentiel. Pour plus d’informations, consultez Gérer les conflits d’accès
concurrentiel.

Validation de la publication et de la liaison


Examinez le fichier Pages/Movies/Edit.cshtml.cs :

C#

public class EditModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

[BindProperty]
public Movie Movie { get; set; } = default!;

public async Task<IActionResult> OnGetAsync(int? id)


{
if (id == null || _context.Movie == null)
{
return NotFound();
}
var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id ==
id);
if (movie == null)
{
return NotFound();
}
Movie = movie;
return Page();
}

// To protect from overposting attacks, enable the specific properties


you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}

_context.Attach(Movie).State = EntityState.Modified;

try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}

return RedirectToPage("./Index");
}

private bool MovieExists(int id)


{
return _context.Movie.Any(e => e.Id == id);
}

Quand une requête HTTP GET est effectuée sur la page Movies/Edit, par exemple,
https://localhost:5001/Movies/Edit/3 :

La méthode OnGetAsync extrait le film de la base de données et retourne la


méthode Page .
La méthode Page restitue la page Pages/Movies/Edit.cshtml Razor. Le fichier
Pages/Movies/Edit.cshtml contient la directive de modèle @model

RazorPagesMovie.Pages.Movies.EditModel , ce qui rend le modèle Movie disponible

sur la page.
Le formulaire d’édition s’affiche avec les valeurs relatives au film.

Quand la page Movies/Edit est postée :

Les valeurs de formulaire affichées dans la page sont liées à la propriété Movie .
L’attribut [BindProperty] active la liaison de données.

C#

[BindProperty]
public Movie Movie { get; set; }

S’il y a des erreurs dans l’état du modèle, par exemple, si ReleaseDate ne peut pas
être converti en date, le formulaire est à nouveau affiché avec les valeurs soumises.

S’il n’y a aucune erreur de modèle, le film est enregistré.

Les méthodes HTTP GET dans les pages Razor Index, Create et Delete suivent un modèle
similaire. La méthode HTTP POST OnPostAsync dans la page Razor Create suit un modèle
semblable à la méthode OnPostAsync dans la page Razor Edit.

Étapes suivantes
Précédent : Utilisation d’une base de données

Suivant : Ajouter une fonction de recherche

6 Collaborer avec nous sur


GitHub
La source de ce contenu se
trouve sur GitHub, où vous
pouvez également créer et
examiner les problèmes et les
demandes de tirage. Pour plus
d’informations, consultez notre
guide du contributeur.
ASP.NET Core feedback
ASP.NET Core is an open source
project. Select a link to provide
feedback:

 Ouvrir un problème de
documentation

 Indiquer des commentaires sur


le produit
Partie 6, ajouter la recherche à ASP.NET
Core Pages Razor
Article • 30/11/2023

Par Rick Anderson

Dans les sections suivantes, la recherche de films par genre ou par nom est ajoutée.

Ajoutez le code en surbrillance suivant à Pages/Movies/Index.cshtml.cs :

C#

public class IndexModel : PageModel


{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;

public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)


{
_context = context;
}

public IList<Movie> Movie { get;set; } = default!;

[BindProperty(SupportsGet = true)]
public string? SearchString { get; set; }

public SelectList? Genres { get; set; }

[BindProperty(SupportsGet = true)]
public string? MovieGenre { get; set; }

Dans le code précédent :

SearchString : Contient le texte que les utilisateurs entrent dans la zone de texte

de recherche. SearchString a l’attribut [BindProperty]. [BindProperty] lie les


valeurs de formulaire et les chaînes de requête avec le même nom que la
propriété. [BindProperty(SupportsGet = true)] est obligatoire pour la liaison sur
les requêtes HTTP GET.
Genres : Contient la liste des genres. Genres permet à l’utilisateur de sélectionner
un genre dans la liste. SelectList nécessite using
Microsoft.AspNetCore.Mvc.Rendering;
MovieGenre : Contient le genre spécifique sélectionné par l’utilisateur. Par exemple,

« Western ».
Genres et MovieGenre sont utilisés plus loin dans ce tutoriel.

2 Avertissement

Pour des raisons de sécurité, vous devez choisir de lier les données de requête GET
aux propriétés du modèle de page. Vérifiez l’entrée utilisateur avant de la mapper à
des propriétés. Le choix de la liaison GET convient pour les scénarios qui s’appuient
sur des valeurs de routage ou de chaîne de requête.

Pour lier une propriété sur des requêtes GET , définissez la propriété SupportsGet de
l’attribut [BindProperty] sur true :

C#

[BindProperty(SupportsGet = true)]

Pour plus d’informations, consultez ASP.NET Core Community Standup: Bind on


GET discussion (YouTube) .

Mettez à jour la méthode OnGetAsync de la page Index avec le code suivant :

C#

public async Task OnGetAsync()


{
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(SearchString))
{
movies = movies.Where(s => s.Title.Contains(SearchString));
}

Movie = await movies.ToListAsync();


}

La première ligne de la méthode OnGetAsync crée une requête LINQ pour sélectionner
les films :

C#

// using System.Linq;
var movies = from m in _context.Movie
select m;
La requête est seulement définie à ce stade, elle n’a pas été exécutée sur la base de
données.

Si la propriété SearchString n’est pas null ou vide, la requête sur les films est modifiée
de façon à filtrer sur la chaîne de recherche :

C#

if (!string.IsNullOrEmpty(SearchString))
{
movies = movies.Where(s => s.Title.Contains(SearchString));
}

Le code s => s.Title.Contains() est une expression lambda. Les expressions lambda
sont utilisées dans les requêtes LINQ basées sur une méthode en tant qu’arguments
pour les méthodes d’opérateur de requête standard, comme la méthode Where ou
Contains . Les requêtes LINQ ne sont pas exécutées quand elles sont définies ou quand

elles sont modifiées en appelant une méthode, comme Where , Contains ou OrderBy . Au
lieu de cela, l’exécution de la requête est différée. L’évaluation d’une expression est
retardée jusqu’à ce que sa valeur réalisée fasse l’objet d’une itération réelle ou que la
méthode ToListAsync soit appelée. Pour plus d’informations, consultez Exécution de
requête.

7 Notes

La méthode Contains est exécutée sur la base de données, et non pas dans le code
C#. Le respect de la casse pour la requête dépend de la base de données et du
classement. Sur SQL Server, Contains est mappée à SQL LIKE, qui ne respecte pas
la casse. SQLite avec le classement par défaut est un mélange de respect de la
casse et de respect de la casse IN, en fonction de la requête. Pour plus
d’informations sur la création de requêtes SQLite sans respect de la casse, consultez
les rubriques suivantes :

Ce problème GitHub
Ce problème GitHub
Classements et respect de la casse

Accédez à la page Films, puis ajoutez une chaîne de requête telle que ?
searchString=Ghost à l’URL. Par exemple, https://localhost:5001/Movies?

searchString=Ghost Les films filtrés sont affichés.


Si le modèle de routage suivant est ajouté à la page d’index, la chaîne de recherche peut
être passée comme un segment d’URL. Par exemple,
https://localhost:5001/Movies/Ghost

CSHTML

@page "{searchString?}"

La contrainte d’itinéraire précédente permet de rechercher le titre comme données


d’itinéraire (un segment d’URL) et non comme valeur de chaîne de requête. Le ? dans "
{searchString?}" signifie qu’il s’agit d’un paramètre d’itinéraire facultatif.
Le runtime ASP.NET Core utilise la liaison de modèle pour définir la valeur de la
propriété SearchString à partir de la chaîne de requête ( ?searchString=Ghost ) ou des
données de la route ( https://localhost:5001/Movies/Ghost ). La liaison de modèle ne
respecte pas la casse.

Cependant, vous ne pouvez pas attendre des utilisateurs qu’ils modifient l’URL pour
rechercher un film. Dans cette étape, une interface utilisateur est ajoutée pour filtrer les
films. Si vous avez ajouté la contrainte d’itinéraire "{searchString?}" , supprimez-la.

Ouvrez le fichier Pages/Movies/Index.cshtml , puis ajoutez la balise mise en surbrillance


dans le code suivant :

CSHTML

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-page="Create">Create New</a>
</p>

<form>
<p>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>

<table class="table">
@*Markup removed for brevity.*@

La balise HTML <form> utilise les Tag Helpers suivants :

Tag Helper Form. Quand le formulaire est envoyé, la chaîne de filtrage est envoyée
à la page Pages/Movies/Index via la chaîne de requête.
Tag Helper Input

Enregistrez les modifications apportées, puis testez le filtre.

Rechercher par genre


Mettez à jour la méthode OnGetAsync de la page Movies/Index.cshtml.cs avec le code
suivant :

C#
public async Task OnGetAsync()
{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;

var movies = from m in _context.Movie


select m;

if (!string.IsNullOrEmpty(SearchString))
{
movies = movies.Where(s => s.Title.Contains(SearchString));
}

if (!string.IsNullOrEmpty(MovieGenre))
{
movies = movies.Where(x => x.Genre == MovieGenre);
}
Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
Movie = await movies.ToListAsync();
}

Le code suivant est une requête LINQ qui récupère tous les genres dans la base de
données.

C#

// Use LINQ to get list of genres.


IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;

La liste SelectList de genres est créée en projetant des différents genres.

C#

Genres = new SelectList(await genreQuery.Distinct().ToListAsync());

Ajoutez une recherche par genre à la Page Razor


Mettez à jour Index.cshtml <form>l’élément comme mis en évidence dans le balisage
suivant :

CSHTML
@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-page="Create">Create New</a>
</p>

<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>

Testez l’application en effectuant une recherche par genre, par titre de film et selon ces
deux critères.

Étapes suivantes
Précédent : Mettre à jour les pages Suivant : Ajouter un nouveau champ

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 7, ajouter un nouveau champ à
une page Razor dans ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Dans cette section, Migrations Entity Framework Code First est utilisé pour :

Ajouter un nouveau champ au modèle.


Migrer la nouvelle modification du schéma des champs vers la base de données.

Quand vous utilisez EF Code First pour créer et suivre automatiquement une base de
données, Code First :

Ajoute une table __EFMigrationsHistory à la base de données pour déterminer si le


schéma de la base de données est synchronisé avec les classes de modèle à partir
desquelles il a été généré.
Lève une exception si les classes de modèle ne sont pas synchronisées avec la base
de données.

La vérification automatique de la synchronisation du schéma et du modèle facilite la


recherche de problèmes de code de base de données incohérents.

Ajout d’une propriété Rating au modèle Movie


1. Ouvrez le fichier Models/Movie.cs et ajoutez une propriété Rating :

C#

public class Movie


{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;

[Column(TypeName = "decimal(18, 2)")]


public decimal Price { get; set; }
public string Rating { get; set; } = string.Empty;
}
2. Modifiez Pages/Movies/Index.cshtml , puis ajoutez un champ Rating :

CSHTML

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-page="Create">Create New</a>
</p>

<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>

<table class="table">

<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Title)
</th>
<th>
@Html.DisplayNameFor(model =>
model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Movie[0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<a asp-page="./Edit" asp-route-
id="@item.Id">Edit</a> |
<a asp-page="./Details" asp-route-
id="@item.Id">Details</a> |
<a asp-page="./Delete" asp-route-
id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

3. Mettez à jour les pages suivantes avec un champ Rating :

Pages/Movies/Create.cshtml .
Pages/Movies/Delete.cshtml .
Pages/Movies/Details.cshtml .
Pages/Movies/Edit.cshtml .

L’application ne fonctionne pas tant que la base de données n’est pas mise à jour pour
inclure le nouveau champ. L’exécution de l’application sans mise à jour de la base de
données lève un SqlException :

SqlException: Invalid column name 'Rating'.

Cette exception SqlException est due au fait que la classe du modèle Film mise à jour
est différente du schéma de la table Film de la base de données. Il n’existe pas de
colonne Rating dans la table de base de données.

Plusieurs approches sont possibles pour résoudre l’erreur :

1. Laisser Entity Framework supprimer et recréer automatiquement la base de


données avec le nouveau schéma de classes du modèle. Cette approche est très
utile au début du cycle de développement. Elle permet au développeur de faire
évoluer rapidement le modèle et le schéma de base de données ensemble.
L’inconvénient est que les données existantes dans la base de données sont
perdues. N’utilisez pas cette approche sur une base de données de production ! La
suppression de la base de données lors des changements de schéma et l’utilisation
d’un initialiseur pour amorcer automatiquement la base de données avec des
données de test est souvent un moyen efficace pour développer une application.
2. Modifier explicitement le schéma de la base de données existante pour le faire
correspondre aux classes du modèle. L’avantage de cette approche est que vous
conservez les données. Apportez cette modification manuellement ou en créant un
script de modification de la base de données.
3. Utilisez les migrations Code First pour mettre à jour le schéma de base de
données.

Pour ce didacticiel, nous allons utiliser les migrations Code First.

Mettez à jour la classe SeedData pour qu’elle fournisse une valeur pour la nouvelle
colonne. Vous pouvez voir un exemple de modification ci-dessous, mais appliquez cette
modification à chaque bloc new Movie .

C#

context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M,
Rating = "R"
},

Consultez le fichier SeedData.cs complet .

Générez la solution.

Visual Studio

Ajouter une migration pour le champ d’évaluation


1. Dans le menu Outils, sélectionnez Gestionnaire de package NuGet > Console
du gestionnaire de package.
2. Dans la console du gestionnaire de package, entrez les commandes suivantes :

PowerShell

Add-Migration Rating
Update-Database

La commande Add-Migration indique au framework qu’il doit :

Comparer le modèle Movie au schéma de base de données Movie .


Créer du code pour migrer le schéma de base de données vers le nouveau
modèle.

Le nom « Rating » est arbitraire et utilisé pour nommer le fichier de migration. Il est
utile d’utiliser un nom explicite pour le fichier de migration.

La commande Update-Database indique à l’infrastructure d’appliquer les


modifications de schéma à la base de données et de conserver les données
existantes.

Supprimez tous les enregistrements de la base de données, l’initialiseur amorce la


base de données et inclut le champ Rating . La suppression peut ce faire en utilisant
les liens de suppression disponibles dans le navigateur ou à partir de Sql Server
Object Explorer (SSOX).

Une autre option consiste à supprimer la base de données et à utiliser des


migrations pour recréer la base de données. Pour supprimer la base de données
dans SSOX :

1. Sélectionnez la base de données dans SSOX.

2. Cliquez avec le bouton droit sur la base de données, puis sélectionnez


Supprimer.

3. Cochez Fermer les connexions existantes.

4. Sélectionnez OK.

5. Dans la console du gestionnaire de package, mettez à jour la base de données


:

PowerShell
Update-Database

Exécutez l’application et vérifiez que vous pouvez créer/modifier/afficher des films avec
un champ Rating . Si la base de données n’est pas amorcée, définissez un point d’arrêt
dans la méthode SeedData.Initialize .

Étapes suivantes
Précédent : Ajouter une recherche Suivant : Ajouter une validation

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 8 de la série de tutoriels sur Pages
Razor
Article • 01/02/2024

Par Rick Anderson

Dans cette section, une logique de validation est ajoutée au modèle Movie . Les règles
de validation sont appliquées chaque fois qu’un utilisateur crée ou modifie un film.

Validation
DRY (« Don't Repeat Yourself », Ne vous répétez pas) constitue un principe clé du
développement de logiciel. Les Pages Razor favorisent le développement dans lequel
une fonctionnalité est spécifiée une seule fois et sont répercutées dans toute
l’application. DRY peut aider à :

Réduire la quantité de code dans une application.


Rendre le code moins sujet aux erreurs, et plus facile à tester et à maintenir.

La prise en charge de la validation fournie par les Pages Razor et Entity Framework est
un bon exemple du principe DRY :

Les règles de validation sont spécifiées de manière déclarative au même endroit,


dans la classe de modèle.
Les règles sont appliquées partout dans l’application.

Ajouter des règles de validation au modèle de


film
L’espace de nom System.ComponentModel.DataAnnotations fournit :

Ensemble d’attributs de validation intégrés appliqués de manière déclarative à une


classe ou à une propriété.
Attributs de mise en forme comme [DataType] qui aident à effectuer la mise en
forme et ne fournissent aucune validation.

Mettez à jour la classe Movie pour tirer parti des attributs de validation intégrés
[Required] , [StringLength] , [RegularExpression] et [Range] .

C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }

[StringLength(60, MinimumLength = 3)]


[Required]
public string Title { get; set; } = string.Empty;

[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; } = string.Empty;

[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; } = string.Empty;
}

Les attributs de validation spécifient le comportement à appliquer sur les propriétés du


modèle auxquelles ils sont appliqués :

Les attributs [Required] et [MinimumLength] indiquent qu’une propriété doit avoir


une valeur. Rien n’empêche un utilisateur d’entrer des espaces blancs pour
satisfaire cette validation.

L’attribut [RegularExpression] sert à limiter les caractères pouvant être entrés.


Dans le code précédent, Genre :
Doit utiliser seulement des lettres.
La première lettre doit être une majuscule. Les espaces sont autorisés, tandis
que les nombres et les caractères spéciaux ne le sont pas.

RegularExpression Rating :

Nécessite que le premier caractère soit une lettre majuscule.


Autorise les caractères spéciaux et les chiffres aux emplacements qui suivent.
« PG-13 » est valide pour une évaluation, mais échoue pour un Genre .

L’attribut [Range] limite une valeur à une plage spécifiée.

L’attribut [StringLength] peut définir la longueur maximale d’une propriété de


chaîne, et éventuellement sa longueur minimale.

Les types valeur (tels que decimal , int , float , DateTime ) sont obligatoires par
nature et n’ont pas besoin de l’attribut [Required] .

Les règles de validation précédentes sont utilisées pour la démonstration, elles ne sont
pas optimales pour un système de production. Par exemple, le précédent empêche
d’entrer un film avec seulement deux caractères et n’autorise pas les caractères spéciaux
dans Genre .

L’application automatique des règles de validation par ASP.NET Core permet de :

Rendre l’application plus robuste.


Réduire les risques d’enregistrement de données non valides dans la base de
données.

Interface utilisateur d’erreur de validation dans les Pages


Razor
Exécutez l’application, puis accédez à Pages/Movies.

Sélectionnez le lien Créer nouveau. Complétez le formulaire avec des valeurs non
valides. Quand la validation jQuery côté client détecte l’erreur, elle affiche un message
d’erreur.
7 Notes

Vous ne pourrez peut-être pas entrer de virgules décimales dans les champs
décimaux. Pour prendre en charge la validation jQuery pour les paramètres
régionaux autres que l’anglais qui utilisent une virgule (« , ») comme décimale et
des formats de date autres que l’anglais des États-Unis, vous devez effectuer des
étapes pour localiser votre application. Consultez ce commentaire GitHub 4076
pour savoir comment ajouter une virgule décimale.

Notez que le formulaire a affiché automatiquement un message d’erreur de validation


dans chaque champ contenant une valeur non valide. Les erreurs sont appliquées à la
fois côté client à l’aide de JavaScript et de jQuery, et côté serveur quand JavaScript est
désactivé pour un utilisateur.
L’un des principaux avantages est qu’aucun changement de code n’a été nécessaire
dans les pages Créer ou Modifier. Une fois les annotations de données appliquées au
modèle, l’interface utilisateur de validation a été activée. Les Pages Movie créées dans ce
tutoriel ont prélevé les règles de validation à l’aide des attributs de validation définis sur
les propriétés de la classe de modèle Razor. Testez la validation à l’aide de la page de
modification. La même validation est appliquée.

Les données de formulaire ne sont pas publiées sur le serveur tant qu’il y a des erreurs
de validation côté client. Vérifiez que les données du formulaire ne sont pas publiées à
l’aide d’une ou de plusieurs des approches suivantes :

Placez un point d’arrêt dans la méthode OnPostAsync . Envoyez le formulaire en


sélectionnant Créer ou Enregistrer. Le point d’arrêt n’est jamais atteint.
Utilisez l’outil Fiddler .
Utilisez les outils de développement du navigateur pour surveiller le trafic réseau.

Validation côté serveur


Quand JavaScript est désactivé dans le navigateur, l’envoi du formulaire avec des erreurs
poste les données sur le serveur.

Facultatif : Testez la validation côté serveur :

1. Désactivez JavaScript dans le navigateur. JavaScript peut être désactivé à l’aide des
outils de développement du navigateur. Si JavaScript ne peut pas être désactivé
dans le navigateur, essayez un autre navigateur.

2. Définissez un point d’arrêt dans la méthode OnPostAsync de la page Créer ou


Modifier.

3. Envoyez un formulaire avec des données non valides.

4. Vérifiez que l’état de modèle n’est pas valide :

C#

if (!ModelState.IsValid)
{
return Page();
}

Vous pouvez également désactiver la validation côté client sur le serveur.


Le code suivant affiche la partie de la page Create.cshtml générée automatiquement
plus tôt dans le tutoriel. Elle est utilisée par les pages Créer et Modifier pour :

Affichez le formulaire initial.


Réafficher le formulaire en cas d’erreur.

CSHTML

<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>

Le Tag Helper d’entrée utilise les attributs DataAnnotations et produit les attributs HTML
nécessaires à la validation jQuery côté client. Le Tag Helper Validation affiche les erreurs
de validation. Pour plus d’informations, consultez Validation.

Les pages Créer et Modifier ne contiennent pas de règles de validation. Les règles de
validation et les chaînes d’erreur sont spécifiées uniquement dans la classe Movie . Ces
règles de validation sont automatiquement appliquées aux pages Razorqui modifient le
modèle Movie .

Quand la logique de validation doit être modifiée, cela s’effectue uniquement dans le
modèle. La validation est appliquée de manière cohérente dans l’ensemble de
l’application. La logique de validation est définie dans un emplacement unique. La
validation dans un emplacement unique permet de maintenir votre code clair, et d’en
faciliter la maintenance et la mise à jour.

Utiliser des attributs DataType


Examiner la classe Movie . L’espace de noms System.ComponentModel.DataAnnotations
fournit des attributs de mise en forme en plus de l’ensemble intégré d’attributs de
validation. L’attribut [DataType] est appliqué aux propriétés ReleaseDate et Price .

C#

[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

Les attributs [DataType] fournissent :

Conseils pour le moteur d’affichage pour mettre en forme les données.


Fournit des attributs tels que <a> pour l’URL et <a
href="mailto:EmailAddress.com"> pour l’e-mail.

Utilisez l’attribut [RegularExpression] pour valider le format des données. L’attribut


[DataType] sert à spécifier un type de données qui est plus spécifique que le type

intrinsèque de la base de données. Les attributs [DataType] ne sont pas des attributs de
validation. Dans l’échantillon d’application, seule la date est affichée, sans l’heure.

L’énumération DataType fournit de nombreux types de données, tels que Date , Time ,
PhoneNumber , Currency , EmailAddress , et bien plus encore.

Les attributs [DataType] :

Peut permettre à l’application de fournir automatiquement des fonctionnalités


propres au type. Par exemple, il est possible de créer un lien mailto: pour
DataType.EmailAddress .

Peuvent fournir un sélecteur de date DataType.Date dans les navigateurs qui


prennent en charge HTML5.
Émettent HTML 5 data- , prononcé « tiret de données », attributs utilisés par les
navigateurs HTML 5.
Ne fournissent aucune validation.

DataType.Date ne spécifie pas le format de la date qui s’affiche. Par défaut, le champ de

données est affiché conformément aux formats par défaut basés sur le CultureInfo du
serveur.

L’annotation de données [Column(TypeName = "decimal(18, 2)")] est nécessaire pour


qu’Entity Framework Core puisse correctement mapper Price en devise dans la base de
données. Pour plus d’informations, consultez Types de données.

L’attribut [DisplayFormat] est utilisé pour spécifier explicitement le format de date :

C#

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode =


true)]
public DateTime ReleaseDate { get; set; }
Le paramètre ApplyFormatInEditMode indique que la mise en forme sera appliquée
quand la valeur est affichée à des fins de modification. Ce comportement n’est peut-être
pas souhaité pour certains champs. Par exemple, dans les valeurs monétaires, le symbole
monétaire n’est généralement pas voulu dans l’interface utilisateur de modification.

L’attribut [DisplayFormat] peut être utilisé seul, mais il est généralement préférable
d’utiliser l’attribut [DataType] . L’attribut [DataType] transmet la sémantique des
données, plutôt que la manière de l’afficher à l’écran. L’attribut [DataType] offre les
avantages suivants qui ne sont pas disponibles avec [DisplayFormat] :

Le navigateur peut activer des fonctionnalités HTML5, par exemple pour afficher
un contrôle de calendrier, le symbole monétaire correspondant aux paramètres
régionaux, des liens de messagerie, etc.
Par défaut, le navigateur affiche les données à l’aide du format correspondant aux
paramètres régionaux.
L’attribut [DataType] peut permettre à l’infrastructure ASP.NET Core de choisir le
modèle de champ approprié pour afficher les données. S’il est utilisé seul,
DisplayFormat utilise le modèle de chaîne.

Remarque : La validation jQuery ne fonctionne pas avec l’attribut [Range] et DateTime .


Par exemple, le code suivant affiche toujours une erreur de validation côté client, même
quand la date se trouve dans la plage spécifiée :

C#

[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]

Il recommandé d’éviter de compiler des dates en dur dans des modèles. Par conséquent,
l’utilisation de l’attribut [Range] et de DateTime est déconseillée. Utilisez Configuration
pour les plages de dates et d’autres valeurs qui sont sujettes à des modifications
fréquentes au lieu de les spécifier dans le code.

Le code suivant illustre la combinaison d’attributs sur une seule ligne :

C#

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RazorPagesMovie.Models;

public class Movie


{
public int Id { get; set; }
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; } = string.Empty;

[Display(Name = "Release Date"), DataType(DataType.Date)]


public DateTime ReleaseDate { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$"), Required, StringLength(30)]


public string Genre { get; set; } = string.Empty;

[Range(1, 100), DataType(DataType.Currency)]


[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$"), StringLength(5)]
public string Rating { get; set; } = string.Empty;
}

Prise en main des Pages Razor et EF Core affiche les opérations avancées EF Core avec
Pages Razor.

Appliquer des migrations


L’application de DataAnnotations à la classe modifie le schéma. Par exemple,
DataAnnotations appliqué au champ Title :

C#

[StringLength(60, MinimumLength = 3)]


[Required]
public string Title { get; set; } = string.Empty;

limite les caractères à 60 ;


n’autorise pas de valeur null .

La table Movie a actuellement le schéma suivant :

SQL

CREATE TABLE [dbo].[Movie] (


[ID] INT IDENTITY (1, 1) NOT NULL,
[Title] NVARCHAR (MAX) NULL,
[ReleaseDate] DATETIME2 (7) NOT NULL,
[Genre] NVARCHAR (MAX) NULL,
[Price] DECIMAL (18, 2) NOT NULL,
[Rating] NVARCHAR (MAX) NULL,
CONSTRAINT [PK_Movie] PRIMARY KEY CLUSTERED ([ID] ASC)
);
Les modifications précédentes du schéma n’entraînent pas la levée d’une exception par
EF. Cependant, créez une migration pour que le schéma soit cohérent avec le modèle.

Visual Studio

Dans le menu Outils, sélectionnez Gestionnaire de package NuGet > Console du


gestionnaire de package. Dans la console du gestionnaire de package, entrez les
commandes suivantes :

PowerShell

Add-Migration New_DataAnnotations
Update-Database

Update-Database exécute la méthode Up de la classe New_DataAnnotations .

Examinez la méthode Up :

C#

public partial class New_DataAnnotations : Migration


{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Title",
table: "Movie",
type: "nvarchar(60)",
maxLength: 60,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");

migrationBuilder.AlterColumn<string>(
name: "Rating",
table: "Movie",
type: "nvarchar(5)",
maxLength: 5,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");

migrationBuilder.AlterColumn<string>(
name: "Genre",
table: "Movie",
type: "nvarchar(30)",
maxLength: 30,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");
}

La table Movie mise à jour a le schéma suivant :

SQL

CREATE TABLE [dbo].[Movie] (


[ID] INT IDENTITY (1, 1) NOT NULL,
[Title] NVARCHAR (60) NOT NULL,
[ReleaseDate] DATETIME2 (7) NOT NULL,
[Genre] NVARCHAR (30) NOT NULL,
[Price] DECIMAL (18, 2) NOT NULL,
[Rating] NVARCHAR (5) NOT NULL,
CONSTRAINT [PK_Movie] PRIMARY KEY CLUSTERED ([ID] ASC)
);

Publication dans Azure


Pour plus d’informations sur le déploiement sur Azure, consultez Tutoriel : Créer une
application ASP.NET Core dans Azure avec SQL Database.

Nous vous remercions d’avoir effectué cette introduction aux pages Razor. Pour
compléter ce tutoriel, vous pouvez consulter Bien démarrer avec les Pages Razor et EF
Core.

Ressources supplémentaires
Tag Helpers dans les formulaires dans ASP.NET Core
Globalisation et localisation dans ASP.NET Core
Tag Helpers dans ASP.NET Core
Créer des Tag Helpers dans ASP.NET Core

Étapes suivantes
Précédent : Ajouter un nouveau champ

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Méthodes de filtre pour les pages
Razordans ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Les filtres de page RazorIPageFilter et IAsyncPageFilter autorisent les pages Razor à


exécuter le code avant et après l’exécution d’un gestionnaire de pages Razor . Les filtres
de page Razor sont similaires aux filtres d’action MVC ASP.NET Core, à la différence près
qu’ils ne peuvent pas être appliqués aux méthodes de gestionnaire de pages
individuelles.

Filtres de page Razor :

Exécutent le code après la sélection d’une méthode de gestionnaire, mais avant la


liaison de données.
Exécutent le code avant l’exécution de la méthode de gestionnaire, une fois la
liaison de données terminée.
Exécutent le code après l’exécution de la méthode de gestionnaire.
Peuvent être implémentés dans une page ou globalement.
Ne peuvent pas être appliqués à des méthodes de gestionnaire de page
spécifiques.
peuvent avoir des dépendances de constructeur remplies par l’injection de
dépendances (DI). Pour plus d’informations, consultez ServiceFilterAttribute et
TypeFilterAttribute.

Alors que les constructeurs de page et les intergiciels permettent d’exécuter le code
personnalisé avant l’exécution d’une méthode de gestionnaire, seuls les filtres de page
Razor permettent d’accéder à HttpContext et à la page. L’intergiciel peut accéder au
HttpContext , mais pas au « contexte de page ». Les filtres ont un paramètre dérivé

FilterContext qui permet d’accéder à HttpContext . Voici un exemple de filtre de page :


Implémenter un attribut de filtre qui ajoute un en-tête à la réponse, ce qu’il est
impossible de faire avec des constructeurs ou des middlewares. L’accès au contexte de
page, qui inclut l’accès aux instances de la page et à son modèle, n’est disponible que
lors de l’exécution de filtres, de gestionnaires ou du corps d’une page Razor.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Les filtres de page Razor fournissent les méthodes suivantes que vous pouvez appliquer
globalement ou au niveau de la page :
Méthodes synchrones :
OnPageHandlerSelected : appelée après la sélection d’une méthode de
gestionnaire, mais avant la liaison de données.
OnPageHandlerExecuting : appelée avant l’exécution de la méthode de
gestionnaire, une fois la liaison de données terminée.
OnPageHandlerExecuted : appelée avant l’exécution de la méthode de
gestionnaire, avant le résultat de l’action.

Méthodes asynchrones :
OnPageHandlerSelectionAsync : appelée de manière asynchrone après la
sélection d’une méthode de gestionnaire, mais avant la liaison de données.
OnPageHandlerExecutionAsync : appelée de manière asynchrone avant l’appel
de la méthode de gestionnaire, une fois la liaison de modèle terminée.

Implémentez la version synchrone ou bien la version asynchrone d’une interface de


filtre, mais pas les deux. Le framework vérifie d’abord si le filtre implémente l’interface
asynchrone et, le cas échéant, il appelle cette interface. Dans le cas contraire, il appelle la
ou les méthodes de l’interface synchrone. Si les deux interfaces sont implémentées,
seules les méthodes asynchrones sont appelées. La même règle s’applique aux
substitutions dans les pages : implémentez la version synchrone ou asynchrone de la
substitution, mais pas les deux.

Implémenter des filtres de page Razor


globalement
Le code suivant implémente IAsyncPageFilter :

C#

public class SampleAsyncPageFilter : IAsyncPageFilter


{
private readonly IConfiguration _config;

public SampleAsyncPageFilter(IConfiguration config)


{
_config = config;
}

public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext


context)
{
var key = _config["UserAgentID"];
context.HttpContext.Request.Headers.TryGetValue("user-agent",
out StringValues
value);
ProcessUserAgent.Write(context.ActionDescriptor.DisplayName,

"SampleAsyncPageFilter.OnPageHandlerSelectionAsync",
value, key.ToString());

return Task.CompletedTask;
}

public async Task


OnPageHandlerExecutionAsync(PageHandlerExecutingContext context,

PageHandlerExecutionDelegate next)
{
// Do post work.
await next.Invoke();
}
}

Dans le code précédent, ProcessUserAgent.Write est le code fourni par l’utilisateur qui
fonctionne avec la chaîne de l’agent utilisateur.

Le code suivant active le filtre SampleAsyncPageFilter dans la classe Startup :

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add(new SampleAsyncPageFilter(Configuration));
});
}

Le code suivant appelle AddFolderApplicationModelConvention pour appliquer le


SampleAsyncPageFilter uniquement aux pages dans /Movies :

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddRazorPages(options =>
{
options.Conventions.AddFolderApplicationModelConvention(
"/Movies",
model => model.Filters.Add(new
SampleAsyncPageFilter(Configuration)));
});
}
Le code suivant implémente le filtre IPageFilter synchrone :

C#

public class SamplePageFilter : IPageFilter


{
private readonly IConfiguration _config;

public SamplePageFilter(IConfiguration config)


{
_config = config;
}

public void OnPageHandlerSelected(PageHandlerSelectedContext context)


{
var key = _config["UserAgentID"];
context.HttpContext.Request.Headers.TryGetValue("user-agent", out
StringValues value);
ProcessUserAgent.Write(context.ActionDescriptor.DisplayName,
"SamplePageFilter.OnPageHandlerSelected",
value, key.ToString());
}

public void OnPageHandlerExecuting(PageHandlerExecutingContext context)


{
Debug.WriteLine("Global sync OnPageHandlerExecuting called.");
}

public void OnPageHandlerExecuted(PageHandlerExecutedContext context)


{
Debug.WriteLine("Global sync OnPageHandlerExecuted called.");
}
}

Le code suivant active le filtre SamplePageFilter :

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.Filters.Add(new SamplePageFilter(Configuration));
});
}

Implémenter des filtres de page Razor en


remplaçant les méthodes de filtre
Le code suivant remplace les filtres de page Razorasynchrones :

C#

public class IndexModel : PageModel


{
private readonly IConfiguration _config;

public IndexModel(IConfiguration config)


{
_config = config;
}

public override Task


OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
Debug.WriteLine("/IndexModel OnPageHandlerSelectionAsync");
return Task.CompletedTask;
}

public async override Task


OnPageHandlerExecutionAsync(PageHandlerExecutingContext context,

PageHandlerExecutionDelegate next)
{
var key = _config["UserAgentID"];
context.HttpContext.Request.Headers.TryGetValue("user-agent", out
StringValues value);
ProcessUserAgent.Write(context.ActionDescriptor.DisplayName,
"/IndexModel-OnPageHandlerExecutionAsync",
value, key.ToString());

await next.Invoke();
}
}

Implémenter un attribut de filtre


Le filtre intégré à base d’attribut OnResultExecutionAsync peut être sous-classé. Le filtre
suivant ajoute un en-tête à la réponse :

C#

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;

namespace PageFilter.Filters
{
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string _value;

public AddHeaderAttribute (string name, string value)


{
_name = name;
_value = value;
}

public override void OnResultExecuting(ResultExecutingContext


context)
{
context.HttpContext.Response.Headers.Add(_name, new string[] {
_value });
}
}
}

Le code suivant applique l’attribut AddHeader :

C#

using Microsoft.AspNetCore.Mvc.RazorPages;
using PageFilter.Filters;

namespace PageFilter.Movies
{
[AddHeader("Author", "Rick")]
public class TestModel : PageModel
{
public void OnGet()
{

}
}
}

Utilisez un outil tel que les outils de développement du navigateur pour examiner les
en-têtes. Sous En-têtes de réponse, author: Rick s’affiche.

Pour obtenir des instructions sur le remplacement de l’ordre, consultez Remplacement


de l’ordre par défaut.

Pour obtenir des instructions sur le court-circuitage du pipeline de filtres à partir d’un
filtre, consultez Annulation et court-circuit.

Attribut de filtre Authorize


L’attribut Authorize peut être appliqué à PageModel :

C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace PageFilter.Pages
{
[Authorize]
public class ModelWithAuthFilterModel : PageModel
{
public IActionResult OnGet() => Page();
}
}

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Conventions des routes et des
applications pour Razor Pages dans
ASP.NET Core
Article • 30/11/2023

Découvrez comment utiliser les conventions du fournisseur de modèles de routes et


d’applications de pages pour contrôler le routage, la découverte et le traitement des
pages dans les applications Razor Pages.

Pour spécifier un itinéraire de page, ajoutez des segments de routage ou ajoutez des
paramètres à un itinéraire, utilisez la directive @page de la page. Pour plus
d’informations, consultez Itinéraires personnalisés.

Il existe des mots réservés qui ne peuvent pas être utilisés comme segments de routage
ou noms de paramètres. Pour plus d’informations, consultez Routage : noms de routage
réservés.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Scénario L'exemple montre

Conventions de modèle Ajouter un modèle de route et un en-tête


aux pages d’une application.
Conventions.Add

IPageRouteModelConvention
IPageApplicationModelConvention
IPageHandlerModelConvention

Conventions d’actions de routage de pages Ajouter un modèle de route aux pages d’un
dossier et à une page unique.
AddFolderRouteModelConvention
AddPageRouteModelConvention
AddPageRoute

Conventions d’actions de modèle de page Ajouter un en-tête dans les pages d’un
dossier, ajouter un en-tête dans une page
AddFolderApplicationModelConvention unique et configurer une fabrique de filtres
AddPageApplicationModelConvention pour ajouter un en-tête dans les pages
ConfigureFilter (classe de filtre, expression d’une application.
lambda ou fabrique de filtres)
Les conventions de pages Razor sont configurées à l’aide d’une surcharge
AddRazorPages qui configure RazorPagesOptions. Les exemples de convention suivants
sont expliqués plus loin dans cette rubrique :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages(options =>
{
options.Conventions.Add( ... );
options.Conventions.AddFolderRouteModelConvention(
"/OtherPages", model => { ... });
options.Conventions.AddPageRouteModelConvention(
"/About", model => { ... });
options.Conventions.AddPageRoute(
"/Contact", "TheContactPage/{text?}");
options.Conventions.AddFolderApplicationModelConvention(
"/OtherPages", model => { ... });
options.Conventions.AddPageApplicationModelConvention(
"/About", model => { ... });
options.Conventions.ConfigureFilter(model => { ... });
options.Conventions.ConfigureFilter( ... );
});
}

Ordre d’itinéraire
Les itinéraires spécifient Order pour un traitement (correspondance d’itinéraire).

Ordre Comportement
d’itinéraire

-1 L’itinéraire est traité avant que d’autres itinéraires ne soient traités.

0 L’ordre n’est pas spécifié (valeur par défaut). Ne pas attribuer Order ( Order =
null ) attribue par défaut la valeur 0 à l’itinéraire Order pour le traitement.

1, 2, … n Spécifie l’ordre de traitement de l’itinéraire.

Le traitement des itinéraires est établi par convention :

Les itinéraires sont traités dans l’ordre séquentiel (-1, 0, 1, 2, ... n).
Lorsque les itinéraires ont le même Order , l’itinéraire le plus spécifique est mis en
correspondance en premier, suivi d’itinéraires moins spécifiques.
Lorsque les itinéraires avec le même Order et le même nombre de paramètres
correspondent à une URL de requête, les itinéraires sont traités dans l’ordre dans
lequel ils sont ajoutés à PageConventionCollection.

Si possible, évitez de déprendre d’un ordre de traitement d’itinéraire établie. En règle


générale, le routage sélectionne l’itinéraire approprié avec la correspondance d’URL. Si
vous devez définir les propriétés de routage Order pour acheminer correctement les
demandes, le schéma de routage de l’application est probablement déroutant pour les
clients et fragile à maintenir. Cherchez à simplifier le schéma de routage de l’application.
L’exemple d’application nécessite un ordre de traitement de routage explicite pour
illustrer plusieurs scénarios de routage à l’aide d’une seule application. Toutefois, vous
devez tenter d’éviter la définition de l’itinéraire Order dans les applications de
production.

Le routage de Razor Pages et celui du contrôleur MVC partagent une implémentation.


Les informations sur l’ordre de routage dans les rubriques MVC sont disponibles dans
Routage vers les actions du contrôleur : classement des itinéraires d’attributs.

Conventions de modèle
Ajoutez un délégué pour IPageConvention afin d’ajouter des conventions de modèles
qui s’appliquent à Razor Pages.

Ajouter une convention de modèle de routage à toutes


les pages
Utilise Conventions pour créer et ajouter IPageRouteModelConvention à la collection
d’instances IPageConvention appliquées pendant la construction du modèle de routage
de page.

L’exemple d’application contient la classe GlobalTemplatePageRouteModelConvention pour


ajouter un modèle de routage {globalTemplate?} à toutes les pages de l’application :

C#

using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace SampleApp.Conventions;

public class GlobalTemplatePageRouteModelConvention :


IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 1,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{globalTemplate?}"),
}
});
}
}
}

Dans le code précédent :

PageRouteModel est passé à la méthode Apply.


PageRouteModel.Selectors obtient le nombre de sélecteurs.
Un nouveau SelectorModel est ajouté qui contient AttributeRouteModel

Les options Razor Pages, telles que l’ajout de Conventions, sont ajoutées lorsque Razor
Pages sont ajoutées à la collection de services. Pour obtenir un exemple, consultez
l’exemple d’application .

C#

using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.EntityFrameworkCore;
using SampleApp.Conventions;
using SampleApp.Data;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>

options.UseInMemoryDatabase("InMemoryDb"));

builder.Services.AddRazorPages(options =>
{
options.Conventions.Add(new
GlobalTemplatePageRouteModelConvention());

options.Conventions.AddFolderRouteModelConvention("/OtherPages",
model =>
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{otherPagesTemplate?}"),
}
});
}
});

options.Conventions.AddPageRouteModelConvention("/About", model =>


{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{aboutTemplate?}"),
}
});
}
});

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapRazorPages();
app.Run();

Considérons la classe GlobalTemplatePageRouteModelConvention :

C#
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace SampleApp.Conventions;

public class GlobalTemplatePageRouteModelConvention :


IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 1,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{globalTemplate?}"),
}
});
}
}
}

La propriété Order de AttributeRouteModel a la valeur 1 . Cela garantit le


comportement de correspondance d’itinéraire suivant dans l’exemple d’application :

Un modèle d’itinéraire pour TheContactPage/{text?} est ajouté plus loin dans cette
rubrique. L’itinéraire Contact Page a un ordre par défaut de null ( Order = 0 ), et
correspond donc avant le modèle d’itinéraire {globalTemplate?} qui a Order = 1 .

Le modèle d’itinéraire {aboutTemplate?} est affiché dans le code précédent. Le


modèle {aboutTemplate?} reçoit l’ordre Order avec la valeur 2 . Quand la page
About est demandée sur /About/RouteDataValue , « RouteDataValue » est chargé
dans RouteData.Values["globalTemplate"] ( Order = 1 ) et non
RouteData.Values["aboutTemplate"] ( Order = 2 ) en raison de la définition de la

propriété Order .

Le modèle d’itinéraire {otherPagesTemplate?} est affiché dans le code précédent.


Le modèle {otherPagesTemplate?} reçoit l’ordre Order avec la valeur 2 . Quand une
page du dossier Pages/OtherPages est demandée avec un paramètre d’itinéraire :

Par exemple : /OtherPages/Page1/xyz


La valeur de données de routage "xyz" est chargée dans
RouteData.Values["globalTemplate"] ( Order = 1 ).

RouteData.Values["otherPagesTemplate"] avec ( Order = 2 ) n’est pas chargé en

raison de la propriété Order 2 ayant une valeur plus élevée.

Si possible, ne définissez pas Order . Si Order n’est pas définie, la valeur par défaut est
Order = 0 . Utilisez le routage pour sélectionner l’itinéraire approprié plutôt que la

propriété Order .

Demandez la page About de l’exemple à localhost:{port}/About/GlobalRouteValue et


inspectez le résultat :

L’exemple d’application utilise le package NuGet Rick.Docs.Samples.RouteInfo pour


afficher les informations de routage dans la sortie de journalisation. À l’aide de
localhost:{port}/About/GlobalRouteValue , l’enregistreur d’événements affiche la

requête, Order et le modèle utilisé :

CLI .NET

info: SampleApp.Pages.AboutModel[0]
/About/GlobalRouteValue Order = 1 Template =
About/{globalTemplate?}

Ajouter une convention de modèle d’application à toutes


les pages
Utilisez Conventions pour créer et ajouter IPageApplicationModelConvention à la
collection d’instances IPageConvention appliquées pendant la construction du modèle
d’application de page.

Pour illustrer cette convention et bien d’autres, plus loin dans cette rubrique, l’exemple
d’application inclut une classe AddHeaderAttribute . Le constructeur de classe accepte
une chaîne name et un tableau de chaînes values . Ces valeurs sont utilisées dans sa
méthode OnResultExecuting pour définir un en-tête de réponse. La classe complète est
affichée dans la section Conventions d’actions de modèle de page, plus loin dans cette
rubrique.

L’exemple d’application utilise la classe AddHeaderAttribute pour ajouter un en-tête,


GlobalHeader , à toutes les pages de l’application :

C#

public class GlobalHeaderPageApplicationModelConvention


: IPageApplicationModelConvention
{
public void Apply(PageApplicationModel model)
{
model.Filters.Add(new AddHeaderAttribute(
"GlobalHeader", new string[] { "Global Header Value" }));
}
}

Program.cs :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("InMemoryDb"));

builder.Services.AddRazorPages(options =>
{
options.Conventions.Add(new
GlobalTemplatePageRouteModelConvention());

options.Conventions.Add(new
GlobalHeaderPageApplicationModelConvention());

Demandez la page About de l’exemple sur localhost:{port}/About et examinez les en-


têtes pour voir le résultat :
Ajouter une convention de modèle de gestionnaire à
toutes les pages
Utilisez Conventions pour créer et ajouter IPageHandlerModelConvention à la collection
d’instances IPageConvention appliquées pendant la construction du modèle de
gestionnaire de page.

C#

public class GlobalPageHandlerModelConvention


: IPageHandlerModelConvention
{
public void Apply(PageHandlerModel model)
{
// Access the PageHandlerModel
}
}

Conventions d’actions de routage de pages


Le fournisseur de modèle de routage par défaut qui dérive de IPageRouteModelProvider
appelle des conventions conçues pour fournir des points d’extensibilité pour la
configuration des itinéraires de page.

Convention de modèle de routage de dossier


Utilisez AddFolderRouteModelConvention pour créer et ajouter un
IPageRouteModelConvention qui appelle une action sur le PageRouteModel pour toutes
les pages sous le dossier spécifié.

L’exemple d’application utilise AddFolderRouteModelConvention pour ajouter un


modèle de routage {otherPagesTemplate?} aux pages du dossier OtherPages :
C#

options.Conventions.AddFolderRouteModelConvention("/OtherPages", model =>


{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{otherPagesTemplate?}"),
}
});
}
});

La propriété Order de AttributeRouteModel a la valeur 2 . Cela permet de garantir que le


modèle de {globalTemplate?} (défini plus tôt dans cette rubrique sur 1 ) est prioritaire
pour la première position de la valeur des données de routage quand une valeur de
routage unique est fournie. Si une page du dossier Pages/OtherPages est demandée
avec une valeur de paramètre de routage (par exemple,
/OtherPages/Page1/RouteDataValue ), « RouteDataValue » est chargé dans

RouteData.Values["globalTemplate"] ( Order = 1 ) et non

RouteData.Values["otherPagesTemplate"] ( Order = 2 ) en raison de la définition de la


propriété Order .

Dans la mesure du possible, ne définissez pas Order , qui entraîne Order = 0 . Utilisez le
routage pour sélectionner l’itinéraire approprié.

Demandez la page Page1 de l’exemple sur


localhost:5000/OtherPages/Page1/GlobalRouteValue/OtherPagesRouteValue et examinez

le résultat :
Convention de modèle de routage de page
Utilisez AddPageRouteModelConvention pour créer et ajouter
IPageRouteModelConvention qui appelle une action sur le PageRouteModel de la page
avec le nom spécifié.

L’exemple d’application utilise AddPageRouteModelConvention pour ajouter un modèle de


routage {aboutTemplate?} à la page About :

C#

options.Conventions.AddPageRouteModelConvention("/About", model =>


{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{aboutTemplate?}"),
}
});
}
});

La propriété Order de AttributeRouteModel a la valeur 2 . Cela permet de garantir que le


modèle de {globalTemplate?} (défini plus tôt dans cette rubrique sur 1 ) est prioritaire
pour la première position de la valeur des données de routage quand une valeur de
routage unique est fournie. Si la page About est demandée avec une valeur de
paramètre de routage sur /About/RouteDataValue , « RouteDataValue » est chargé dans
RouteData.Values["globalTemplate"] ( Order = 1 ) et non

RouteData.Values["aboutTemplate"] ( Order = 2 ) en raison de la définition de la

propriété Order .

Dans la mesure du possible, ne définissez pas Order , qui entraîne Order = 0 . Utilisez le
routage pour sélectionner l’itinéraire approprié.

Demandez la page About de l’exemple sur localhost:


{port}/About/GlobalRouteValue/AboutRouteValue et examinez le résultat :

La sortie de l’enregistreur d'événements s’affiche :

CLI .NET

info: SampleApp.Pages.AboutModel[0]
/About/GlobalRouteValue/AboutRouteValue Order = 2 Template =
About/{globalTemplate?}/{aboutTemplate?}

Utilisez un transformateur de paramètres pour


personnaliser les itinéraires de page
Voir Transformateurs de paramètres.

Configurer un routage de page


Utilisez AddPageRoute pour configurer un routage vers une page située dans le chemin
spécifié. Les liens générés qui pointent vers la page utilisent le routage spécifié.
AddPageRoute utilise AddPageRouteModelConvention pour établir le routage.

L’exemple d’application crée un itinéraire vers /TheContactPage pour la page


Contact Razor :

C#

options.Conventions.AddPageRoute("/Contact", "TheContactPage/{text?}");

La page Contact est également accessible sur / Contact1` via son routage par défaut.

Le routage personnalisé de l’exemple d’application vers la page Contact permet un


segment de routage text facultatif ( {text?} ). Cette page inclut également ce segment
facultatif dans sa directive @page , au cas où le visiteur accèderait à la page via le routage
/Contact :

CSHTML

@page "{text?}"
@model ContactModel
@{
ViewData["Title"] = "Contact";
}

<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>

<address>
One Microsoft Way<br>
Redmond, WA 98052-6399<br>
<abbr title="Phone">P:</abbr>
425.555.0100
</address>

<address>
<strong>Support:</strong> <a
href="mailto:Support@example.com">Support@example.com</a><br>
<strong>Marketing:</strong> <a
href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>

<p>@Model.RouteDataTextTemplateValue</p>

Notez que l’URL générée pour le lien Contact dans la page affichée reflète le routage
mis à jour :
Visitez la page Contact via son routage ordinaire, /Contact ou via le routage
personnalisé, /TheContactPage . Si vous indiquez un segment de routage text
supplémentaire, la page affiche le segment HTML que vous fournissez :

Conventions d’actions de modèle de page


Le fournisseur de modèles de pages par défaut, qui implémente
IPageApplicationModelProvider, appelle des conventions conçues pour fournir des
points d’extensibilité permettant de configurer les modèles de pages. Ces conventions
sont utiles durant la génération et la modification de scénarios de découverte et de
traitement de pages.

Pour les exemples de cette section, l’exemple d’application utilise une classe
AddHeaderAttribute , qui est un ResultFilterAttribute, et qui applique un en-tête de
réponse :

C#

public class AddHeaderAttribute : ResultFilterAttribute


{
private readonly string _name;
private readonly string[] _values;

public AddHeaderAttribute(string name, string[] values)


{
_name = name;
_values = values;
}

public override void OnResultExecuting(ResultExecutingContext context)


{
context.HttpContext.Response.Headers.Add(_name, _values);
base.OnResultExecuting(context);
}
}

À l’aide de conventions, l’exemple montre comment appliquer l’attribut à toutes les


pages d’un dossier et à une seule page.

Convention de modèle d’application de dossier

Utilisez AddFolderApplicationModelConvention pour créer et ajouter


IPageApplicationModelConvention qui appelle une action sur les instances
PageApplicationModel pour toutes les pages du dossier spécifié.

L’exemple illustre l’utilisation de AddFolderApplicationModelConvention en ajoutant un


en-tête, OtherPagesHeader , aux pages situées dans le dossier OtherPages de
l’application :

C#

options.Conventions.AddFolderApplicationModelConvention("/OtherPages", model
=>
{
model.Filters.Add(new AddHeaderAttribute(
"OtherPagesHeader", new string[] { "OtherPages Header Value" }));
});

Demandez la page Page1 de l’exemple sur localhost:5000/OtherPages/Page1 et


examinez les en-têtes pour voir le résultat :
Convention de modèle d’application de page

Utilisez AddPageApplicationModelConvention pour créer et ajouter


IPageApplicationModelConvention qui appelle une action sur le PageApplicationModel
pour la page avec le nom spécifié.

L’exemple illustre l’utilisation de AddPageApplicationModelConvention en ajoutant un en-


tête, AboutHeader , à la page About :

C#

options.Conventions.AddPageApplicationModelConvention("/About", model =>


{
model.Filters.Add(new AddHeaderAttribute(
"AboutHeader", new string[] { "About Header Value" }));
});

Demandez la page About de l’exemple sur localhost:5000/About et examinez les en-


têtes pour voir le résultat :

Configurer un filtre

ConfigureFilter configure le filtre spécifié à appliquer. Vous pouvez implémenter une


classe de filtre. Toutefois, l’exemple d’application montre comment implémenter un filtre
dans une expression lambda, laquelle est implémentée en arrière-plan en tant que
fabrique qui retourne un filtre :

C#

options.Conventions.ConfigureFilter(model =>
{
if (model.RelativePath.Contains("OtherPages/Page2"))
{
return new AddHeaderAttribute(
"OtherPagesPage2Header",
new string[] { "OtherPages/Page2 Header Value" });
}
return new EmptyFilter();
});

Le modèle d’application de page est utilisé pour vérifier le chemin relatif des segments
qui mènent à la page Page2 dans le dossier OtherPages. Si la condition est satisfaite, un
en-tête est ajouté. Sinon, EmptyFilter est appliqué.

EmptyFilter est un filtre d’action. Étant donné que les filtres d’action sont ignorés par

Razor Pages, EmptyFilter n’a aucun effet comme prévu si le chemin d’accès ne contient
pas OtherPages/Page2 .

Demandez la page Page2 de l’exemple sur localhost:5000/OtherPages/Page2 et


examinez les en-têtes pour voir le résultat :

Configurer une fabrique de filtres

ConfigureFilter configure la fabrique spécifiée pour appliquer des filtres à toutes les
pages Razor.

L’exemple d’application illustre l’utilisation d’une fabrique de filtres en ajoutant un en-


tête, FilterFactoryHeader , et deux valeurs aux pages de l’application :
C#

options.Conventions.ConfigureFilter(new AddHeaderWithFactory());

AddHeaderWithFactory.cs :

C#

public class AddHeaderWithFactory : IFilterFactory


{
// Implement IFilterFactory
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new AddHeaderFilter();
}

private class AddHeaderFilter : IResultFilter


{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(
"FilterFactoryHeader",
new string[]
{
"Filter Factory Header Value 1",
"Filter Factory Header Value 2"
});
}

public void OnResultExecuted(ResultExecutedContext context)


{
}
}

public bool IsReusable


{
get
{
return false;
}
}
}

Demandez la page About de l’exemple sur localhost:5000/About et examinez les en-


têtes pour voir le résultat :
Filtres MVC et filtre de page (IPageFilter)
Les filtres d’action MVC sont ignorés par les Razor Pages, car les Razor Pages utilise des
méthodes de gestionnaire. Vous pouvez utiliser d’autres types de filtre MVC :
Autorisation, Exception, Ressource et Résultat. Pour plus d’informations, consultez la
rubrique Filtres.

Le filtre page (IPageFilter) est un filtre qui s’applique à Razor Pages. Pour plus
d’informations, consultez méthodes de filtres pour Razor Pages.

Ressources supplémentaires
Routage de Razor Pages
Conventions des autorisations de Razor Pages dans ASP.NET Core
Zones dans ASP.NET Core

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Vue d’ensemble d’ASP.NET Core MVC
Article • 30/11/2023

Par Steve Smith

ASP.NET Core MVC est un puissant framework qui vous permet de générer des
applications web et des API à l’aide du modèle de conception Model-View-Controller.

Modèle MVC
Le modèle d’architecture Model-View-Controller (MVC) sépare une application en trois
groupes de composants principaux : les modèles, les vues et les contrôleurs. Ce modèle
permet d’effectuer la séparation des préoccupations. En utilisant ce modèle, les
demandes de l’utilisateur sont acheminées vers un contrôleur qui a la responsabilité de
fonctionner avec le modèle pour effectuer des actions de l’utilisateur et/ou de récupérer
les résultats de requêtes. Le contrôleur choisit la vue à afficher à l’utilisateur et lui fournit
toutes les données de modèle dont elle a besoin.

Le diagramme suivant montre les trois composants principaux et leurs relations entre
eux :

Cette délimitation des responsabilités vous aide à mettre à l’échelle la complexité de


l’application, car il est plus facile de coder, déboguer et tester une chose (modèle, vue
ou contrôleur) qui a un seul travail. Il est plus difficile de mettre à jour, tester et
déboguer du code dont les dépendances sont réparties sur deux de ces domaines ou
plus. Par exemple, la logique de l’interface utilisateur a tendance à changer plus
fréquemment que la logique métier. Si le code de présentation et la logique métier sont
combinés en un seul objet, l’objet contenant la logique métier doit être modifié chaque
fois que l’interface utilisateur change. Cela introduit souvent des erreurs et nécessite de
retester la logique métier après chaque changement minimal de l’interface utilisateur.

7 Notes

La vue et le contrôleur dépendent tous deux du modèle. Toutefois, le modèle ne


dépend ni de la vue ni du contrôleur. Il s’agit de l’un des principaux avantages de la
séparation. Cette séparation permet de générer et de tester le modèle
indépendamment de la présentation visuelle.

Responsabilités du modèle
Le modèle d’une application MVC représente l’état de l’application, ainsi que la logique
métier ou les opérations à effectuer. La logique métier doit être encapsulée dans le
modèle, ainsi que toute autre logique d’implémentation pour la persistance de l’état de
l’application. En général, les vues fortement typées utilisent des types ViewModel
conçus pour contenir les données à afficher sur cette vue. Le contrôleur crée et remplit
ces instances de ViewModel à partir du modèle.

Responsabilités de la vue
Les vues sont responsables de la présentation du contenu via l’interface utilisateur. Elles
utilisent le Razor moteur de vue pour incorporer du code .NET dans les balises HTML. Il
doit exister une logique minimale dans les vues, et cette logique doit être liée à la
présentation du contenu. Si vous avez besoin d’exécuter une grande partie de la logique
dans les fichiers de vue pour afficher les données d’un modèle complexe, utilisez un
composant de vue, ViewModel ou un modèle de vue pour simplifier la vue.

Responsabilités du contrôleur
Les contrôleurs sont des composants qui gèrent l’interaction avec l’utilisateur,
fonctionnent avec le modèle et, au final, sélectionnent une vue à afficher. Dans une
application MVC, la vue affiche uniquement des informations ; le contrôleur gère les
entrées et interactions des utilisateurs, et y répond. Dans le modèle MVC, le contrôleur
est le point d’entrée initial. Il est responsable de la sélection des types de modèle à
utiliser et de la vue à afficher (ce qui explique son nom, car il contrôle la manière dont
l’application répond à une requête donnée).
7 Notes

Vous devez éviter les excès de responsabilités pour ne pas rendre les contrôleurs
trop complexes. Pour éviter que la logique du contrôleur ne devienne trop
complexe, envoyez (push) la logique métier hors du contrôleur vers le modèle de
domaine.

 Conseil

Si vous constatez que le contrôleur effectue souvent les mêmes genres d’action,
placez ces actions usuelles dans des filtres.

ASP.NET Core MVC


Le framework ASP.NET Core MVC est un framework de présentation léger, open source
et hautement testable, optimisé pour ASP.NET Core.

ASP.NET Core MVC offre un fonctionnement basé sur des patterns pour créer des sites
Web dynamiques qui permettent une séparation claire des préoccupations. Il vous
donne un contrôle total sur le balisage, prend en charge les développements TDD et
utilise les standards web les plus récents.

Routage
ASP.NET Core MVC est construit sur le routage d'ASP.NET Core, un composant de
mappage d’URL puissant qui vous permet de créer des applications ayant des URLs
compréhensibles et découvrables. Cela vous permet de définir les patterns de noms
d’URL de votre application qui fonctionnent bien pour l’optimisation des moteurs de
recherche (SEO) et pour la génération de lien, sans tenir compte de la façon dont les
fichiers sont organisés sur votre serveur web. Vous pouvez définir vos routes à l’aide
d’une syntaxe pratique de modèle de routes (route template) qui prend en charge les
contraintes de valeur, les valeurs par défaut et les valeurs facultatives de routes.

Le routage basé sur les conventions vous permet de définir en gros les formats d’URL
acceptés par votre application, ainsi que le mappage de chacun de ces formats à une
méthode d’action spécifique sur un contrôleur donné. Quand une requête entrante est
reçue, le moteur de routage analyse l’URL et la fait correspondre à l’un des formats
d’URL définis, puis il appelle la méthode d’action du contrôleur associé.
C#

routes.MapRoute(name: "Default", template: "


{controller=Home}/{action=Index}/{id?}");

Le routage d’attributs vous permet de spécifier des informations de routage en décorant


vos contrôleurs et vos actions avec des attributs qui définissent les routages de votre
application. Cela signifie que vos définitions de routage sont placées à côté du
contrôleur et de l’action auxquels elles sont associées.

C#

[Route("api/[controller]")]
public class ProductsController : Controller
{
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
...
}
}

Liaison de données
La liaison de modèle ASP.NET Core MVC convertit les données de requête client (les
valeurs de formulaire, les données de routage, les paramètres de chaîne de requête, les
en-têtes HTTP) en objets que le contrôleur peut traiter. Par conséquent, votre logique de
contrôleur ne doit pas nécessairement faire le travail d’identifier les données de requête
entrante; Il a simplement les données en tant que paramètres dans ses méthodes
d’action.

C#

public async Task<IActionResult> Login(LoginViewModel model, string


returnUrl = null) { ... }

Validation du modèle
ASP.NET Core MVC prend en charge la validation en décorant votre objet de modèle
avec des attributs de validation d’annotation de données. Les attributs de validation
sont vérifiés côté client avant que les valeurs ne soient postées sur le serveur, ainsi que
sur le serveur avant l’appel de l’action du contrôleur.
C#

using System.ComponentModel.DataAnnotations;
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }

[Required]
[DataType(DataType.Password)]
public string Password { get; set; }

[Display(Name = "Remember me?")]


public bool RememberMe { get; set; }
}

Action du contrôleur :

C#

public async Task<IActionResult> Login(LoginViewModel model, string


returnUrl = null)
{
if (ModelState.IsValid)
{
// work with the model
}
// At this point, something failed, redisplay form
return View(model);
}

Le framework gère la validation des données de requête à la fois sur le client et sur le
serveur. La logique de validation spécifiée pour les types de modèle est ajoutée aux
vues affichées en tant qu’annotations discrètes, et est appliquée dans le navigateur à
l’aide de la validation jQuery .

Injection de dépendances
ASP.NET Core offre une prise en charge intégrée de l’injection de dépendances. Dans
ASP.NET Core MVC, les contrôleurs peuvent demander les services nécessaires via leurs
constructeurs, ce qui leur permet de suivre le principe des dépendances explicites.

Votre application peut également utiliser l’injection de dépendances dans les fichiers de
vue, à l’aide de la directive @inject :

CSHTML
@inject SomeService ServiceName

<!DOCTYPE html>
<html lang="en">
<head>
<title>@ServiceName.GetTitle</title>
</head>
<body>
<h1>@ServiceName.GetTitle</h1>
</body>
</html>

Filtres
Les filtres permettent aux développeurs d’intégrer des problèmes transversaux, par
exemple la prise en charge des exceptions ou les autorisations. Les filtres permettent
d’exécuter une logique de prétraitement et de posttraitement personnalisée pour les
méthodes d’action. Vous pouvez les configurer pour qu’ils se lancent à certaines étapes
du pipeline d’exécution d’une requête donnée. Vous pouvez appliquer les filtres aux
contrôleurs ou aux actions en tant qu’attributs (ou vous pouvez les exécuter de manière
globale). Plusieurs filtres (tels que Authorize ) sont inclus dans le framework.
[Authorize] est l’attribut qui est utilisé pour créer des filtres d’autorisation MVC.

C#

[Authorize]
public class AccountController : Controller

Domaines
Les zones permettent de partitionner une grande application web ASP.NET Core MVC
en regroupements opérationnels plus petits. Une zone est en réalité une structure MVC
à l’intérieur d’une application. Dans un projet MVC, les composants logiques, comme
Modèle, Contrôleur et Vue, sont conservés dans des dossiers différents, et MVC utilise
des conventions de nommage pour créer la relation entre ces composants. Pour une
application volumineuse, il peut être avantageux de partitionner l’application en
différentes zones de fonctionnalités de premier niveau. Par exemple, une application de
commerce électronique avec plusieurs entités, telles que le paiement, le facturation et la
recherche, etc. Chacune de ces unités a ses propres composants logiques de vues,
contrôleurs et modèles.
API Web
En plus d’être une excellente plateforme pour la création de sites web, ASP.NET Core
MVC prend en charge la génération d’API web. Vous pouvez créer des services
accessibles à un large éventail de clients, notamment les navigateurs et les appareils
mobiles.

L’infrastructure comprend la prise en charge de la négociation de contenu HTTP, ainsi


que la prise en charge intégrée de la mise en forme des données au format JSON ou
XML. Écrivez des formateurs personnalisés pour ajouter la prise en charge de vos
propres formats.

Utilisez la génération de lien pour activer la prise en charge de liens hypermédia. Activez
facilement la prise en charge du partage de ressources Cross-Origin (CORS) afin que
vos API web puissent être partagées entre plusieurs applications web.

Testabilité
Le framework utilise les interfaces et l’injection de dépendances, ce qui le rend
particulièrement adapté aux tests unitaires. De plus, le framework inclut des
fonctionnalités (par exemple un fournisseur TestHost et InMemory pour Entity
Framework) qui facilitent aussi les tests d’intégration. Découvrez en plus sur le test de la
logique du contrôleur.

Moteur d’affichage Razor


Les vues ASP.NET Core MVC utilisent le Razor moteur de vue pour afficher les vues.
Razor est un langage de balisage de modèles compact, expressif et fluide permettant de
définir des vues avec du code C# incorporé. Razor est utilisé pour générer de façon
dynamique du contenu web sur le serveur. Vous pouvez mélanger sans problème du
code serveur avec du contenu et du code côté client.

CSHTML

<ul>
@for (int i = 0; i < 5; i++) {
<li>List item @i</li>
}
</ul>

À l’aide du moteur de vue Razor, vous pouvez définir des dispositions, des vues
partielles et des sections remplaçables.
Vues fortement typées
Les vues Razor dans MVC peuvent être fortement typées en fonction de votre modèle.
Les contrôleurs peuvent passer un modèle fortement typé à des vues, ce qui leur permet
de disposer du contrôle de type et de la prise en charge d’IntelliSense.

Par exemple, la vue suivante affiche un modèle de type IEnumerable<Product> :

CSHTML

@model IEnumerable<Product>
<ul>
@foreach (Product p in Model)
{
<li>@p.Name</li>
}
</ul>

Tag Helpers
Les Tag Helpers permettent au code côté serveur de participer à la création et au rendu
des éléments HTML dans les fichiers Razor. Vous pouvez utiliser des Tag helpers pour
définir des balises personnalisées (par exemple, <environment> ) ou pour modifier le
comportement de balises existantes (par exemple, <label> ). Les Tag helpers associent
des éléments spécifiques en fonction du nom de l’élément et des ses attributs. Ils
fournissent les avantages de rendu côté serveur tout en conservant la possibilité d'éditer
le HTML.

Il existe de nombreux Tag Helpers pour les tâches courantes (par exemple la création de
formulaires ou de liens, le chargement de ressources, etc.) et bien d’autres encore, dans
les dépôts GitHub publics et sous forme de packages NuGet. Les Tag helpers sont créés
en c#, et ils ciblent des éléments HTML en fonction de la balise parente, du nom
d’attribut ou du nom de l’élément. Par exemple, le Tag Helper Link intégré permet de
créer un lien vers l’action Login de AccountsController :

CSHTML

<p>
Thank you for confirming your email.
Please <a asp-controller="Account" asp-action="Login">Click here to Log
in</a>.
</p>
Le EnvironmentTagHelper permet d’inclure différents scripts dans vos vues (par exemple
bruts ou minimisés) en fonction de l’environnement d’exécution, Développement,
Préproduction ou Production :

CSHTML

<environment names="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
</environment>
<environment names="Staging,Production">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.4.js"
asp-fallback-src="~/lib/jquery/dist/jquery.js"
asp-fallback-test="window.jQuery">
</script>
</environment>

Les Tag Helpers fournissent une expérience utilisateur de développement HTML et un


riche environnement IntelliSense pour la création de balises HTML et Razor. La plupart
des Tag Helpers intégrés ciblent les éléments HTML existants et fournissent des attributs
côté serveur pour l’élément.

Les composants de vues


Les composants de vues vous permettent de compresser la logique de rendu et de la
réutiliser dans l’application. Ils sont similaires aux vues partielles, mais avec une logique
associée.

Version de compatibilité
La méthode SetCompatibilityVersion permet à une application d’accepter ou de refuser
les changements de comportement potentiellement cassants introduits dans ASP.NET
Core MVC 2.1 ou version ultérieure.

Pour plus d’informations, consultez Version de compatibilité pour ASP.NET Core MVC.

Ressources supplémentaires
MyTested.AspNetCore.Mvc – Bibliothèque de tests Fluent pour ASP.NET Core
MVC : bibliothèque de tests unitaires fortement typés, fournissant une interface
Fluent pour tester les applications MVC et d’API web. (Non géré ou pris en charge
par Microsoft.)
Injection de dépendances dans ASP.NET Core
6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Bien démarrer avec ASP.NET Core MVC
Article • 30/11/2023

Par Rick Anderson

Ce didacticiel vous permet de découvrir le développement web ASP.NET Core MVC avec
des contrôleurs et des vues. Si vous débutez dans le développement web ASP.NET Core,
choisissez la version RazorRazor Pages de ce didacticiel, qui fournit un point de départ
plus facile. Consultez Choisir une interface utilisateur ASP.NET Core, qui compare Razor
Pages, MVC et Blazor pour le développement de l’interface utilisateur.

Il s’agit du premier didacticiel d’une série qui enseigne le développement web ASP.NET
Core MVC avec des contrôleurs et des vues.

À la fin de cette série, vous disposerez d’une application permettant de gérer et


d’afficher des données de film. Vous allez apprendre à effectuer les actions suivantes :

" Créez une application web.


" Ajouter et structurer un modèle.
" Utiliser une base de données.
" Ajouter une fonctionnalité de recherche et de validation.

Affichez ou téléchargez un exemple de code (procédure de téléchargement).

Prérequis
Visual Studio

Préversion de Visual Studio 2022 avec la charge de travail ASP.NET et


développement web.
Créer une application web
Visual Studio

Démarrez Visual Studio et sélectionnez Créer un projet.


Dans la boîte de dialogue Créer un nouveau projet, sélectionnez Application
web ASP.NET Core (modèle-vue-contrôleur)>Suivant.
Dans la boîte de dialogue Configurer votre nouveau projet, entrez MvcMovie
pour Nom du projet. Il est important de nommer le projet MvcMovie. La mise
en majuscules doit correspondre à chaque namespace quand le code est copié.
Cliquez sur Suivant.
Dans la boîte de dialogue Informations supplémentaires :
Sélectionnez .NET 8.0 (support à long terme).
Vérifiez que l’option ne pas utiliser les instructions de niveau supérieur
soit décochée.
Cliquez sur Créer.
Pour plus d’informations, notamment d’autres approches pour créer le projet,
consultez Créer un projet dans Visual Studio.

Visual Studio utilise le modèle de projet par défaut pour le projet MVC créé. Le
projet créé :

Est une application opérationnelle.


Est un projet de démarrage de base.

Exécuter l'application

Visual Studio

Sélectionnez Ctrl+F5 pour exécuter l’application sans le débogueur.

Visual Studio affiche la boîte de dialogue suivante lorsqu’un projet n’est pas
encore configuré pour utiliser SSL :
Sélectionnez Oui si vous faites confiance au certificat SSL d’IIS Express.

La boîte de dialogue suivante s’affiche :

Sélectionnez Oui si vous acceptez d’approuver le certificat de développement.

Pour plus d’informations sur l’approbation du navigateur Firefox, consultez


Erreur de certificat Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.

Visual Studio exécute l’application et ouvre le navigateur par défaut.

La barre d’adresses affiche localhost:<port#> au lieu de quelque chose qui


ressemble à example.com . Le nom d’hôte standard de votre ordinateur local est
localhost . Quand Visual Studio crée un projet web, un port aléatoire est utilisé

pour le serveur web.


Le lancement de l’application sans débogage en sélectionnant Ctrl+F5 vous permet
de :

Modifiez le code.
Enregistrez le fichier.
Actualiser rapidement le navigateur et voir les modifications de code.

Vous pouvez lancer l’application en mode débogage ou non-débogage à partir du


menu Déboguer :

Vous pouvez déboguer l’application en sélectionnant le bouton https dans la barre


d’outils :
L’image suivante montre l’application :

Visual Studio

Aide de Visual Studio


Apprendre à déboguer le code C# avec Visual Studio
Introduction à l’IDE Visual Studio

Dans le prochain didacticiel de la série, vous allez découvrir MVC et commencer à écrire
du code.

Suivant : Ajouter un contrôleur

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus
documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 2, ajouter un contrôleur à une
application ASP.NET Core MVC
Article • 30/11/2023

Par Rick Anderson

Le modèle d’architecture MVC (Model-View-Controller) sépare une application en trois


composants principaux : Modèle, Vue et Contrôleur. Le modèle MVC vous permet de
créer des applications qui sont plus faciles à tester et à mettre à jour que les applications
monolithiques traditionnelles.

Les applications basées sur MVC contiennent :

Des Modèles : des classes qui représentent les données de l’application. Les classes
du modèle utilisent une logique de validation pour appliquer des règles
d’entreprise à ces données. En règle générale, les objets du modèle récupèrent et
stockent l’état du modèle dans une base de données. Dans ce didacticiel, un
modèle Movie récupère les données des films dans une base de données, les
fournit à la vue ou les met à jour. Les données mises à jour sont écrites dans une
base de données.
Vues : les vues sont les composants qui affichent l’interface utilisateur de
l’application. En règle générale, cette interface utilisateur affiche les données du
modèle.
Contrôleurs : Classes qui :
Gèrent les demandes de navigateur.
Récupèrent les données du modèle.
Appellent les modèles d’affichage qui retournent une réponse.

Dans une application MVC, la vue affiche uniquement des informations. Le contrôleur
gère et répond à l’entrée et à l’interaction utilisateur. Par exemple, le contrôleur gère les
valeurs des données de routage et des chaînes de requête, et passe ces valeurs au
modèle. Le modèle peut utiliser ces valeurs pour interroger la base de données. Par
exemple :

https://localhost:5001/Home/Privacy : spécifie le contrôleur Home et l’action

Privacy .

https://localhost:5001/Movies/Edit/5 : est une requête de modification du film


avec ID=5 à l’aide du contrôleur Movies et de l’action Edit , qui sont détaillées plus
loin dans le tutoriel.

Les données d’itinéraire sont expliquées plus loin dans le didacticiel.


Le modèle d’architecture MVC sépare une application en trois groupes de composants
principaux : les modèles, les vues et les contrôleurs. Ce modèle permet de séparer les
problèmes : La logique d’interface utilisateur appartient à la vue. La logique d’entrée
appartient au contrôleur. La logique métier appartient au modèle. Cette séparation vous
aide à gérer la complexité quand vous créez une application, car elle vous permet de
travailler sur un aspect de l’implémentation à la fois, sans impacter le code d’un autre
aspect. Par exemple, vous pouvez travailler sur le code des vues de façon indépendante
du code de la logique métier.

Ces concepts sont présentés et démontrés dans cette série de tutoriels lors de la
création d’une application de film. Le projet MVC contient des dossiers pour les
contrôleurs et pour les vues.

Ajout d'un contrôleur


Visual Studio

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur Contrôleurs >
Ajouter > Contrôleur.

Dans la boîte de dialogue Ajouter un nouvel élément généré automatiquement,


sélectionnez Contrôleur MVC - Vide>Ajouter.
Dans la boîte de dialogue Ajouter un nouvel élément - MvcMovie, entrez
HelloWorldController.cs et sélectionnez Ajouter.

Remplacez le contenu de Controllers/HelloWorldController.cs par le code suivant :

C#

using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;

namespace MvcMovie.Controllers;

public class HelloWorldController : Controller


{
//
// GET: /HelloWorld/
public string Index()
{
return "This is my default action...";
}
//
// GET: /HelloWorld/Welcome/
public string Welcome()
{
return "This is the Welcome action method...";
}
}
Chaque méthode public d’un contrôleur peut être appelée en tant que point de
terminaison HTTP. Dans l’exemple ci-dessus, les deux méthodes retournent une chaîne.
Notez les commentaires qui précèdent chaque méthode.

Un point de terminaison HTTP :

URL pouvant être ciblée dans l’application web, telle que


https://localhost:5001/HelloWorld .

Combine :
Protocole utilisé : HTTPS .
Emplacement réseau du serveur web, y compris le port TCP : localhost:5001 .
URI cible : HelloWorld .

Le premier commentaire indique qu’il s’agit d’une méthode HTTP GET qui est appelée
en ajoutant /HelloWorld/ à l’URL de base.

Le deuxième commentaire indique une méthode HTTP GET qui est appelée en
ajoutant /HelloWorld/Welcome/ à l’URL. Plus loin dans ce tutoriel, le moteur de
génération de modèles automatique est utilisé pour générer des méthodes HTTP POST
qui mettent à jour des données.

Exécutez l’application sans le débogueur.

Ajoutez /HelloWorld au chemin dans la barre d’adresse. La méthode Index retourne


une chaîne.

MVC appelle les classes du contrôleur et les méthodes d’action au sein de celles-ci en
fonction de l’URL entrante. La logique de routage d’URL par défaut utilisée par le
modèle MVC utilise un format comme celui-ci pour déterminer le code à appeler :

/[Controller]/[ActionName]/[Parameters]
Le format de routage est défini dans le fichier Program.cs .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

Quand vous naviguez jusqu’à l’application et que vous ne fournissez aucun segment
d’URL, sa valeur par défaut est le contrôleur « Home » et la méthode « Index » spécifiée
dans la ligne du modèle mise en surbrillance ci-dessus. Dans les segments d’URL
précédents :

Le premier segment d’URL détermine la classe du contrôleur à exécuter.


localhost:5001/HelloWorld mappe donc à la classe de contrôleur HelloWorld.

La seconde partie du segment d’URL détermine la méthode d’action sur la classe.


Ainsi, localhost:5001/HelloWorld/Index provoque l’exécution de la méthode Index
de la classe HelloWorldController . Notez que vous n’avez eu qu’à accéder à
localhost:5001/HelloWorld pour que la méthode Index soit appelée par défaut.
Index est la méthode par défaut qui est appelée sur un contrôleur si un nom de

méthode n’est pas explicitement spécifié.


La troisième partie du segment d’URL ( id ) concerne les données de routage. Les
données d’itinéraire sont expliquées plus loin dans le didacticiel.

Accédez à : https://localhost:{PORT}/HelloWorld/Welcome . Remplacez {PORT} par votre


numéro de port.

La méthode Welcome s’exécute et retourne la chaîne This is the Welcome action


method... . Pour cette URL, le contrôleur est HelloWorld , et Welcome est la méthode

d’action. Vous n’avez pas encore utilisé la partie [Parameters] de l’URL.


Modifiez le code pour passer des informations sur les paramètres de l’URL au
contrôleur. Par exemple : /HelloWorld/Welcome?name=Rick&numtimes=4 .

Modifiez la méthode Welcome en y incluant les deux paramètres, comme indiqué dans le
code suivant.

C#

// GET: /HelloWorld/Welcome/
// Requires using System.Text.Encodings.Web;
public string Welcome(string name, int numTimes = 1)
{
return HtmlEncoder.Default.Encode($"Hello {name}, NumTimes is:
{numTimes}");
}

Le code précédent :

Utilise la fonctionnalité de paramètre facultatif de C# pour indiquer que le


paramètre numTimes a 1 comme valeur par défaut si aucune valeur n’est passée
pour ce paramètre.
Utilise HtmlEncoder.Default.Encode pour protéger l’application contre les entrées
malveillantes, par exemple via JavaScript.
Utilise des chaînes interpolées dans $"Hello {name}, NumTimes is: {numTimes}" .

Exécutez l’application et accédez à : https://localhost:{PORT}/HelloWorld/Welcome?


name=Rick&numtimes=4 . Remplacez {PORT} par votre numéro de port.
Essayez différentes valeurs pour name et numtimes dans l’URL. Le système de liaison de
données MVC mappe automatiquement les paramètres nommés provenant de la chaîne
de requête aux paramètres de la méthode. Pour plus d’informations, consultez Liaison
de données.

Dans l’image précédente :

Le segment Parameters d’URL n’est pas utilisé.


Les paramètres name et numTimes sont transmis dans la chaîne de requête .
Le ? (point d’interrogation) dans l’URL ci-dessus est un séparateur, qui est suivi
d’une chaîne de requête.
Le caractère & sépare les paires champ-valeur.

Remplacez la méthode Welcome par le code suivant :

C#

public string Welcome(string name, int ID = 1)


{
return HtmlEncoder.Default.Encode($"Hello {name}, ID: {ID}");
}

Exécutez l’application et entrez l’URL suivante : https://localhost:


{PORT}/HelloWorld/Welcome/3?name=Rick

Dans l’URL précédente :

Le troisième segment de l’URL correspondait au paramètre de routage id .


La méthode Welcome contient un paramètre id qui correspondait au modèle
d’URL de la méthode MapControllerRoute .
Le ? de fin démarre la chaîne de requête .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

Dans l’exemple précédent :

Le troisième segment de l’URL correspondait au paramètre de routage id .


La méthode Welcome contient un paramètre id qui correspondait au modèle
d’URL de la méthode MapControllerRoute .
Le ? de fin (dans id? ) indique que le paramètre id est facultatif.

Précédent : Bien démarrer Suivant : Ajouter une vue

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 3, Ajouter une vue à une
application ASP.NET Core MVC
Article • 30/11/2023

Par Rick Anderson

Dans cette section, vous modifiez la classe HelloWorldController pour utiliser des
fichiers de vue Razor. Cela encapsule proprement le processus de génération de
réponses HTML à un client.

Les modèles de vue sont créés avec Razor. Les modèles de vue basés sur Razor :

Ont une extension de fichier .cshtml .


Offrent un moyen élégant pour créer une sortie HTML avec C#.

Actuellement, la méthode Index retourne une chaîne avec un message dans la classe du
contrôleur. Dans la classe HelloWorldController , remplacez la méthode Index par le
code suivant :

C#

public IActionResult Index()


{
return View();
}

Le code précédent :

Appel de la méthode View du contrôleur.


Utilise un modèle de vue pour générer une réponse HTML.

Les méthodes du contrôleur :

Sont appelées méthodes d’action. Par exemple, la méthode d’action Index dans le
code précédent.
Retournent généralement un IActionResult ou une classe dérivée de ActionResult,
et non un type comme string .

Ajouter une vue


Visual Studio
Cliquez avec le bouton droit sur le dossier Vues, cliquez sur Ajouter > Nouveau
dossier, puis nommez le dossier HelloWorld.

Cliquez avec le bouton droit sur le dossier Vues/HelloWorld, puis cliquez sur Ajouter
> Nouvel élément.

Dans la boîte de dialogue Ajouter un nouvel élément, sélectionnez Afficher tous


les modèles.

Dans la boîte de dialogue Ajouter un nouvel élément - MvcMovie :

Dans la zone de recherche située en haut à droite, entrez vue


Sélectionnez Vue Razor - Vide
Conservez la valeur de la zone Nom, Index.cshtml .
Sélectionnez Ajouter

Remplacez le contenu du fichier de vue Views/HelloWorld/Index.cshtml Razor par ce qui


suit :

CSHTML

@{
ViewData["Title"] = "Index";
}

<h2>Index</h2>
<p>Hello from our View Template!</p>

Accédez à https://localhost:{PORT}/HelloWorld :

La méthode Index dans HelloWorldController a exécuté l’instruction return


View(); , laquelle spécifiait que la méthode doit utiliser un fichier de modèle de vue

pour restituer une réponse au navigateur.

Aucun nom de fichier de modèle de vue n’ayant été spécifié, MVC utilise le fichier
d’affichage par défaut. Lorsque le nom du fichier de vue n’est pas spécifié, la vue
par défaut est retournée. Dans cet exemple, la vue par défaut porte le même nom
que la méthode d’action Index . Le modèle de vue
/Views/HelloWorld/Index.cshtml est utilisé.

L’image suivante montre la chaîne « Hello from our View Template! » codée en dur
dans la vue :

Changer les vues et les pages de disposition


Sélectionnez les liens de menu MvcMovie, Home et Privacy. Chaque page affiche la
même disposition de menu. La disposition du menu est implémentée dans le fichier
Views/Shared/_Layout.cshtml .

Ouvrez le fichier Views/Shared/_Layout.cshtml .

Les modèles de disposition permettent de :


Spécification de la disposition de conteneur HTML d’un site à un emplacement.
Application de la disposition de conteneur HTML sur plusieurs pages du site.

Recherchez la ligne @RenderBody() . RenderBody est un espace réservé dans lequel toutes
les pages spécifiques aux vues que vous créez s’affichent, encapsulées dans la page de
disposition. Par exemple, si vous sélectionnez le lien Privacy, la vue
Views/Home/Privacy.cshtml est affichée à l’intérieur de la méthode RenderBody .

Changer le lien de titre, de pied de page et de


menu dans le fichier de disposition
Remplacez le contenu du fichier Views/Shared/_Layout.cshtml par le balisage suivant.
Les modifications apparaissent en surbrillance :

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Movie App</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-
light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Movies"
asp-action="Index">Movie App</a>
<button class="navbar-toggler" type="button" data-bs-
toggle="collapse" data-bs-target=".navbar-collapse" aria-
controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle
navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex
justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-
controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>

<footer class="border-top footer text-muted">


<div class="container">
&copy; 2023 - Movie App - <a asp-area="" asp-controller="Home"
asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Le balisage précédent apporte les modifications suivantes :

Trois occurrences de MvcMovie à Movie App .


Remplacement de l’élément d’ancrage <a class="navbar-brand" asp-area="" asp-
controller="Home" asp-action="Index">MvcMovie</a> par <a class="navbar-brand"

asp-controller="Movies" asp-action="Index">Movie App</a> .

Dans le balisage précédent, l’ asp-area="" attribut Tag Helper d’ancrage et la valeur


d’attribut ont été omis, car cette application n’utilise pas de zones.

Remarque : Le contrôleur Movies n’a pas été implémenté. À ce stade, le lien Movie App
ne fonctionne pas.

Enregistrez les modifications et sélectionnez le lien Privacy. Notez comment le titre de


l’onglet de navigateur affiche Stratégie Privacy - Movie App au lieu de Stratégie Privacy
- MvcMovie
Sélectionnez le lien Home.

Notez que le titre et le texte d’ancrage affichent Movie App. Les changements ont été
apportés dans le modèle de disposition et ont le nouveau texte de lien et le nouveau
titre reflétés sur toutes les pages du site.

Examinez le fichier Views/_ViewStart.cshtml :

CSHTML

@{
Layout = "_Layout";
}

Le fichier Views/_ViewStart.cshtml apporte le fichier Views/Shared/_Layout.cshtml à


chaque vue. La propriété Layout peut être utilisée pour définir un mode de disposition
différent ou lui affecter la valeur null . Aucun fichier de disposition n’est donc utilisé.

Ouvrez le fichier de vue Views/HelloWorld/Index.cshtml .

Modifiez le titre et l’élément <h2> comme indiqué dans ce qui suit :

CSHTML

@{
ViewData["Title"] = "Movie List";
}

<h2>My Movie List</h2>

<p>Hello from our View Template!</p>


Le titre et l’élément <h2> sont légèrement différents afin que vous puissiez voir
clairement quel morceau du code modifie l’affichage.

Dans le code ci-dessus, ViewData["Title"] = "Movie List"; définit la propriété Title


du dictionnaire ViewData sur « Movie List ». La propriété Title est utilisée dans
l’élément HTML <title> dans la page de disposition :

CSHTML

<title>@ViewData["Title"] - Movie App</title>

Enregistrez la modification et accédez à https://localhost:{PORT}/HelloWorld .

Notez que les éléments suivants ont changé :

Titre du navigateur.
Titre principal.
En-têtes secondaires.

S’il n’y a aucune modification dans le navigateur, il peut s’agir du contenu en cache qui
est en cours d’affichage. Appuyez sur Ctrl+F5 dans le navigateur pour forcer le
chargement de la réponse du serveur. Le titre du navigateur est créé avec la valeur
ViewData["Title"] que nous avons définie dans le modèle de vue Index.cshtml et la

chaîne « - Movie App » ajoutée dans le fichier de disposition.

Le contenu du modèle de vue Index.cshtml est fusionné avec le modèle de vue


Views/Shared/_Layout.cshtml . Une réponse HTML unique est envoyée au navigateur. Les

modèles de disposition permettent d’apporter facilement des modifications qui


s’appliquent à toutes les pages d’une application. Pour en savoir plus, consultez
Disposition.
Le petit morceau de « données », le message « Hello from our View Template! », est
toutefois codé en dur. L’application MVC a une vue (« V») et un contrôleur (« C »), mais
pas encore de modèle (« M »).

Passage de données du contrôleur vers la vue


Les actions du contrôleur sont appelées en réponse à une demande d’URL entrante. Une
classe de contrôleur est l’endroit où le code est écrit et qui gère les demandes du
navigateur entrantes. Le contrôleur récupère les données d’une source de données et
détermine le type de réponse à envoyer au navigateur. Il est possible d’utiliser des
modèles de vue à partir d’un contrôleur pour générer et mettre en forme une réponse
HTML au navigateur.

Les contrôleurs sont chargés de fournir les données nécessaires pour qu’un modèle de
vue restitue une réponse.

Les modèles de vue ne doivent pas :

Effectuer une logique métier.


Interagir directement avec une base de données.

Un modèle de vue doit fonctionner uniquement avec les données que le contrôleur lui
fournit. Préserver cette « séparation des intérêts » permet de maintenir le code :

Propre.
Testable.
Maintenable.
Actuellement, la méthode Welcome de la classe HelloWorldController prend un name et
un paramètre ID , puis génère les valeurs directement dans le navigateur.

Au lieu que le contrôleur restitue cette réponse sous forme de chaîne, changez le
contrôleur pour qu’il utilise un modèle de vue à la place. Comme le modèle de vue
génère une réponse dynamique, les données appropriées doivent être passées du
contrôleur à la vue pour générer la réponse. Pour cela, le contrôleur doit placer les
données dynamiques (paramètres) dont le modèle de vue a besoin dans un dictionnaire
ViewData . Le modèle de vue peut ensuite accéder aux données dynamiques.

Dans HelloWorldController.cs , modifiez la méthode Welcome pour ajouter un Message


et une valeur NumTimes au dictionnaire ViewData .

Le dictionnaire ViewData est un objet dynamique, ce qui signifie que n’importe quel
type peut être utilisé. L’objet ViewData ne possède aucune propriété définie tant que
vous ne placez pas d’élément dedans. Le système de liaison de données MVC mappe
automatiquement les paramètres nommés ( name et numTimes ) provenant de la chaîne
de requête aux paramètres de la méthode. Le HelloWorldController complet :

C#

using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;

namespace MvcMovie.Controllers;

public class HelloWorldController : Controller


{
public IActionResult Index()
{
return View();
}
public IActionResult Welcome(string name, int numTimes = 1)
{
ViewData["Message"] = "Hello " + name;
ViewData["NumTimes"] = numTimes;
return View();
}
}

L’objet dictionnaire ViewData contient des données qui seront passées à la vue.

Créez un modèle de vue d’accueil nommé Views/HelloWorld/Welcome.cshtml .

Vous allez créer une boucle dans le modèle de vue Welcome.cshtml qui affiche « Hello »
NumTimes . Remplacez le contenu de Views/HelloWorld/Welcome.cshtml par ce qui suit :
CSHTML

@{
ViewData["Title"] = "Welcome";
}

<h2>Welcome</h2>

<ul>
@for (int i = 0; i < (int)ViewData["NumTimes"]!; i++)
{
<li>@ViewData["Message"]</li>
}
</ul>

Enregistrez vos modifications et accédez à l’URL suivante :

https://localhost:{PORT}/HelloWorld/Welcome?name=Rick&numtimes=4

Les données sont extraites de l’URL et passées au contrôleur à l’aide du classeur de


modèles MVC. Le contrôleur empaquette les données dans un dictionnaire ViewData et
passe cet objet à la vue. La vue restitue ensuite les données au format HTML dans le
navigateur.

Dans l’exemple précédent, le dictionnaire ViewData a été utilisé pour passer des
données du contrôleur à une vue. Plus loin dans ce didacticiel, un modèle de vue est
utilisé pour passer les données d’un contrôleur à une vue. L’approche basée sur le
modèle de vue pour passer des données est préférée à l’approche basée sur le
dictionnaire ViewData .

Dans le didacticiel suivant, une base de données de films est créée.

Précédent : Ajouter un contrôleur Suivant : Ajouter un modèle


6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 4, ajouter un modèle dans une
application ASP.NET Core MVC
Article • 05/01/2024

Par Rick Anderson et Jon P Smith .

Dans ce tutoriel, des classes sont ajoutées pour la gestion des films dans une base de
données. Ces classes constituent la partie « Modèle » de l’application MVC.

Ces classes de modèle sont utilisées avec Entity Framework Core (EF Core) pour utiliser
une base de données. EF Core est une infrastructure de mappage relationnel d’objets
qui simplifie le code d’accès aux données à écrire.

Les classes du modèle créées sont appelées classes POCO, à partir de Plain Old CLR
Objects. Les classes POCO n’ont aucune dépendance vis-à-vis de EF Core. Elles
définissent uniquement les propriétés des données qui seront stockées dans la base de
données.

Dans ce tutoriel, les classes du modèle sont d’abord créées, puis EF Core crée la base de
données.

Ajouter une classe de modèle de données


Visual Studio

Cliquez avec le bouton droit sur le dossier Modèles>Ajouter>Classe. Nommez le


fichier Movie.cs .

Mettez à jour le fichier Models/Movie.cs avec le code suivant :

C#

using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
public string? Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
public decimal Price { get; set; }
}

La classe Movie contient un champ Id , qui est nécessaire à la base de données pour la
clé primaire.

L’attribut DataType de ReleaseDate spécifie le type de données ( Date ). Avec cet


attribut :

L’utilisateur n’est pas obligé d’entrer les informations de temps dans le champ de
date.
Seule la date est affichée, pas les informations de temps.

Les DataAnnotations sont traitées dans un prochain didacticiel.

La présence du point d’interrogation après string indique que la propriété peut


accepter les valeurs Null. Pour plus d’informations, consultez Types référence pouvant
accepter la valeur Null.

Ajouter des packages NuGet


Visual Studio

Visual Studio installe automatiquement les packages requis.

Générez le projet en tant que vérification des erreurs du compilateur.

Générer automatiquement des pages de film


Utilisez l’outil de génération de modèles automatique pour Create , Read , Update , et
Delete des pages du modèle de film.

Visual Studio

Cliquez avec le bouton droit sur le dossier Contrôleurs dans l’Explorateur de


solutions, puis sélectionnez Ajouter > Nouvel élément généré automatiquement.
Dans la boîte de dialogue Ajouter un nouvel élément de modèle généré
automatiquement :

Dans le volet gauche, sélectionnez Installé>Communes>MVC.


Sélectionnez Contrôleur MVC avec vues, utilisant Entity Framework.
Cliquez sur Ajouter.
Terminez la boîte de dialogue Ajouter un contrôleur MVC avec des vues, à l’aide
d’Entity Framework :

Dans la liste déroulante Classe du modèle, sélectionnez Film


(MvcMovie.Models).
Dans la ligne Classe du contexte de données, sélectionnez le signe + (plus).
Dans la boîte de dialogue Ajouter un contexte de données, le nom de
classe MvcMovie.Data.MvcMovieContext est généré.
Cliquez sur Ajouter.
Dans la liste déroulante Fournisseur de base de données, sélectionnez SQL
Server.
Vues et Nom du contrôleur : conservez la valeur par défaut.
Cliquez sur Ajouter.
Si vous obtenez un message d’erreur, sélectionnez Ajouter une deuxième fois pour
réessayer.

La génération de modèles automatique ajoute les packages suivants :

Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools

Microsoft.VisualStudio.Web.CodeGeneration.Design

La génération automatique crée les éléments suivants :

Un contrôleur de films : Controllers/MoviesController.cs


Razor afficher les fichiers pour les pages Créer, Supprimer, Détails, Modifier
et Indexer : Views/Movies/*.cshtml
Une classe de contexte de base de données : Data/MvcMovieContext.cs

La génération de modèles automatique met à jour les éléments suivants :

Insère les références de package requises dans le fichier projet


MvcMovie.csproj .

Inscrit le contexte de base de données dans le fichier Program.cs .


Ajoute une chaîne de connexion de base de données au fichier
appsettings.json .
La création automatique de ces fichiers et mises à jour est appelée génération de
modèles automatique.

Les pages générées automatiquement ne peuvent pas encore être utilisées, car la
base de données n’existe pas. L’exécution de l’application et la sélection du lien
Application vidéo entraînent un message d’erreur Impossible d’ouvrir la base de
données ou Le type de table suivant n’existe pas : Film.

Générez l’application pour vérifier qu’il n’y a pas d’erreurs.

Migration initiale
Utilisez la fonctionnalité EF CoreMigrations pour créer la base de données. La
fonctionnalité Migrations est un ensemble d’outils qui vous permettent de créer et de
mettre à jour une base de données pour qu’elle corresponde à votre modèle de
données.

Visual Studio

Dans le menu Outils, sélectionnez Gestionnaire de package NuGet>Console du


gestionnaire de package.

Dans la console du Gestionnaire de package, entrez les commandes suivantes :

PowerShell

Add-Migration InitialCreate
Update-Database

Add-Migration InitialCreate : Génère un fichier de migration

Migrations/{timestamp}_InitialCreate.cs . L’argument InitialCreate est le

nom de la migration. Vous pouvez utiliser n’importe quel nom, mais par
convention, un nom décrivant la migration est sélectionné. Étant donné qu’il
s’agit de la première migration, la classe générée contient du code permettant
de créer le schéma de la base de données. Le schéma de base de données est
basé sur le modèle spécifié dans la classe MvcMovieContext .

Update-Database : Met à jour la base de données vers la dernière migration,

qui a été créée par la commande précédente. Cette commande exécute la


méthode Up dans le fichier Migrations/{time-stamp}_InitialCreate.cs , qui
crée la base de données.
La commande Update-Database génère l’avertissement suivant :

Aucun type de magasin n’a été spécifié pour la propriété décimale « Price » sur
le type d’entité « Movie ». Les valeurs sont tronquées en mode silencieux si
elles ne sont pas compatibles avec la précision et l’échelle par défaut. Spécifiez
explicitement le type de colonne SQL Server qui peut prendre en charge toutes
les valeurs dans « OnModelCreating » à l’aide de « HasColumnType », spécifiez
la précision et l’échelle à l’aide de « HasPrecision » ou configurez un
convertisseur de valeurs à l’aide de « HasConversion ».

Ignorez l’avertissement précédent, il est résolu dans un tutoriel ultérieur.

Pour plus d’informations sur les outils de la console du gestionnaire de package


pour EF Core, consultez EF Core référence sur les outils - Console du gestionnaire
de package dans Visual Studio.

Tester l’application
Visual Studio

Exécutez l’application et sélectionnez sur le lien Movie App.

Si vous obtenez une exception semblable à ce qui suit, vous avez peut-être manqué
la commande Update-Database à l’étape des migrations :

Console

SqlException: Cannot open database "MvcMovieContext-1" requested by the


login. The login failed.

7 Notes

Vous ne pourrez peut-être pas entrer de virgules décimales dans le champ Price .
Pour prendre en charge la validation jQuery pour les paramètres régionaux
autres que « Anglais » qui utilisent une virgule (« , ») comme décimale et des
formats de date autres que le format « Anglais (États-Unis »), l’application doit être
localisée. Pour obtenir des instructions sur la localisation, consultez ce problème
GitHub .
Examinez la classe de contexte de base de données
générée et l’inscription
Avec EF Core, l’accès aux données est effectué à l’aide d’un modèle. Un modèle est
constitué de classes d’entité et d’un objet de contexte qui représente une session avec
la base de données. L’objet de contexte permet l’interrogation et l’enregistrement des
données. Le contexte de base de données est dérivé de
Microsoft.EntityFrameworkCore.DbContext et il spécifie les entités à inclure dans le
modèle de données.

La génération de modèles crée la classe Data/MvcMovieContext.cs de contexte de base


de données :

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MvcMovie.Models;

namespace MvcMovie.Data
{
public class MvcMovieContext : DbContext
{
public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
: base(options)
{
}

public DbSet<MvcMovie.Models.Movie> Movie { get; set; }


}
}

Le code précédent crée une propriété DbSet<Movie> qui représente les films dans la
base de données.

Injection de dépendances
ASP.NET Core comprend l’injection de dépendances (DI). Les services, tels que le
contexte de base de données, sont inscrits avec une injection de dépendance dans
Program.cs . Ces services sont fournis aux composants qui en ont besoin via des

paramètres de constructeur.
Dans le fichier Controllers/MoviesController.cs , le constructeur utilise une injection de
dépendance pour injecter le contexte de base de données MvcMovieContext dans le
contrôleur. Le contexte de base de données est utilisé dans chacune des méthodes la
CRUD du contrôleur.

La génération de modèles a généré le code en surbrillance suivant dans Program.cs :

Visual Studio

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));

Le système de configuration ASP.NET Core lit la chaîne de connexion de base de


données « MvcMovieContext ».

Examinez la chaîne de connexion de base de données


générée
La génération de modèles a ajouté une chaîne de connexion au fichier
appsettings.json :

Visual Studio

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Data Source=MvcMovieContext-ea7a4069-f366-4742-
bd1c-3f753a804ce1.db"
}
}

Pour le développement local, le système de configuration ASP.NET Core lit la clé


ConnectionString depuis le fichier appsettings.json .

La classe InitialCreate
Examinez le fichier de migration Migrations/{timestamp}_InitialCreate.cs :

C#

using System;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace MvcMovie.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Movie",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)",
nullable: true),
ReleaseDate = table.Column<DateTime>(type: "datetime2",
nullable: false),
Genre = table.Column<string>(type: "nvarchar(max)",
nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)",
nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Movie", x => x.Id);
});
}

protected override void Down(MigrationBuilder migrationBuilder)


{
migrationBuilder.DropTable(
name: "Movie");
}
}
}

Dans le code précédent :

La méthode InitialCreate.Up crée la table Film et configure Id comme la clé


primaire.
InitialCreate.Down rétablit les modifications de schéma provoquées par la

migration Up .

Injection de dépendances dans le contrôleur


Ouvrez le fichier Controllers/MoviesController.cs et examinez le constructeur :

C#

public class MoviesController : Controller


{
private readonly MvcMovieContext _context;

public MoviesController(MvcMovieContext context)


{
_context = context;
}

Le constructeur utilise une injection de dépendance pour injecter le contexte de base de


données ( MvcMovieContext ) dans le contrôleur. Le contexte de base de données est
utilisé dans chacune des méthodes la CRUD du contrôleur.

Testez la page Create. Entrez et envoyez des données.

Testez les pages Edit, Details et Delete.

Modèles fortement typés et directive @model


Plus tôt dans ce didacticiel, vous avez vu comment un contrôleur peut passer des
données ou des objets à une vue en utilisant le dictionnaire ViewData . Le dictionnaire
ViewData est un objet dynamique qui fournit un moyen pratique d’effectuer une liaison

tardive pour passer des informations à une vue.

Le modèle MVC fournit la possibilité de passer des objets de modèle fortement typés à
une vue. Cette approche fortement typée permet de vérifier votre code au moment de
la compilation. Le mécanisme de génération de modèles a passé un modèle fortement
typé dans la classe et les vues MoviesController .

Examinez la méthode Details générée dans le fichier


Controllers/MoviesController.cs :

C#

// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie


.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}

return View(movie);
}

Le paramètre id est généralement passé en tant que données de routage. Par exemple,
https://localhost:5001/movies/details/1 définit :

Le contrôleur sur le contrôleur movies , le premier segment de l’URL.


L’action sur details , le deuxième segment de l’URL.
Le id à 1, le dernier segment d’URL.

Le id peut être transmis avec une chaîne de requête, comme dans l’exemple suivant :

https://localhost:5001/movies/details?id=1

Le paramètre id est défini comme type Nullable ( int? ) au cas où la valeur id n’est pas
fournie.

Une expression lambda est passée à la méthode FirstOrDefaultAsync pour sélectionner


les entités de film qui correspondent aux données de routage ou à la valeur de la chaîne
de requête.

C#
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);

Si un film est trouvé, une instance du modèle Movie est passée à la vue Details :

C#

return View(movie);

Examinez le contenu du fichier Views/Movies/Details.cshtml :

CSHTML

@model MvcMovie.Models.Movie

@{
ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
<h4>Movie</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Genre)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>

L’instruction @model située en haut du fichier de la vue spécifie le type d’objet attendu
par la vue. Lorsque le contrôleur de film était créé, l’instruction @model suivante était
incluse :

CSHTML

@model MvcMovie.Models.Movie

Cette directive @model autorise l’accès au film que le contrôleur a passé à la vue. L’objet
Model est fortement typé. Par exemple, dans la vue Details.cshtml , le code passe

chaque champ du film aux Helpers HTML DisplayNameFor et DisplayFor avec l’objet
Model fortement typé. Les méthodes et les vues Create et Edit passent aussi un objet

du modèle Movie .

Examinez la vue Index.cshtml et la méthode Index dans le contrôleur Movies. Notez


comment le code crée un objet List quand il appelle la méthode View . Le code passe
cette liste Movies de la méthode d’action Index à la vue :

C#

// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}

Le code retourne les détails du problème si la propriété Movie du contexte de données


est null.

Lors de la création du contrôleur du film, la génération de modèles automatique a inclus


l’instruction @model suivante en haut du fichier Index.cshtml :

CSHTML

@model IEnumerable<MvcMovie.Models.Movie>
La directive @model permet d’accéder à la liste des films que le contrôleur a passé à la
vue en utilisant un objet Model qui est fortement typé. Par exemple, dans la vue
Index.cshtml , le code boucle dans les films avec une instruction foreach sur l’objet
Model fortement typé :

CSHTML

@model IEnumerable<MvcMovie.Models.Movie>

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a>
|
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Comme l’objet Model est fortement typé en tant qu’objet IEnumerable<Movie> , chaque
élément de la boucle est typé en tant que Movie . Entre autres avantages, le compilateur
valide les types utilisés dans le code.

Ressources supplémentaires
Entity Framework Core pour les débutants
Tag Helpers
Globalisation et localisation

Précédent : Ajout d’une vue Suivant : Utilisation de SQL

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Partie 5, Utilisation d’une base de
données dans une application ASP.NET
Core MVC
Article • 30/11/2023

Par Rick Anderson et Jon P Smith .

L’objet MvcMovieContext gère la tâche de connexion à la base de données et de


mappage d’objets Movie à des enregistrements de la base de données. Le contexte de
base de données est inscrit auprès du conteneur d’injection de dépendances dans le
fichier Program.cs :

Visual Studio

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));

Le système de configuration d’ASP.NET Core lit la clé ConnectionString . Pour un


développement local, il obtient la chaîne de connexion à partir du fichier
appsettings.json .

JSON

"ConnectionStrings": {
"MvcMovieContext": "Data Source=MvcMovieContext-ea7a4069-f366-4742-
bd1c-3f753a804ce1.db"
}

Quand l’application est déployée sur un serveur de test ou de production, une variable
d’environnement peut être utilisée pour définir la chaîne de connexion à un serveur SQL
Server de production. Pour plus d’informations, consultez Configuration.

Visual Studio
Base de données locale SQL Server Express
Base de données locale :

Version légère du moteur de base de données SQL Server Express, installée


par défaut avec Visual Studio.
Démarre à la demande à l’aide d’une chaîne de connexion.
Est destiné au développement de programmes. Elle s’exécute en mode
utilisateur, ce qui n’implique aucune configuration complexe.
Par défaut, crée des fichiers .mdf dans le répertoire C:/Users/{utilisateur}.

Examiner la base de données


Dans le menu Affichage, ouvrez l’Explorateur d’objets SQL Server (SSOX).

Cliquez avec le bouton droit sur la table Movie ( dbo.Movie ) >Concepteur de vues
Notez l’icône de clé en regard de ID . Par défaut, EF fait d’une propriété nommée
ID la clé primaire.

Cliquez avec le bouton droit sur la table Movie > Afficher les données.
Amorcer la base de données
Créez une classe nommée SeedData dans l’espace de noms Modèles. Remplacez le code
généré par ce qui suit :

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MvcMovie.Data;
using System;
using System.Linq;

namespace MvcMovie.Models;

public static class SeedData


{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MvcMovieContext(
serviceProvider.GetRequiredService<
DbContextOptions<MvcMovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return; // DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}

Si la base de données contient des films, l’initialiseur de valeur initiale retourne une
valeur et aucun film n’est ajouté.

C#

if (context.Movie.Any())
{
return; // DB has been seeded.
}

Ajouter l’initialiseur de valeur initiale

Visual Studio

Remplacez le contenu de Program.cs par le code suivant. Le nouveau code est mis
en évidence.

C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MvcMovie.Data;
using MvcMovie.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MvcMovieContext>(options =>

options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));

// Add services to the container.


builder.Services.AddControllersWithViews();

var app = builder.Build();


using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;

SeedData.Initialize(services);
}

// Configure the HTTP request pipeline.


if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Supprimez tous les enregistrements de la base de données. Pour ce faire, utilisez les
liens de suppression disponibles dans le navigateur ou à partir de SSOX.

Testez l’application. Forcez l’application à s’initialiser en appelant les méthodes de la


classe Program.cs , pour que la méthode seed s’exécute. Pour forcer l’initialisation,
fermez la fenêtre d’invite de commandes que Visual Studio a ouverte, puis
redémarrez en appuyant sur Ctrl+F5.

L’application affiche les données de valeurs de départ.


Précédent : Ajout d’un modèle

Suivant : Ajout de méthodes et de vues de contrôleur

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 6, Méthodes et vues de
contrôleur dans ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Nous avons une bonne ébauche de l’application de films, mais sa présentation n’est pas
idéale, par exemple, ReleaseDate devrait être écrit en deux mots.

Ouvrez le fichier Models/Movie.cs et ajoutez les lignes en surbrillance ci-dessous :

C#

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}

Les DataAnnotations sont expliquées dans le tutoriel suivant. L’attribut Display spécifie
les éléments à afficher pour le nom d’un champ (dans le cas présent, « Release Date » au
lieu de « ReleaseDate »). L’attribut DataType spécifie le type des données (Date). Les
informations d’heures stockées dans le champ ne s’affichent donc pas.

L’annotation de données [Column(TypeName = "decimal(18, 2)")] est nécessaire pour


qu’Entity Framework Core puisse correctement mapper Price en devise dans la base de
données. Pour plus d’informations, consultez Types de données.

Accédez au contrôleur Movies et maintenez le pointeur de la souris sur un lien Edit pour
afficher l’URL cible.

Les liens Edit, Details et Delete sont générés par le Tag Helper d’ancre Core MVC dans le
fichier Views/Movies/Index.cshtml .

CSHTML
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>

Les Tag Helpers permettent au code côté serveur de participer à la création et au rendu
des éléments HTML dans les fichiers Razor. Dans le code ci-dessus, AnchorTagHelper
génère dynamiquement la valeur de l’attribut HTML href à partir de la méthode
d’action du contrôleur et de l’ID de route. Vous utilisez Afficher la source à partir de
votre navigateur favori ou utilisez les outils de développement pour examiner le
balisage généré. Une partie du code HTML généré est affichée ci-dessous :

HTML

<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>

Rappelez-vous le format du routage défini dans le fichier Program.cs :

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core traduit https://localhost:5001/Movies/Edit/4 en une requête à la


méthode d’action Edit du contrôleur Movies avec un paramètre Id de 4. (Les
méthodes de contrôleur sont également appelées méthodes d’action.)

Les Tag Helpers sont l’une des nouvelles fonctionnalités les plus populaires dans
ASP.NET Core. Pour plus d'informations, consultez Ressources supplémentaires.

Ouvrez le contrôleur Movies et examinez les deux méthodes d’action Edit . Le code
suivant montre la méthode HTTP GET Edit , qui extrait le film et renseigne le formulaire
de modification généré par le fichier Razor Edit.cshtml .

C#

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie.FindAsync(id);


if (movie == null)
{
return NotFound();
}
return View(movie);
}

Le code suivant montre la méthode HTTP POST Edit , qui traite les valeurs de film
publiées :

C#

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you
want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id,
[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}

L’attribut [Bind] est l’un des moyens qui permettent d’assurer une protection contre la
sur-publication. Vous devez inclure dans l’attribut [Bind] uniquement les propriétés que
vous souhaitez modifier. Pour plus d’informations, consultez Protéger votre contrôleur
contre la sur-publication. Les ViewModels fournissent une alternative pour empêcher
la sur-publication.

Notez que la deuxième méthode d’action Edit est précédée de l’attribut [HttpPost] .

C#

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you
want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id,
[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
L’attribut HttpPost indique que cette méthode Edit peut être appelée uniquement pour
les requêtes POST . Vous pouvez appliquer l’attribut [HttpGet] à la première méthode
Edit, mais cela n’est pas nécessaire car [HttpGet] est la valeur par défaut.

L’attribut ValidateAntiForgeryToken est utilisé pour lutter contre la falsification d’une


demande. Il est associé à un jeton anti-contrefaçon généré dans le fichier de la vue Edit
( Views/Movies/Edit.cshtml ). Le fichier de la vue Edit génère le jeton anti-contrefaçon
avec le Tag Helper Form.

CSHTML

<form asp-action="Edit">

Le Tag Helper Form génère un jeton anti-contrefaçon masqué qui doit correspondre au
jeton anti-contrefaçon généré par [ValidateAntiForgeryToken] dans la méthode Edit
du contrôleur Movies. Pour plus d’informations, consultez Prévenir les attaques par
falsification de requête intersites (XSRF/CSRF) dans ASP.NET Core.

La méthode HttpGet Edit prend le paramètre ID du film, recherche le film à l’aide de la


méthode Entity Framework FindAsync , et retourne le film sélectionné à la vue Edit. Si un
film est introuvable, l’erreur NotFound (HTTP 404) est retournée.

C#

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie.FindAsync(id);


if (movie == null)
{
return NotFound();
}
return View(movie);
}

Quand le système de génération de modèles automatique a créé la vue Edit, il a


examiné la classe Movie et a créé le code pour restituer les éléments <label> et <input>
de chaque propriété de la classe. L’exemple suivant montre la vue Edit qui a été générée
par le système de génération de modèles automatique de Visual Studio :
CSHTML

@model MvcMovie.Models.Movie

@{
ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>

<div>
<a asp-action="Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Notez que le modèle de vue comporte une instruction @model MvcMovie.Models.Movie en
haut du fichier. @model MvcMovie.Models.Movie indique que la vue s’attend à ce que le
modèle pour le modèle de vue soit de type Movie .

Le code de génération de modèles automatique utilise plusieurs méthodes Tag Helper


afin de rationaliser le balisage HTML. Le Tag Helper Label affiche le nom du champ
(« Title », « ReleaseDate », « Genre » ou « Price »). Le Tag Helper Input affiche l’élément
<input> HTML. Le Tag Helper Validation affiche les messages de validation associés à

cette propriété.

Exécutez l’application et accédez à l’URL /Movies . Cliquez sur un lien Edit. Dans le
navigateur, affichez la source de la page. Le code HTML généré pour l’élément <form>
est indiqué ci-dessous.

HTML

<form action="/Movies/Edit/7" method="post">


<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field
is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre"
name="Genre" value="Western" />
<span class="text-danger field-validation-valid" data-
valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true"
data-val-number="The field Price must be a number." data-val-required="The
Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" data-
valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden"
value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmq
UyXnJBXhmrjcUVDJyDUMm7-
MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Les éléments <input> sont dans un élément HTML <form> dont l’attribut action est
défini de façon à publier à l’URL /Movies/Edit/id . Les données du formulaire sont
publiées au serveur en cas de clic sur le bouton Save . La dernière ligne avant l’élément
</form> de fermeture montre le jeton XSRF masqué généré par le Tag Helper Form.

Traitement de la requête POST


Le code suivant montre la version [HttpPost] de la méthode d’action Edit .

C#

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you
want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id,
[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}

if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}

L’attribut [ValidateAntiForgeryToken] valide le jeton XSRF masqué généré par le


générateur de jetons anti-contrefaçon dans le Tag Helper Form

Le système de liaison de modèle prend les valeurs de formulaire publiées et crée un


objet Movie qui est passé en tant que paramètre movie . La propriété
ModelState.IsValid vérifie que les données envoyées dans le formulaire peuvent être

utilisées pour changer (modifier ou mettre à jour) un objet Movie . Si les données sont
valides, elles sont enregistrées. Les données de film mises à jour (modifiées) sont
enregistrées dans la base de données en appelant la méthode SaveChangesAsync du
contexte de base de données. Après avoir enregistré les données, le code redirige
l’utilisateur vers la méthode d’action Index de la classe MoviesController , qui affiche la
collection de films, avec notamment les modifications qui viennent d’être apportées.

Avant que le formulaire soit publié sur le serveur, la validation côté client vérifie les
règles de validation sur les champs. En cas d’erreur de validation, un message d’erreur
s’affiche et le formulaire n’est pas publié. Si JavaScript est désactivé, aucune validation
côté client n’est effectuée, mais le serveur détecte les valeurs publiées qui ne sont pas
valides, et les valeurs de formulaire sont réaffichées avec des messages d’erreur. Plus
loin dans ce didacticiel, nous examinerons la Validation du modèle plus en détail. Le Tag
Helper Validation dans le modèle de vue Views/Movies/Edit.cshtml se charge de
l’affichage des messages d’erreur appropriés.
Toutes les méthodes HttpGet du contrôleur Movies suivent un modèle similaire. Elles
reçoivent un objet de film (ou une liste d’objets, dans le cas de Index ) et passent l’objet
(modèle) à la vue. La méthode Create passe un objet de film vide à la vue Create .
Toutes les méthodes qui créent, modifient, suppriment ou changent d’une quelconque
manière des données le font dans la surcharge [HttpPost] de la méthode. Modifier des
données dans une méthode HTTP GET présente un risque pour la sécurité. La
modification des données dans une méthode HTTP GET enfreint également les bonnes
pratiques HTTP et le modèle architectural REST , qui spécifie que les demandes GET ne
doivent pas changer l’état de votre application. En d’autres termes, une opération GET
doit être sûre, ne doit avoir aucun effet secondaire et ne doit pas modifier vos données
persistantes.

Ressources supplémentaires
Globalisation et localisation
Introduction aux Tag Helpers
Créer des Tag Helpers
Éviter les attaques de falsification de demande intersites (XSRF/CSRF) dans
ASP.NET Core
Protéger votre contrôleur contre la sur-publication
ViewModels
Tag Helper de formulaire
Tag Helper Input
Tag Helper Label
Tag Helper de sélection
Tag Helper Validation

Précédent Suivant

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 7, ajouter la recherche à une
application MVC ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Dans cette section, vous ajoutez une fonctionnalité de recherche à la méthode d’action
Index qui vous permet de rechercher des films par genre ou par nom.

Mettez à jour la méthode Index trouvée dans Controllers/MoviesController.cs avec le


code suivant :

C#

public async Task<IActionResult> Index(string searchString)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


select m;

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}

return View(await movies.ToListAsync());


}

La ligne suivante dans la méthode d’action Index crée une requête LINQ pour
sélectionner les films :

C#

var movies = from m in _context.Movie


select m;

La requête est seulement définie à ce stade ; elle n’a pas été exécutée sur la base de
données.

Si le paramètre searchString contient une chaîne, la requête de films est modifiée de


façon à filtrer sur la valeur de la chaîne de recherche :
C#

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}

Le code s => s.Title!.Contains(searchString) ci-dessus est une expression lambda.


Les expressions lambda sont utilisées dans les requêtes LINQ basées sur une méthode
en tant qu’arguments pour les méthodes d’opérateur de requête standard, comme la
méthode Where ou Contains (utilisées dans le code ci-dessus). Les requêtes LINQ ne
sont pas exécutées quand elles sont définies ou quand elles sont modifiées en appelant
une méthode, comme Where , Contains ou OrderBy . Au lieu de cela, l’exécution de la
requête est différée. Cela signifie que l’évaluation d’une expression est retardée jusqu’à
ce que sa valeur réalisée fasse l’objet d’une itération réelle ou que la méthode
ToListAsync soit appelée. Pour plus d’informations sur l’exécution différée des requêtes,

consultez Exécution de requête.

Remarque : La méthode Contains est exécutée sur la base de données, et non pas dans
le code C# ci-dessus. Le respect de la casse pour la requête dépend de la base de
données et du classement. Sur SQL Server, Contains est mappée à SQL LIKE, qui ne
respecte pas la casse. Dans SQLite, avec le classement par défaut, elle respecte la casse.

Accédez à /Movies/Index . Ajoutez une chaîne de requête comme ?searchString=Ghost à


l’URL. Les films filtrés sont affichés.
Si vous changez la signature de la méthode Index pour y inclure un paramètre nommé
id , le paramètre id correspondra à l’espace réservé facultatif {id} pour les routes par

défaut définies dans Program.cs .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

Remplacez le paramètre par id et toutes les occurrences de searchString par id .

La méthode Index précédente :

C#

public async Task<IActionResult> Index(string searchString)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


select m;

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}

return View(await movies.ToListAsync());


}

La méthode Index mise à jour avec le paramètre id :

C#

public async Task<IActionResult> Index(string id)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

var movies = from m in _context.Movie


select m;

if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title!.Contains(id));
}

return View(await movies.ToListAsync());


}

Vous pouvez maintenant passer le titre de la recherche en tant que données de routage
(un segment de l’URL) et non pas en tant que valeur de chaîne de requête.

Cependant, vous ne pouvez pas attendre des utilisateurs qu’ils modifient l’URL à chaque
fois qu’ils veulent rechercher un film. Vous allez donc maintenant ajouter des éléments
d’interface utilisateur pour les aider à filtrer les films. Si vous avez changé la signature de
la méthode Index pour tester comment passer le paramètre ID lié à une route,
rétablissez-la de façon à ce qu’elle prenne un paramètre nommé searchString :

C#

public async Task<IActionResult> Index(string searchString)


{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;

if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}

return View(await movies.ToListAsync());


}

Ouvrez le fichier Views/Movies/Index.cshtml et ajoutez la balise <form> mise en


surbrillance ci-dessous :

CSHTML

@model IEnumerable<MvcMovie.Models.Movie>

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index">


<p>
Title: <input type="text" name="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">

La balise HTML <form> utilise le Tag Helper de formulaire, de façon que quand vous
envoyez le formulaire, la chaîne de filtrage soit envoyée à l’action Index du contrôleur
de films. Enregistrez vos modifications puis testez le filtre.
Contrairement à ce que vous pourriez penser, une surcharge de [HttpPost] dans la
méthode Index n’est pas nécessaire. Vous n’en avez pas besoin, car la méthode ne
change pas l’état de l’application, elle filtre seulement les données.

Vous pourriez ajouter la méthode [HttpPost] Index suivante.

C#

[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}

Le paramètre notUsed est utilisé pour créer une surcharge pour la méthode Index . Nous
parlons de ceci plus loin dans le didacticiel.

Si vous ajoutez cette méthode, le demandeur de l’action correspondrait à la méthode


[HttpPost] Index et la méthode [HttpPost] Index s’exécuterait comme indiqué dans

l’image ci-dessous.
Cependant, même si vous ajoutez cette version [HttpPost] de la méthode Index , il
existe une limitation dans la façon dont tout ceci a été implémenté. Imaginez que vous
voulez insérer un signet pour une recherche spécifique, ou que vous voulez envoyer un
lien à vos amis sur lequel ils peuvent cliquer pour afficher la même liste filtrée de films.
Notez que l’URL de la requête HTTP POST est identique à l’URL de la requête GET
(localhost:{PORT}/Movies/Index) : il n’existe aucune information de recherche dans l’URL.
Les informations de la chaîne de recherche sont envoyées au serveur en tant que valeur
d’un champ de formulaire . Vous pouvez vérifier ceci avec les outils de développement
du navigateur ou avec l’excellent outil Fiddler . L’illustration ci-dessous montre les
outils de développement du navigateur Chrome :
Vous pouvez voir le paramètre de recherche et le jeton XSRF dans le corps de la
demande. Notez que, comme indiqué dans le didacticiel précédent, le Tag Helper de
formulaire génère un jeton XSRF anti-contrefaçon. Nous ne modifions pas les données :
nous n’avons donc pas besoin de valider le jeton dans la méthode du contrôleur.

Comme le paramètre de recherche se trouve dans le corps de la demande et pas dans


l’URL, vous ne pouvez pas capturer ces informations de recherche pour les insérer dans
un signet ou les partager avec d’autres personnes. Nous résolvons ce problème en
spécifiant que la requête doit être HTTP GET trouvable dans le fichier
Views/Movies/Index.cshtml .

CSHTML

@model IEnumerable<MvcMovie.Models.Movie>

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-action="Create">Create New</a>
</p>

<form asp-controller="Movies" asp-action="Index" method="get">


<p>
Title: <input type="text" name="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">

Maintenant, quand vous soumettez une recherche, l’URL contient la chaîne de requête
de la recherche. La recherche accède également à la méthode d’action HttpGet Index ,
même si vous avez une méthode HttpPost Index .
La mise en forme suivante montre la modification apportée à la balise form :

CSHTML

<form asp-controller="Movies" asp-action="Index" method="get">

Ajouter la recherche par genre


Ajoutez la classe MovieGenreViewModel suivante au dossier Models :

C#

using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;

namespace MvcMovie.Models;

public class MovieGenreViewModel


{
public List<Movie>? Movies { get; set; }
public SelectList? Genres { get; set; }
public string? MovieGenre { get; set; }
public string? SearchString { get; set; }
}

Le modèle de vue movie-genre contiendra :

Une liste de films.


Une SelectList contenant la liste des genres. Cela permet à l’utilisateur de
sélectionner un genre dans la liste.
MovieGenre , qui contient le genre sélectionné.

SearchString , qui contient le texte que les utilisateurs entrent dans la zone de

texte de recherche.

Remplacez la méthode Index dans MoviesController.cs par le code suivant :

C#

// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string
searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}

// Use LINQ to get list of genres.


IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;

if (!string.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}

if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}

var movieGenreVM = new MovieGenreViewModel


{
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Movies = await movies.ToListAsync()
};

return View(movieGenreVM);
}
Le code suivant est une requête LINQ qui récupère tous les genres de la base de
données.

C#

// Use LINQ to get list of genres.


IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;

La SelectList de genres est créée en projetant les genres distincts (nous ne voulons pas
que notre liste de sélection ait des genres en doublon).

Quand l’utilisateur recherche l’élément, la valeur de recherche est conservée dans la


zone de recherche.

Ajouter la recherche par genre à la vue Index


Mettez à jour Index.cshtml trouvé dans Views/Movies/ comme suit :

CSHTML

@model MvcMovie.Models.MovieGenreViewModel

@{
ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>

<select asp-for="MovieGenre" asp-items="Model.Genres">


<option value="">All</option>
</select>

Title: <input type="text" asp-for="SearchString" />


<input type="submit" value="Filter" />
</p>
</form>

<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies!)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-
id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-
id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Examinez l’expression lambda utilisée dans le HTML Helper suivant :

@Html.DisplayNameFor(model => model.Movies![0].Title)

Dans le code précédent, le Helper HTML DisplayNameFor inspecte la propriété Title


référencée dans l’expression lambda pour déterminer le nom d’affichage. Comme
l’expression lambda est inspectée et non pas évaluée, vous ne recevez pas de violation
d’accès quand model , model.Movies ou model.Movies[0] sont null ou vides. Quand
l’expression lambda est évaluée (par exemple @Html.DisplayFor(modelItem =>
item.Title) ), les valeurs des propriétés du modèle sont évaluées. ! après model.Movies

est l’opérateur null-forgiving, qui est utilisé pour déclarer qui Movies n’est pas nul.

Testez l’application en effectuant une recherche par genre, par titre de film et selon ces
deux critères :

Précédent Suivant

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 8, ajouter un nouveau champ à
une application ASP.NET Core MVC
Article • 30/11/2023

Par Rick Anderson

Dans cette section, Migrations Entity Framework Code First est utilisé pour :

Ajouter un nouveau champ au modèle.


Migrer le nouveau champ vers la base de données.

Quand EF Code First est utilisé pour créer automatiquement une base de données, Code
First :

Ajoute une table à la base de données pour en suivre le schéma.


Vérifie que la base de données est synchronisée avec les classes de modèle à partir
desquelles elle a été générée. S’ils ne sont pas synchronisés, EF lève une exception.
Cela facilite la recherche de problèmes d’incohérence de code/de bases de
données.

Ajouter une propriété Rating au modèle Movie


Ajoutez une propriété Rating à Models/Movie.cs :

C#

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
public string? Title { get; set; }

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }

[Column(TypeName = "decimal(18, 2)")]


public decimal Price { get; set; }
public string? Rating { get; set; }
}

Générer l’application

Visual Studio

Ctrl+Maj+B

Comme vous avez ajouté un nouveau champ à la classe Movie , vous devez mettre à jour
la liste des liaisons de propriété pour y inclure cette nouvelle propriété. Dans
MoviesController.cs , mettez à jour l’attribut [Bind] des méthodes d’action Create et
Edit pour y inclure la propriété Rating :

C#

[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")]

Mettez à jour les modèles de vue pour afficher, créer et modifier la nouvelle propriété
Rating dans la vue du navigateur.

Modifiez le fichier /Views/Movies/Index.cshtml et ajoutez un champ Rating :

CSHTML

<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Rating)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies!)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-
id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-
id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

Mettez à jour /Views/Movies/Create.cshtml avec un champ Rating .

Visual Studio / Visual Studio pour Mac

Vous pouvez copier/coller le « groupe de formulaire » précédent et laisser


IntelliSense vous aider à mettre à jour les champs. IntelliSense fonctionne avec des
Tag Helpers.
Mettez à jour les modèles restants.

Mettez à jour la classe SeedData pour qu’elle fournisse une valeur pour la nouvelle
colonne. Vous pouvez voir un exemple de modification ci-dessous, mais elle doit être
appliquée à chaque new Movie .

C#

new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Rating = "R",
Price = 7.99M
},

L’application ne fonctionne pas tant que la base de données n’est pas mise à jour pour
inclure le nouveau champ. Si elle est exécutée maintenant, l’erreur SqlException est
levée :

SqlException: Invalid column name 'Rating'.

Cette erreur survient car la classe du modèle Movie mise à jour est différente du schéma
de la table Movie de la base de données existante. (Il n’existe pas de colonne Rating
dans la table de base de données.)

Plusieurs approches sont possibles pour résoudre l’erreur :


1. Laissez Entity Framework supprimer et recréer automatiquement la base de
données sur la base du nouveau schéma de classe de modèle. Cette approche est
très utile au début du cycle de développement quand vous effectuez un
développement actif sur une base de données de test. Elle permet de faire évoluer
rapidement le schéma de modèle et de base de données ensemble. L’inconvénient,
cependant, est que vous perdez les données existantes dans la base de données.
Par conséquent, n’utilisez pas cette approche sur une base de données de
production. L’utilisation d’un initialiseur pour amorcer automatiquement une base
de données avec des données de test est souvent un moyen efficace pour
développer une application. Il s’agit d’une bonne approche pour le développement
initial et lors de l’utilisation de SQLite.

2. Modifier explicitement le schéma de la base de données existante pour le faire


correspondre aux classes du modèle. L’avantage de cette approche est que vous
conservez vos données. Vous pouvez apporter cette modification manuellement
ou en créant un script de modification de la base de données.

3. Utilisez les migrations Code First pour mettre à jour le schéma de base de
données.

Pour ce didacticiel, les migrations Code First sont utilisées.

Visual Studio

Dans le menu Outils, sélectionnez Gestionnaire de package NuGet > Console du


gestionnaire de package.
Dans la console du gestionnaire de package, entrez les commandes suivantes :

PowerShell

Add-Migration Rating
Update-Database

La commande Add-Migration indique au framework de migration d’examiner le


modèle Movie actuel avec le schéma de la base de données Movie actuel et de
créer le code nécessaire pour migrer la base de données vers le nouveau modèle.

Le nom « Rating » est arbitraire et utilisé pour nommer le fichier de migration. Il est
utile d’utiliser un nom explicite pour le fichier de migration.

Si tous les enregistrements de la base de données sont supprimés, la méthode


d’initialisation amorce la base de données et inclut le champ Rating .

Exécutez l’application et vérifiez que vous pouvez créer, modifier et afficher des films
avec un champ Rating .

Précédent Suivant
6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 9, ajouter la validation à une
application MVC ASP.NET Core
Article • 30/11/2023

Par Rick Anderson

Dans cette section :

Une logique de validation est ajoutée au modèle Movie .


Vous vous assurez que les règles de validation sont appliquées chaque fois qu’un
utilisateur crée ou modifie un film.

Ne vous répétez pas


L’un des principes de conception de MVC est « Ne vous répétez pas » (désigné par
l’acronyme DRY , Don’t Repeat Yourself). ASP.NET Core MVC vous encourage à
spécifier les fonctionnalités ou les comportements une seule fois, puis à utiliser la
réflexion partout dans une application. Cela réduit la quantité de code à écrire, et rend
le code que vous écrivez moins susceptible aux erreurs et plus facile à tester et à gérer.

La prise en charge de la validation fournie par MVC et Entity Framework Core Code First
est un bon exemple du principe DRY en action. Vous pouvez spécifier de façon
déclarative des règles de validation à un seul emplacement (dans la classe de modèle),
et les règles sont appliquées partout dans l’application.

Ajouter des règles de validation au modèle de


film
L’espace de noms DataAnnotations fournit un ensemble d’attributs de validation
intégrés qui sont appliqués de façon déclarative à une classe ou à une propriété.
DataAnnotations contient également des attributs de mise en forme, comme DataType ,
qui aident à effectuer la mise en forme et ne fournissent aucune validation.

Mettez à jour la classe Movie pour tirer parti des attributs de validation intégrés
Required , StringLength , RegularExpression , Range et de l’attribut de mise en forme

DataType .

C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }

[StringLength(60, MinimumLength = 3)]


[Required]
public string? Title { get; set; }

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
[Required]
[StringLength(30)]
public string? Genre { get; set; }

[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string? Rating { get; set; }
}

Les attributs de validation spécifient le comportement que vous souhaitez appliquer sur
les propriétés du modèle sur lesquels ils sont appliqués :

Les attributs Required et MinimumLength indiquent qu’une propriété doit avoir une
valeur, mais rien n’empêche un utilisateur d’entrer un espace pour satisfaire à cette
validation.

L’attribut RegularExpression sert à limiter les caractères pouvant être entrés. Dans
le code précédent, « Genre » :
Doit utiliser seulement des lettres.
La première lettre doit être une majuscule. Les espaces blancs sont autorisés
tandis que les nombres et les caractères spéciaux ne sont pas autorisés.

L’expression RegularExpression « Rating » :


Nécessite que le premier caractère soit une lettre majuscule.
Autorise les caractères spéciaux et les chiffres aux emplacements qui suivent.
« PG-13 » est valide pour une évaluation, mais échoue pour un « Genre ».

L’attribut Range limite une valeur à une plage spécifiée.

L’attribut StringLength vous permet de définir la longueur maximale d’une


propriété de chaîne, et éventuellement sa longueur minimale.

Les types valeur (tels que decimal , int , float et DateTime ) sont obligatoires par
nature et n’ont pas besoin de l’attribut [Required] .

L’application automatique des règles de validation par ASP.NET Core permet d’accroître
la fiabilité de votre application. Cela garantit également que vous n’oublierez pas de
valider un élément et que vous n’autoriserez pas par inadvertance l’insertion de données
incorrectes dans la base de données.

Interface utilisateur des erreurs de validation


Exécutez l’application et accédez au contrôleur Movies.

Sélectionnez le lien Créer nouveau pour ajouter un nouveau film. Remplissez le


formulaire avec des valeurs non valides. Dès que la validation côté client jQuery détecte
l’erreur, elle affiche un message d’erreur.
7 Notes

Vous ne pourrez peut-être pas entrer de virgules décimales dans les champs
décimaux. Pour prendre en charge la validation jQuery pour les paramètres
régionaux autres que l’anglais qui utilisent une virgule (« , ») comme décimale et
des formats de date autres que l’anglais des États-Unis, vous devez effectuer des
étapes pour localiser votre application. Consultez ce commentaire GitHub 4076
pour savoir comment ajouter une virgule décimale.

Notez que le formulaire a affiché automatiquement un message d’erreur de validation


approprié dans chaque champ contenant une valeur non valide. Les erreurs sont
appliquées à la fois côté client (à l’aide de JavaScript et jQuery) et côté serveur (au cas
où un utilisateur aurait désactivé JavaScript).

L’un des principaux avantages est que vous n’avez pas eu à changer une seule ligne de
code dans la classe MoviesController ou dans la vue Create.cshtml pour activer cette
interface utilisateur de validation. Le contrôleur et les vues créées précédemment dans
ce didacticiel ont détecté les règles de validation que vous avez spécifiées à l’aide des
attributs de validation sur les propriétés de la classe de modèle Movie . Testez la
validation à l’aide de la méthode d’action Edit . La même validation est appliquée.

Les données de formulaire ne sont pas envoyées au serveur tant qu’il y a des erreurs de
validation côté client. Vous pouvez vérifier cela en plaçant un point d’arrêt dans la
méthode HTTP Post , en utilisant l’outil Fiddler ou à l’aide des Outils de
développement F12.

Fonctionnement de la validation
Vous vous demandez peut-être comment l’interface utilisateur de validation a été
générée sans aucune mise à jour du code dans le contrôleur ou dans les vues. Le code
suivant montre les deux méthodes Create .

C#

// GET: Movies/Create
public IActionResult Create()
{
return View();
}

// POST: Movies/Create
// To protect from overposting attacks, enable the specific properties you
want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult>
Create([Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(movie);
}

La première méthode d’action (HTTP GET) Create affiche le formulaire de création


initial. La deuxième version ( [HttpPost] ) gère la publication de formulaire. La seconde
méthode Create (la version [HttpPost] ) appelle ModelState.IsValid pour vérifier si le
film a des erreurs de validation. L’appel de cette méthode évalue tous les attributs de
validation qui ont été appliqués à l’objet. Si l’objet comporte des erreurs de validation, la
méthode Create réaffiche le formulaire. S’il n’y a pas d’erreur, la méthode enregistre le
nouveau film dans la base de données. Dans notre exemple de film, le formulaire n’est
pas publié sur le serveur quand des erreurs de validation sont détectées côté client ; la
seconde méthode Create n’est jamais appelée quand il y a des erreurs de validation
côté client. Si vous désactivez JavaScript dans votre navigateur, la validation client est
désactivée et vous pouvez tester la méthode Create HTTP POST ModelState.IsValid
pour détecter les erreurs de validation.

Vous pouvez définir un point d’arrêt dans la méthode [HttpPost] Create et vérifier que
la méthode n’est jamais appelée. La validation côté client n’enverra pas les données du
formulaire quand des erreurs de validation seront détectées. Si vous désactivez
JavaScript dans votre navigateur et que vous envoyez ensuite le formulaire avec des
erreurs, le point d’arrêt sera atteint. Vous bénéficiez toujours d’une validation complète
sans JavaScript.

L’illustration suivante montre comment désactiver JavaScript dans le navigateur Firefox.


L’illustration suivante montre comment désactiver JavaScript dans le navigateur Chrome.
Après la désactivation de JavaScript, publiez les données non valides et parcourez le
débogueur.
Une partie du modèle d’affichage Create.cshtml est indiquée dans le balisage suivant :

HTML

<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>

@*Markup removed for brevity.*@

Le balisage précédent est utilisé par les méthodes d’action pour afficher le formulaire
initial et pour le réafficher en cas d’erreur.

Le Tag Helper Input utilise les attributs DataAnnotations et produit les attributs HTML
nécessaires à la validation jQuery côté client. Le Tag Helper Validation affiche les erreurs
de validation. Pour plus d’informations, consultez Validation.

Le grand avantage de cette approche est que ni le contrôleur ni le modèle de vue


Create ne savent rien des règles de validation appliquées ou des messages d’erreur

affichés. Les règles de validation et les chaînes d’erreur sont spécifiées uniquement dans
la classe Movie . Ces mêmes règles de validation sont automatiquement appliquées à la
vue Edit et à tous les autres modèles de vues que vous pouvez créer et qui modifient
votre modèle.

Quand vous devez changer la logique de validation, vous pouvez le faire à un seul
endroit en ajoutant des attributs de validation au modèle (dans cet exemple, la classe
Movie ). Vous n’aurez pas à vous soucier des différentes parties de l’application qui

pourraient être incohérentes avec la façon dont les règles sont appliquées. Toute la
logique de validation sera définie à un seul emplacement et utilisée partout. Le code est
ainsi très propre, facile à mettre à jour et évolutif. Et cela signifie que vous respecterez
entièrement le principe DRY.

Utilisation d’attributs DataType


Ouvrez le fichier Movie.cs et examinez la classe Movie . L’espace de noms
System.ComponentModel.DataAnnotations fournit des attributs de mise en forme en plus

de l’ensemble intégré d’attributs de validation. Nous avons déjà appliqué une valeur
d’énumération DataType aux champs de date de sortie et de prix. Le code suivant illustre
les propriétés ReleaseDate et Price avec l’attribut DataType approprié.

C#

[Display(Name = "Release Date")]


[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }

[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }

Les attributs DataType fournissent uniquement des indices permettant au moteur de vue
de mettre en forme les données (et fournissent des éléments/attributs tels que <a>
pour les URL et <a href="mailto:EmailAddress.com"> pour l’e-mail). Vous pouvez utiliser
l’attribut RegularExpression pour valider le format des données. L’attribut DataType sert
à spécifier un type de données qui est plus spécifique que le type intrinsèque de la base
de données ; il ne s’agit pas d’un attribut de validation. Dans le cas présent, nous
voulons uniquement effectuer le suivi de la date, et non de l’heure. L’énumération
DataType fournit de nombreux types de données, telles que Date, Time, PhoneNumber,
Currency ou EmailAddress. L’attribut DataType peut également permettre à l’application
de fournir automatiquement des fonctionnalités propres au type. Par exemple, vous
pouvez créer un lien mailto: pour DataType.EmailAddress , et vous pouvez fournir un
sélecteur de date pour DataType.Date dans les navigateurs qui prennent en charge
HTML5. Les attributs DataType émettent des attributs HTML 5 data- compréhensibles
par les navigateurs HTML 5. Les attributs DataType ne fournissent aucune validation.

DataType.Date ne spécifie pas le format de la date qui s’affiche. Par défaut, le champ de

données est affiché conformément aux formats par défaut basés sur le CultureInfo du
serveur.

L’attribut DisplayFormat est utilisé pour spécifier explicitement le format de date :

C#

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode =


true)]
public DateTime ReleaseDate { get; set; }

Le paramètre ApplyFormatInEditMode indique que la mise en forme doit également être


appliquée quand la valeur est affichée dans une zone de texte à des fins de
modification. (Ceci peut ne pas être souhaitable pour certains champs ; par exemple,
pour les valeurs monétaires, vous ne souhaiterez sans doute pas que le symbole
monétaire figure dans la zone de texte.)

Vous pouvez utiliser l’attribut DisplayFormat par lui-même, mais il est généralement
préférable d’utiliser l’attribut DataType . L’attribut DataType donne la sémantique des
données, plutôt que de décrire comment effectuer le rendu sur un écran, et il offre les
avantages suivants dont vous ne bénéficiez pas avec DisplayFormat :

Le navigateur peut activer des fonctionnalités HTML5 (par exemple pour afficher
un contrôle de calendrier, le symbole monétaire correspondant aux paramètres
régionaux, des liens de messagerie, etc.).

Par défaut, le navigateur affiche les données à l’aide du format correspondant à


vos paramètres régionaux.

L’attribut DataType peut permettre à MVC de choisir le modèle de champ correct


pour afficher les données ( DisplayFormat , utilisé par lui-même, utilise le modèle de
chaîne).

7 Notes

La validation jQuery ne fonctionne pas avec l’attribut Range et DateTime . Par


exemple, le code suivant affiche toujours une erreur de validation côté client, même
quand la date se trouve dans la plage spécifiée :
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]

Vous devez désactiver la validation de date jQuery pour utiliser l’attribut Range avec
DateTime . Il n’est généralement pas recommandé de compiler des dates dures dans vos

modèles. Par conséquent, l’utilisation de l’attribut Range et de DateTime est


déconseillée.

Le code suivant illustre la combinaison d’attributs sur une seule ligne :

C#

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie


{
public int Id { get; set; }
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Display(Name = "Release Date"), DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$"), Required, StringLength(30)]
public string Genre { get; set; }
[Range(1, 100), DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$"), StringLength(5)]
public string Rating { get; set; }
}

Dans la partie suivante de la série, nous examinons l’application et nous apportons des
améliorations aux méthodes Details et Delete générées automatiquement.

Ressources supplémentaires
Utilisation des formulaires
Globalisation et localisation
Introduction aux Tag Helpers
Créer des Tag Helpers

Précédent Suivant
6 Collaborer avec nous sur ASP.NET Core feedback
GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Partie 10, Examiner les méthodes Details
et Delete d’une application ASP.NET
Core
Article • 30/11/2023

Par Rick Anderson

Ouvrez le contrôleur vidéo et examinez la méthode Details :

C#

// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}

var movie = await _context.Movie


.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}

return View(movie);
}

Le moteur de génération de modèles automatique MVC qui a créé cette méthode


d’action ajoute un commentaire présentant une requête HTTP qui appelle la méthode.
Dans le cas présent, il s’agit d’une requête GET avec trois segments d’URL : le contrôleur
Movies , la méthode Details et une valeur id . N’oubliez pas que ces segments sont

définis dans Program.cs .

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

EF facilite la recherche de données à l’aide de la méthode FirstOrDefaultAsync . Une


fonctionnalité de sécurité importante intégrée à la méthode réside dans le fait que le
code vérifie que la méthode de recherche a trouvé un film avant de tenter toute
opération que ce soit avec lui. Par exemple, un pirate informatique pourrait induire des
erreurs dans le site en modifiant l’URL créée par les liens, en remplaçant
http://localhost:{PORT}/Movies/Details/1 par quelque chose comme
http://localhost:{PORT}/Movies/Details/12345 (ou une autre valeur qui ne représente

pas un film réel). Si vous avez recherché un film null, l’application lève une exception.

Examinez les méthodes Delete et DeleteConfirmed .

C#

// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{

if (id == null)
{
return NotFound();
}

var movie = await _context.Movie


.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}

return View(movie);
}

// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var movie = await _context.Movie.FindAsync(id);
if (movie != null)
{
_context.Movie.Remove(movie);
}

await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}

Notez que la méthode HTTP GET Delete ne supprime pas le film spécifié, mais retourne
une vue du film où vous pouvez soumettre (HttpPost) la suppression. L’exécution d’une
opération de suppression en réponse à une requête GET (ou encore l’exécution d’une
opération de modification, d’une opération de création ou de toute autre opération qui
modifie des données) génère une faille de sécurité.
La méthode [HttpPost] qui supprime les données est nommée DeleteConfirmed pour
donner à la méthode HTTP POST une signature ou un nom unique. Les signatures des
deux méthodes sont illustrées ci-dessous :

C#

// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{

C#

// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{

Le Common Language Runtime (CLR) nécessite des méthodes surchargées pour avoir
une signature à paramètre unique (même nom de méthode, mais liste de paramètres
différentes). Toutefois, vous avez ici besoin de deux méthodes Delete (une pour GET et
une pour POST) ayant toutes les deux la même signature de paramètre. (Elles doivent
toutes les deux accepter un entier unique comme paramètre.)

Il existe deux approches pour ce problème. L’une consiste à attribuer aux méthodes des
noms différents. C’est ce qu’a fait le mécanisme de génération de modèles automatique
dans l’exemple précédent. Toutefois, elle présente un petit problème : ASP.NET mappe
des segments d’une URL à des méthodes d’action par nom. Si vous renommez une
méthode, il est probable que le routage ne pourra pas trouver cette méthode. La
solution consiste à faire ce que vous voyez dans l’exemple, c’est-à-dire à ajouter
l’attribut ActionName("Delete") à la méthode DeleteConfirmed . Cet attribut effectue un
mappage du système de routage afin qu’une URL qui inclut /Delete/ pour une requête
POST trouve la méthode DeleteConfirmed .

Pour contourner le problème des méthodes qui ont des noms et des signatures
identiques, vous pouvez également modifier artificiellement la signature de la méthode
POST pour inclure un paramètre supplémentaire (inutilisé). C’est ce que nous avons fait
dans une publication précédente quand nous avons ajouté le paramètre notUsed . Vous
pouvez faire de même ici pour la méthode [HttpPost] Delete :

C#

// POST: Movies/Delete/6
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id, bool notUsed)

Publier sur Azure


Pour plus d’informations sur le déploiement sur Azure, consultez Tutoriel : Créer une
application ASP.NET Core et SQL Database dans Azure App Service.

Modèles d’application web fiables


Consultez Le modèle d’application web fiable for.NETvidéos YouTube et l’article pour
obtenir des conseils sur la création d’une application ASP.NET Core moderne, fiable,
performante, testable, économique et évolutive, que ce soit à partir de zéro ou en
refactorisant une application existante.

Précédent

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Vues dans ASP.NET Core MVC
Article • 30/11/2023

Article rédigé par Steve Smith et Dave Brock

Ce document explique l’utilisation des vues dans les applications ASP.NET Core MVC.
Pour plus d’informations sur les pages Razor, consultez Présentation des pages Razor
dans ASP.NET Core.

Selon le schéma MVC (Modèle-Vue-Contrôleur), la vue gère la présentation des données


et les interactions utilisateur dans l’application. Une vue est un modèle HTML dans
lequel est incorporé un balisage Razor. Le balisage Razor est du code qui interagit avec
le balisage HTML pour générer une page web qui est envoyée au client.

Dans ASP.NET Core MVC, les vues sont des fichiers .cshtml qui utilisent le langage de
programmation C# dans le balisage Razor. En règle générale, les fichiers de vue sont
regroupés dans des dossiers nommés correspondant aux différents contrôleurs de
l’application. Ces dossiers sont eux-mêmes regroupés sous le dossier Views situé à la
racine de l’application :

Le contrôleur Home est représenté par un dossier Home à l’intérieur du dossier Views . Le
dossier Home contient les vues des pages web About , Contact et Index (page d’accueil).
Quand un utilisateur demande l’une de ces trois pages web, les actions dans le
contrôleur Home déterminent laquelle des trois vues est utilisée pour générer une page
web et la retourner à l’utilisateur.

Utilisez des dispositions pour rendre les sections de page web homogènes et réduire la
répétition de code. Les dispositions contiennent souvent l’en-tête, des éléments de
menu et de navigation, ainsi que le pied de page. L’en-tête et le pied de page
renferment généralement le code de balisage réutilisable pour divers éléments de
métadonnées, et les liens vers des ressources de script et de style. Les dispositions vous
permettent de limiter l’usage de ce balisage réutilisable dans vos vues.

Les vues partielles réduisent la répétition de code grâce à la gestion des parties
réutilisables dans les vues. Par exemple, une vue partielle est utile dans le cas d’une
biographie d’auteur publiée sur un site web de blog qui doit s’afficher dans plusieurs
vues. Une biographie d’auteur présente un contenu de vue standard et ne nécessite pas
d’exécution de code particulier pour générer le contenu à afficher sur la page web. Le
contenu de la biographie d’auteur est fourni à la vue uniquement par la liaison de
données. C’est pourquoi l’utilisation d’une vue partielle pour ce type de contenu est la
solution la plus appropriée.

Les composants de vue sont similaires aux vues partielles dans le sens où ils vous
permettent aussi de réduire la répétition de code. La différence est qu’ils sont plutôt
conçus pour du contenu de vue qui nécessite l’exécution de code sur le serveur pour
afficher la page web. Les composants de vue sont utiles quand le contenu affiché doit
interagir avec une base de données, comme c’est le cas pour un panier d’achat sur un
site web. Les composants de vue ne dépendent pas d’une liaison de données pour
pouvoir générer la sortie d’une page web.

Avantages de l’utilisation des vues


Les vues facilitent l’implémentation du principe de conception basé sur la séparation des
préoccupations (separation of concerns) au sein d’une application MVC. En effet, elles
permettent de séparer le balisage de l’interface utilisateur des autres parties de
l’application. Une application conçue selon ce principe présente une plus grande
modularité, ce qui offre plusieurs avantages :

L’application est plus facile à gérer, car elle est mieux organisée. Les vues sont
généralement regroupées par fonctionnalité dans l’application. Vous pouvez ainsi
trouver plus rapidement les vues associées quand vous utilisez une fonctionnalité.
Les différentes parties de l’application sont faiblement couplées. Vous pouvez
générer et mettre à jour les vues de l’application séparément de la logique métier
et des composants d’accès aux données. Vous pouvez modifier les vues de
l’application sans avoir nécessairement besoin de mettre à jour d’autres parties de
l’application.
Il est plus facile de tester les parties de l’interface utilisateur de l’application, car les
vues constituent des unités individuelles séparées.
Du fait d’une meilleure organisation, vous risquez moins de répéter par
inadvertance certaines sections de l’interface utilisateur.
Création d’une vue
Les vues spécifiques à un contrôleur sont créées dans le dossier
Views/[ControllerName] . Les vues communes à plusieurs contrôleurs sont créées dans le

dossier Views/Shared . Pour créer une vue, ajoutez un nouveau fichier et attribuez-lui le
même nom que son action de contrôleur associée, avec l’extension de fichier .cshtml .
Pour créer une vue qui correspond à l’action About dans le contrôleur Home , créez un
fichier About.cshtml dans le dossier Views/Home :

CSHTML

@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<p>Use this area to provide additional information.</p>

Le balisage Razor commence par le symbole @ . Pour exécuter des instructions C#,
placez le code C# dans des blocs de code Razor délimités par des accolades ( { ... } ).
L’exemple ci-dessus montre comment attribuer la valeur « About » à ViewData["Title"] .
Vous pouvez afficher des valeurs dans le code HTML simplement en référençant chaque
valeur avec le symbole @ . Le code ci-dessus donne un exemple de contenu des
éléments <h2> et <h3> .

Le contenu de la vue illustré ci-dessus est uniquement une partie de la page web entière
qui est affichée à l’utilisateur. Le reste de la disposition de la page et les autres aspects
communs de la vue sont spécifiés dans d’autres fichiers de vue. Pour en savoir plus,
consultez l’article Disposition.

Comment les contrôleurs spécifient-ils les vues


?
Les vues sont généralement retournées par des actions sous forme d’objet ViewResult,
qui est un type d’objet ActionResult. Votre méthode d’action peut créer et retourner un
ViewResult directement, mais cela n’est pas le plus fréquent. Étant donné que la plupart

des contrôleurs héritent de Controller, utilisez simplement la méthode d’assistance View


pour retourner le ViewResult :

HomeController.cs :
C#

public IActionResult About()


{
ViewData["Message"] = "Your application description page.";

return View();
}

Quand cette action est retournée, la vue About.cshtml figurant dans la dernière section
s’affiche dans la page web comme ceci :

La méthode d’assistance View a plusieurs surcharges. Vous pouvez éventuellement


spécifier :

Une vue explicite à retourner :

C#

return View("Orders");

Un modèle à passer à la vue :

C#

return View(Orders);

Une vue et un modèle :

C#
return View("Orders", Orders);

Détection de la vue
Quand une action doit retourner une vue, un processus appelé détection de la vue
s’enclenche. Ce processus détermine quel fichier de vue est utilisé en fonction du nom
de la vue.

Le comportement par défaut de la méthode View ( return View(); ) est de retourner une
vue du même nom que la méthode d’action à partir de laquelle elle est appelée. Par
exemple, le nom de la méthode About ActionResult du contrôleur est utilisé pour
rechercher un fichier de vue nommé About.cshtml . Le runtime commence par
rechercher la vue dans le dossier Views/[ControllerName] . S’il ne trouve pas de vue
correspondante, il recherche ensuite la vue dans le dossier Shared .

Peu importe si vous retournez implicitement ViewResult avec return View(); ou si vous
passez explicitement le nom de la vue à la méthode View avec return View("
<ViewName>"); . Dans les deux cas, la détection de la vue recherche un fichier de vue

correspondant dans cet ordre :

1. Views/\[ControllerName]/\[ViewName].cshtml
2. Views/Shared/\[ViewName].cshtml

Vous pouvez spécifier le chemin du fichier de vue au lieu du nom de la vue. Si vous
utilisez un chemin absolu à partir de la racine de l’application (commençant
éventuellement par « / » ou « ~/ »), vous devez indiquer l’extension .cshtml :

C#

return View("Views/Home/About.cshtml");

Vous pouvez également utiliser un chemin relatif pour spécifier des vues situées dans
des répertoires différents. Dans ce cas, n’indiquez pas l’extension .cshtml . Dans
HomeController , vous pouvez retourner la vue Index du dossier de vues Manage avec un

chemin relatif :

C#

return View("../Manage/Index");
De même, vous pouvez indiquer le dossier spécifique du contrôleur actif avec le préfixe
« ./ » :

C#

return View("./About");

Les vues partielles et les composants de vue utilisent des mécanismes de détection
presque identiques.

Vous pouvez personnaliser la convention par défaut de recherche des vues dans
l’application à l’aide d’un IViewLocationExpander personnalisé.

La détection des vues recherche les fichiers de vue d’après le nom de fichier. Si le
système de fichiers sous-jacent respecte la casse, les noms de vue respectent
probablement la casse eux aussi. Pour garantir la compatibilité entre les systèmes
d’exploitation, utilisez la même casse pour les noms de contrôleur et d’action et pour les
noms de leurs dossiers et fichiers de vues associés. Si vous utilisez un système de
fichiers qui respecte la casse et que vous voyez un message d’erreur indiquant qu’un
fichier de vue est introuvable, vérifiez que le nom du fichier de vue demandé et le nom
du fichier de vue réel ont une casse identique.

Suivez les bonnes pratiques en matière d’organisation de la structure des fichiers de


vue. Votre structure doit refléter au mieux les relations entre les contrôleurs, les actions
et les vues pour être plus claire et facile à gérer.

Transmettre des données à des vues


Plusieurs approches sont possibles pour passer des données aux vues :

Données fortement typées : viewmodel


Données faiblement typées
ViewData ( ViewDataAttribute )

ViewBag

Données fortement typées (viewmodel)


L’approche la plus fiable consiste à spécifier un type de modèle dans la vue. Ce modèle
est communément appelé ViewModel. Vous passez une instance de type ViewModel à la
vue à partir de l’action.
L’utilisation d’un ViewModel pour passer des données à une vue vous permet
d’effectuer un contrôle de type fort dans la vue. Le terme typage fort (ou fortement typé)
signifie que chaque variable et constante a un type défini explicitement (par exemple,
string , int ou DateTime ). La validité des types utilisés dans une vue est contrôlée au

moment de la compilation.

Visual Studio et Visual Studio Code répertorient les membres de classe fortement
typés à l’aide d’une fonctionnalité appelée IntelliSense. Quand vous voulez afficher les
propriétés d’un ViewModel, tapez le nom de variable pour le ViewModel suivi d’un point
( . ). Cela vous permet d’écrire du code plus rapidement et avec moins d’erreurs.

Spécifiez un modèle à l’aide de la directive @model . Utilisez le modèle avec @Model :

CSHTML

@model WebApplication1.ViewModels.Address

<h2>Contact</h2>
<address>
@Model.Street<br>
@Model.City, @Model.State @Model.PostalCode<br>
<abbr title="Phone">P:</abbr> 425.555.0100
</address>

Pour fournir le modèle à la vue, le contrôleur le passe en tant que paramètre :

C#

public IActionResult Contact()


{
ViewData["Message"] = "Your contact page.";

var viewModel = new Address()


{
Name = "Microsoft",
Street = "One Microsoft Way",
City = "Redmond",
State = "WA",
PostalCode = "98052-6399"
};

return View(viewModel);
}

Il n’y a pas de restrictions relatives aux types de modèles que vous pouvez fournir à une
vue. Nous vous recommandons d’utiliser des ViewModels POCO (Plain Old CLR Object),
avec peu ou pas de méthodes de comportement définies. En règle générale, les classes
ViewModel sont stockées dans le dossier Models ou dans un dossier ViewModels distinct
à la racine de l’application. Le ViewModel Address utilisé dans l’exemple ci-dessus est
un ViewModel OCT stocké dans un fichier nommé Address.cs :

C#

namespace WebApplication1.ViewModels
{
public class Address
{
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
}
}

Rien ne vous empêche d’utiliser les mêmes classes pour vos types de ViewModel et vos
types de modèle métier. Toutefois, l’utilisation de modèles distincts vous permet de
changer les vues indépendamment de la logique métier et des composants d’accès aux
données de votre application. La séparation des modèles et des ViewModel est
également un gage de sécurité si vous avez des modèles qui utilisent la liaison de
données et la validation pour les données envoyées à l’application par l’utilisateur.

Données faiblement typées ( ViewData , attribut


[ViewData] et ViewBag )

ViewBag n’est pas disponible par défaut pour une utilisation dans les pages

Razor PageModel classes.

En plus des vues fortement typées, les vues ont accès à une collection de données
faiblement typées (ou librement typées). Contrairement aux types forts, les types faibles
(ou types libres) ne nécessitent pas de déclarer explicitement le type de données utilisé.
Vous pouvez utiliser la collection de données faiblement typées pour passer de petites
quantités de données entre les contrôleurs et les vues.

Passer des données Exemple


entre...

Un contrôleur et une Remplissage d’une liste déroulante avec des données.


vue

Une vue et une Définition du contenu de l’élément <title> dans la disposition à partir
disposition d’un fichier de vue.
Passer des données Exemple
entre...

Une vue partielle et une Widget qui affiche des données en fonction de la page web demandée
vue par l’utilisateur.

Cette collection peut être référencée par les propriétés ViewData ou ViewBag sur les
contrôleurs et les vues. La propriété ViewData est un dictionnaire d’objets faiblement
typés. La propriété ViewBag est un wrapper autour de ViewData qui fournit des
propriétés dynamiques pour la collection ViewData sous-jacente. Remarque : les
recherches de clés respectent la casse pour ViewData et ViewBag .

ViewData et ViewBag sont résolues dynamiquement au moment de l’exécution. Dans la

mesure où elles n’effectuent pas de contrôle de type à la compilation, ces deux


propriétés sont généralement davantage sujettes aux erreurs qu’un ViewModel. Pour
cette raison, certains développeurs préfèrent ne jamais utiliser les propriétés ViewData et
ViewBag , ou les utiliser le moins possible.

ViewData

ViewData est un objet ViewDataDictionary accessible via des clés string . Vous pouvez

stocker et utiliser des données de type chaîne directement, sans avoir à les caster, mais
vous devez effectuer un cast des autres valeurs de l’objet ViewData vers des types
spécifiques lors de leur extraction. Vous pouvez utiliser ViewData pour passer des
données des contrôleurs aux vues et au sein même des vues, y compris les vues
partielles et les dispositions.

L’exemple suivant utilise un objet ViewData dans une action pour définir les valeurs d’un
message d’accueil et d’une adresse :

C#

public IActionResult SomeAction()


{
ViewData["Greeting"] = "Hello";
ViewData["Address"] = new Address()
{
Name = "Steve",
Street = "123 Main St",
City = "Hudson",
State = "OH",
PostalCode = "44236"
};
return View();
}

Utilisation des données dans une vue :

CSHTML

@{
// Since Address isn't a string, it requires a cast.
var address = ViewData["Address"] as Address;
}

@ViewData["Greeting"] World!

<address>
@address.Name<br>
@address.Street<br>
@address.City, @address.State @address.PostalCode
</address>

Attribut [ViewData]

Une autre approche qui utilise l’objet ViewDataDictionary est ViewDataAttribute. Les
valeurs des propriétés définies sur des contrôleurs ou sur des modèles de page Razor
marqués avec l’attribut [ViewData] sont stockées dans le dictionnaire et chargées à
partir de celui-ci.

Dans l’exemple suivant, le contrôleur Home contient une propriété Title marquée avec
[ViewData] . La méthode About définit le titre de la vue About :

C#

public class HomeController : Controller


{
[ViewData]
public string Title { get; set; }

public IActionResult About()


{
Title = "About Us";
ViewData["Message"] = "Your application description page.";

return View();
}
}

Dans la disposition, le titre est lu à partir du dictionnaire ViewData :


CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...

ViewBag

ViewBag n’est pas disponible par défaut pour une utilisation dans les pages

Razor PageModel classes.

ViewBag est un objet Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.DynamicViewData

qui fournit un accès dynamique aux objets stockés dans ViewData . ViewBag est parfois
plus pratique à utiliser, car il ne nécessite pas de cast. L’exemple suivant montre
comment utiliser ViewBag pour obtenir le même résultat qu’avec l’objet ViewData ci-
dessus :

C#

public IActionResult SomeAction()


{
ViewBag.Greeting = "Hello";
ViewBag.Address = new Address()
{
Name = "Steve",
Street = "123 Main St",
City = "Hudson",
State = "OH",
PostalCode = "44236"
};

return View();
}

CSHTML

@ViewBag.Greeting World!

<address>
@ViewBag.Address.Name<br>
@ViewBag.Address.Street<br>
@ViewBag.Address.City, @ViewBag.Address.State
@ViewBag.Address.PostalCode
</address>
Utilisation simultanée de ViewData et de ViewBag
ViewBag n’est pas disponible par défaut pour une utilisation dans les pages

Razor PageModel classes.

Comme ViewData et ViewBag font référence à la même collection ViewData sous-


jacente, vous pouvez utiliser ViewData et ViewBag simultanément, en les combinant et
en les associant pour lire et écrire des valeurs.

Définissez le titre avec ViewBag et la description avec ViewData au début d’une vue
About.cshtml :

CSHTML

@{
Layout = "/Views/Shared/_Layout.cshtml";
ViewBag.Title = "About Contoso";
ViewData["Description"] = "Let us tell you about Contoso's philosophy
and mission.";
}

Lisez les propriétés, mais inversez l’utilisation de ViewData et ViewBag . Dans le fichier
_Layout.cshtml , obtenez le titre et la description avec ViewData et ViewBag ,

respectivement :

CSHTML

<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"]</title>
<meta name="description" content="@ViewBag.Description">
...

Souvenez-vous que les chaînes ne nécessitent pas de cast pour ViewData . Vous pouvez
utiliser @ViewData["Title"] sans cast.

L’utilisation simultanée de ViewData et ViewBag est possible, de la même manière que la


combinaison et l’association des propriétés de lecture et d’écriture. Le balisage suivant
est affiché :

HTML

<!DOCTYPE html>
<html lang="en">
<head>
<title>About Contoso</title>
<meta name="description" content="Let us tell you about Contoso's
philosophy and mission.">
...

Résumé des différences entre ViewData et ViewBag


ViewBag n’est pas disponible par défaut pour une utilisation dans les pages

Razor PageModel classes.

ViewData

Dérivé de ViewDataDictionary, cet objet fournit des propriétés de dictionnaire


potentiellement utiles, telles que ContainsKey , Add , Remove et Clear .
Les clés contenues dans le dictionnaire sont des chaînes ; les espaces blancs
sont donc autorisés. Exemple : ViewData["Some Key With Whitespace"]
Les autres types que string doivent être castés dans la vue pour utiliser
ViewData .
ViewBag

Dérivé de Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.DynamicViewData ,
cet objet permet de créer des propriétés dynamiques avec la notation par
points ( @ViewBag.SomeKey = <value or object> ). Aucun cast n’est nécessaire. La
syntaxe de ViewBag facilite son ajout aux contrôleurs et aux vues.
Simplifie la vérification des valeurs Null. Exemple : @ViewBag.Person?.Name

Quand utiliser ViewData ou ViewBag


ViewData et ViewBag constituent deux approches appropriées pour passer de petites

quantités de données entre les contrôleurs et les vues. Choisissez celle qui vous convient
le mieux. Vous pouvez combiner et associer les objets ViewData et ViewBag . Toutefois, il
est recommandé d’utiliser une seule approche pour faciliter la lecture et la gestion du
code. Les deux approches sont résolues dynamiquement au moment de l’exécution et
sont donc susceptibles d’engendrer des erreurs d’exécution. C’est la raison pour laquelle
certains développeurs préfèrent ne pas les utiliser.

Vues dynamiques
Les vues qui ne déclarent pas de modèle de type à l’aide de @model mais qui reçoivent
une instance de modèle (par exemple, return View(Address); ) peuvent référencer
dynamiquement les propriétés de l’instance :
CSHTML

<address>
@Model.Street<br>
@Model.City, @Model.State @Model.PostalCode<br>
<abbr title="Phone">P:</abbr> 425.555.0100
</address>

Cette fonctionnalité offre beaucoup de flexibilité, mais elle ne fournit pas de protection
de la compilation ou la fonction IntelliSense. Si la propriété n’existe pas, la génération de
la page web échoue au moment de l’exécution.

Autres fonctionnalités de vue


Les Tag Helpers permettent d’ajouter facilement un comportement côté serveur dans
des balises HTML existantes. Leur utilisation vous évite d’avoir à écrire du code
personnalisé ou des méthodes d’assistance dans les vues. Les Tag Helpers sont
appliqués comme attributs aux éléments HTML et sont ignorés par les éditeurs qui ne
peuvent pas les traiter. Vous pouvez ainsi modifier et afficher le balisage des vues dans
divers outils.

La génération d’un balisage HTML personnalisé est possible avec de nombreux HTML
Helpers intégrés. Si vous avez une logique d’interface utilisateur plus complexe, gérez-la
avec les composants de vue. Les composants de vue sont conçus sur le même principe
de séparation des préoccupations (SoC) que les contrôleurs et les vues. Ils vous évitent
de devoir utiliser des actions et des vues pour le traitement des données utilisées par les
éléments d’interface utilisateur communs.

Comme de nombreux autres aspects d’ASP.NET Core, les vues prennent en charge
l’injection de dépendances, ce qui permet aux services d’être injectés dans les vues.

Isolation CSS
Isolez les styles CSS en pages, vues et composants individuels afin de réduire ou
d’éviter :

Les dépendances sur les styles globaux qui peuvent être difficiles à gérer.
Les conflits de style dans du contenu imbriqué.

Pour ajouter un fichier CSS délimité pour une page ou une vue, placez les styles CSS
dans un fichier .cshtml.css compagnon correspondant au nom du fichier .cshtml .
Dans l’exemple suivant, un fichier Index.cshtml.css fournit des styles CSS qui sont
appliqués seulement à la page ou à la vue Index.cshtml .

Pages/Index.cshtml.css (Razor Pages) ou Views/Index.cshtml.css (MVC) :

css

h1 {
color: red;
}

L’isolation CSS se produit au moment de la build. Le framework réécrit les sélecteurs


CSS pour qu’ils correspondent au balisage rendu par les pages ou les vues de
l’application. Les styles CSS réécrits sont regroupés et produits sous la forme d’une
ressource statique. {APP ASSEMBLY}.styles.css L’espace réservé {APP ASSEMBLY} est le
nom de l’assembly du projet. Un lien vers les styles CSS regroupés est placé dans la
disposition de l’application.

Dans le contenu <head> du fichier Pages/Shared/_Layout.cshtml de l’application (Razor


Pages) ou de Views/Shared/_Layout.cshtml (MVC), ajoutez ou vérifiez la présence du lien
vers les styles CSS regroupés :

HTML

<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />

Dans l’exemple suivant, le nom de l’assembly de l’application est WebApp :

HTML

<link rel="stylesheet" href="WebApp.styles.css" />

Les styles définis dans un fichier CSS délimité sont appliqués seulement à la sortie
rendue du fichier correspondant. Dans l’exemple précédent, les déclarations CSS h1
définies ailleurs dans l’application ne sont pas en conflit avec le style de titre de Index .
Les règles d’héritage et de cascade des styles CSS restent en vigueur pour les fichiers
CSS délimités. Par exemple, les styles appliqués directement à un élément <h1> du
fichier Index.cshtml remplacent les styles du fichier CSS délimité dans
Index.cshtml.css .

7 Notes
Pour garantir l’isolation du style CSS lors du regroupement, l’importation de CSS
dans des blocs de code Razor n’est pas prise en charge.

L’isolation CSS s’applique seulement aux éléments HTML. L’isolation CSS n’est pas
prise en charge pour les Tag Helpers.

Dans le fichier CSS regroupé, chaque page, vue ou composant Razor est associé à un
identificateur d’étendue au format b-{STRING} , où l’espace réservé {STRING} est une
chaîne de dix caractères générée par le framework. L’exemple suivant fournit le style
pour l’élément <h1> précédent dans la page Index d’une application Razor Pages :

css

/* /Pages/Index.cshtml.rz.scp.css */
h1[b-3xxtam6d07] {
color: red;
}

Dans la page Index où le style CSS est appliqué à partir du fichier regroupé,
l’identificateur d’étendue est ajouté en tant qu’attribut HTML :

HTML

<h1 b-3xxtam6d07>

L’identificateur est unique pour une application. Au moment de la build, un bundle de


projet est créé avec la convention {STATIC WEB ASSETS BASE PATH}/Project.lib.scp.css ,
où l’espace réservé {STATIC WEB ASSETS BASE PATH} est le chemin de base des
ressources web statiques.

Si d’autres projets sont utilisés, comme des packages NuGet ou des bibliothèques de
classes Razor, le fichier regroupé :

Fait référence aux styles en utilisant des importations CSS.


N’est pas publié en tant que ressource web statique de l’application qui
consomme les styles.

Prise en charge des préprocesseurs CSS


Les préprocesseurs CSS sont utiles pour améliorer le développement CSS en utilisant
des fonctionnalités comme les variables, l’imbrication, les modules, les mixins et
l’héritage. Bien que l’isolation CSS ne prenne pas en charge nativement les
préprocesseurs CSS comme Sass ou Less, l’intégration de préprocesseurs CSS se fait
sans problème dès lors que la compilation du préprocesseur se produit avant que le
framework réécrive les sélecteurs CSS lors du processus de build. Par exemple, avec
Visual Studio, configurez la compilation du préprocesseur existant en tant que tâche
Avant la build dans l’Explorateur d’exécuteur de tâches Visual Studio.

De nombreux packages NuGet tiers, comme AspNetCore.SassCompiler , peuvent


compiler des fichiers SASS/SCSS au début du processus de build avant que l’isolation
CSS ne se produise, et aucune configuration supplémentaire n’est requise.

Configuration de l’isolation CSS


L’isolation CSS permet la configuration de certains scénarios avancés, comme quand il
existe des dépendances sur des outils ou des workflows existants.

Personnaliser le format de l’identificateur d’étendue


Dans cette section, l’espace réservé {Pages|Views} est Pages pour les applications Razor
Pages ou Views pour les applications MVC.

Par défaut, les identificateurs d’étendue utilisent le format b-{STRING} , où l’espace


réservé {STRING} est une chaîne de dix caractères générée par le framework. Pour
personnaliser le format de l’identificateur d’étendue, mettez à jour le fichier projet avec
un modèle souhaité :

XML

<ItemGroup>
<None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-
identifier" />
</ItemGroup>

Dans l’exemple précédent, le CSS généré pour Index.cshtml.css change son


identificateur d’étendue de b-{STRING} en custom-scope-identifier .

Utilisez des identificateurs d’étendue pour mettre en œuvre l’héritage avec des fichiers
CSS délimités. Dans l’exemple de fichier projet suivant, un fichier BaseView.cshtml.css
contient des styles communs entre les vues. Un fichier DerivedView.cshtml.css hérite de
ces styles.

XML
<ItemGroup>
<None Update="{Pages|Views}/BaseView.cshtml.css" CssScope="custom-scope-
identifier" />
<None Update="{Pages|Views}/DerivedView.cshtml.css" CssScope="custom-
scope-identifier" />
</ItemGroup>

Utilisez l’opérateur de caractère générique ( * ) pour partager des identificateurs


d’étendue entre plusieurs fichiers :

XML

<ItemGroup>
<None Update="{Pages|Views}/*.cshtml.css" CssScope="custom-scope-
identifier" />
</ItemGroup>

Changer le chemin de base pour les ressources web


statiques
Le fichier CSS délimité est généré à la racine de l’application. Dans le fichier projet,
utilisez la propriété StaticWebAssetBasePath pour changer le chemin par défaut.
L’exemple suivant place le fichier CSS délimité et le reste des ressources de l’application
dans le chemin _content :

XML

<PropertyGroup>
<StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>

Désactiver le regroupement automatique


Pour ne pas accepter la façon dont l’infrastructure publie et charge des fichiers délimités
au moment de l’exécution, utilisez la propriété DisableScopedCssBundling . Lors de
l’utilisation de cette propriété, d’autres outils ou processus sont chargés de prendre les
fichiers CSS isolés du répertoire obj , et de les publier et de les charger au moment de
l’exécution :

XML

<PropertyGroup>
<DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>

Prise en charge de la bibliothèque de classes


Razor (RCL)
Quand une bibliothèque de classes Razor(RCL) fournit des styles isolés, l’attribut href
de la balise <link> pointe vers {STATIC WEB ASSET BASE PATH}/{PACKAGE
ID}.bundle.scp.css , où les espaces réservés sont :

{STATIC WEB ASSET BASE PATH} : le chemin de base de la ressource web statique.

{PACKAGE ID} : l’identificateur de package de la bibliothèque. L’identificateur de

package est défini par défaut sur le nom de l’assembly du projet si l’identificateur
de package n’est pas spécifié dans le fichier projet.

Dans l’exemple suivant :

Le chemin de base de la ressource web statique est _content/ClassLib .


Le nom de l’assembly de la bibliothèque de classes est ClassLib .

Pages/Shared/_Layout.cshtml (Razor Pages) ou Views/Shared/_Layout.cshtml (MVC) :

HTML

<link href="_content/ClassLib/ClassLib.bundle.scp.css" rel="stylesheet">

Pour plus d’informations sur les bibliothèques de classes Razor, consultez les articles
suivants :

Interface utilisateur Razor réutilisable dans les bibliothèques de classes avec


ASP.NET Core
Consommer des composants Razor ASP.NET Core d’une bibliothèque de classes
Razor (RCL)

Pour plus d’informations sur l’isolation CSS de Blazor, consultez Isolation CSS Blazor
d’ASP.NET Core.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
project. Select a link to provide
feedback:
La source de ce contenu se
trouve sur GitHub, où vous  Ouvrir un problème de
pouvez également créer et documentation
examiner les problèmes et les
demandes de tirage. Pour plus  Indiquer des commentaires sur
d’informations, consultez notre le produit
guide du contributeur.
Vues partielles dans ASP.NET Core
Article • 30/11/2023

Par Steve Smith , Maher JENDOUBI , Rick Anderson et Scott Sauber

Une vue partielle est un Razorfichier de balisage ( .cshtml ) sans une @pagedirective qui
rend la sortie HTML à l’intérieur de la sortie rendue d’un autre fichier de balisage.

Le terme vue partielle s’emploie dans le cadre du développement d’une application


MVC, où les fichiers de balisage sont appelés vues, ou une application Razor Pages où
les fichiers de balisage sont appelés pages. Cette rubrique désigne les vues MVC et les
pages Razor Pages avec le terme générique fichiers de balisage.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Quand utiliser des vues partielles ?


Les vues partielles sont utiles pour :

Découper des fichiers de balisage volumineux en composants plus petits.

Dans un grand fichier de balisage complexe composé de plusieurs parties


logiques, il peut s’avérer utile de travailler sur chaque partie isolée dans une vue
partielle. Le code dans le fichier de balisage est facile à gérer, puisqu’il contient
uniquement la structure de page globale et les références aux vues partielles.

Réduire la duplication du contenu de balisage commun entre les fichiers de


balisage.

Quand les mêmes éléments de balisage sont utilisés entre plusieurs fichiers de
balisage, une vue partielle supprime la duplication du contenu de balisage dans un
seul fichier de vue partielle. Quand le balisage est modifié dans la vue partielle, il
met à jour la sortie rendue des fichiers de balisage qui utilisent cette vue partielle.

Les vues partielles ne doivent pas être utilisées pour tenir à jour des éléments de
disposition communs. Ces éléments doivent être spécifiés dans des fichiers
_Layout.cshtml.

N’utilisez pas une vue partielle quand du code ou une logique de rendu complexe doit
être exécuté pour effectuer le rendu du balisage. Dans ce cas, utilisez un composant de
vue à la place.
Déclarer des vues partielles
Une vue partielle est un .cshtml fichier de balisage sans une directive @page tenu à jour
dans le dossier Vues (MVC) ou le dossier Pages (Razor Pages).

Dans ASP.NET Core MVC, le ViewResult d’un contrôleur peut retourner une vue ou une
vue partielle. Dans RazorPages, un PageModel peut retourner une vue partielle
représentée en tant qu’objet PartialViewResult. Le référencement et le rendu des vues
partielles sont décrits dans la section Référencer une vue partielle.

Contrairement au rendu d’une vue MVC ou d’une page, une vue partielle n’exécute pas
_ViewStart.cshtml . Pour plus d’informations sur _ViewStart.cshtml , consultez

Disposition dans ASP.NET Core.

Les noms de fichiers des vues partielles commencent souvent par un trait de
soulignement ( _ ). Cette convention de nommage n’est pas obligatoire, mais elle aide à
différencier visuellement les vues partielles des autres vues et pages.

Référencer une vue partielle

Utiliser une vue partielle dans un RazorPageModel Pages


Avec ASP.NET Core 2.0 ou 2.1, la méthode de gestionnaire suivante affiche la vue
partielle _AuthorPartialRP.cshtml à la réponse :

C#

public IActionResult OnGetPartial() =>


new PartialViewResult
{
ViewName = "_AuthorPartialRP",
ViewData = ViewData,
};

Avec ASP.NET Core 2.2 ou version ultérieure, une méthode de gestionnaire peut
également appeler la méthode Partial pour générer un objet PartialViewResult :

C#

public IActionResult OnGetPartial() =>


Partial("_AuthorPartialRP");
Utiliser une vue partielle dans un fichier de balises
Il y a plusieurs façons de référencer une vue partielle au sein d’un fichier de balisage.
Pour vos applications, nous vous recommandons de choisir l’une des approches de
rendu asynchrone suivantes :

Tag Helper Partial


Assistance HTML asynchrone

Tag Helper Partial


Le Tag Helper Partial nécessite ASP.NET Core 2.1 ou version ultérieure.

Le Tag Helper Partial effectue un rendu asynchrone et utilise une syntaxe de type HTML :

CSHTML

<partial name="_PartialName" />

Si une extension de fichier est spécifiée, la vue partielle référencée par le Tag Helper doit
se trouver dans le même dossier que le fichier de balisage qui appelle la vue partielle :

CSHTML

<partial name="_PartialName.cshtml" />

L’exemple suivant référence une vue partielle à la racine de l’application. Les chemins
qui commencent par un tilde et une barre oblique ( ~/ ) ou par une barre oblique seule
( / ) renvoient à la racine de l’application :

Razor Pages

CSHTML

<partial name="~/Pages/Folder/_PartialName.cshtml" />


<partial name="/Pages/Folder/_PartialName.cshtml" />

MVC

CSHTML

<partial name="~/Views/Folder/_PartialName.cshtml" />


<partial name="/Views/Folder/_PartialName.cshtml" />
L’exemple suivant référence une vue partielle avec un chemin relatif :

CSHTML

<partial name="../Account/_PartialName.cshtml" />

Pour plus d’informations, consultez assistance des balises partielle dans ASP.NET Core.

Assistance HTML asynchrone


Avec un Helper HTML, la bonne pratique est d’utiliser PartialAsync. PartialAsync
retourne un type IHtmlContent wrappé dans un Task<TResult>. La méthode est
référencée par l’ajout du préfixe @ à l’appel attendu :

CSHTML

@await Html.PartialAsync("_PartialName")

Si l’extension de fichier est spécifiée, la vue partielle référencée par le Helper HTML doit
se trouver dans le même dossier que le fichier de balisage qui appelle la vue partielle :

CSHTML

@await Html.PartialAsync("_PartialName.cshtml")

L’exemple suivant référence une vue partielle à la racine de l’application. Les chemins
qui commencent par un tilde et une barre oblique ( ~/ ) ou par une barre oblique seule
( / ) renvoient à la racine de l’application :

Razor Pages

CSHTML

@await Html.PartialAsync("~/Pages/Folder/_PartialName.cshtml")
@await Html.PartialAsync("/Pages/Folder/_PartialName.cshtml")

MVC

CSHTML

@await Html.PartialAsync("~/Views/Folder/_PartialName.cshtml")
@await Html.PartialAsync("/Views/Folder/_PartialName.cshtml")
L’exemple suivant référence une vue partielle avec un chemin relatif :

CSHTML

@await Html.PartialAsync("../Account/_LoginPartial.cshtml")

Vous pouvez aussi effectuer le rendu d’une vue partielle avec RenderPartialAsync. Cette
méthode ne retourne aucun IHtmlContent. Elle envoie la sortie rendue directement à la
réponse. Comme elle ne retourne aucun résultat, cette méthode doit être appelée dans
un bloc de code Razor :

CSHTML

@{
await Html.RenderPartialAsync("_AuthorPartial");
}

Dans la mesure où elle envoie directement le contenu rendu, la méthode


RenderPartialAsync est plus efficace dans certains scénarios. Dans les scénarios

nécessitant de hautes performances, effectuez un test d’évaluation de la page à l’aide de


ces deux approches et choisissez celle qui génère une réponse plus rapidement.

Assistance HTML synchrone


Partial et RenderPartial sont les équivalents synchrones de PartialAsync et
RenderPartialAsync , respectivement. Les équivalents synchrones ne sont pas

recommandés en raison du risque d’interblocage dans certains scénarios. Les méthodes


synchrones sont prévues d’être supprimées dans une version ultérieure.

) Important

Si vous devez exécuter du code, utilisez un composant de vue au lieu d’une vue
partielle.

L’appel de Partial ou RenderPartial génère un avertissement de l’analyseur Visual


Studio. Par exemple, la présence de Partial génère le message d’avertissement suivant
:

L’utilisation de IHtmlHelper.Partial peut entraîner des interblocages d’application.


Utilisez plutôt un Tag Helper <Partial> ou IHtmlHelper.PartialAsync.
Remplacez les appels à @Html.Partial par @await Html.PartialAsync ou le Tag Helper
Partial. Pour plus d’informations sur la migration du Tag Helper Partial, consultez Migrer
à partir d’une assistance HTML.

Découverte des vues partielles


Quand une vue partielle est référencée par son nom sans extension de fichier, les
emplacements suivants sont recherchés dans l’ordre indiqué :

Razor Pages

1. Dossier de la page en cours d’exécution


2. Directory Graph au-dessus du dossier de la page
3. /Shared
4. /Pages/Shared
5. /Views/Shared

MVC

1. /Areas/<Area-Name>/Views/<Controller-Name>
2. /Areas/<Area-Name>/Views/Shared
3. /Views/Shared
4. /Pages/Shared

Les conventions suivantes s’appliquent à la détection des vues partielles :

Vous pouvez avoir plusieurs vues partielles avec le même nom de fichier à
condition que les vues partielles se trouvent dans des dossiers distincts.
Quand vous référencez une vue partielle par son nom (sans extension de fichier) et
que la vue partielle est présente à la fois dans le dossier de l’appelant et le dossier
Partagé, la vue partielle fournie est celle du dossier de l’appelant. Si la vue partielle
n’est pas présente dans le dossier de l’appelant, la vue partielle fournie est celle du
dossier Partagé. Les vues partielles dans le dossier Partagé sont appelées vues
partielles partagées ou vues partielles par défaut.
Les vues partielles peuvent être chaînées_, c’est-à-dire qu’une vue partielle peut
appeler une autre vue partielle si une référence circulaire n’est pas formée par les
appels. Les chemins relatifs sont toujours relatifs au fichier actuel, et non au fichier
racine ou parent associé.

7 Notes
Une Razor section définie dans une vue partielle n’est pas visible par les fichiers de
balisage parents. La section est visible uniquement par la vue partielle dans
laquelle elle est définie.

Accéder à des données à partir de vues


partielles
Quand une vue partielle est instanciée, elle reçoit une copie du dictionnaire ViewData du
parent. Les mises à jour apportées aux données de la vue partielle ne sont pas
conservées dans la vue parente. Tout changement apporté à ViewData dans une vue
partielle est perdu quand la vue partielle est retournée.

L’exemple suivant montre comment passer une instance de ViewDataDictionary à une


vue partielle :

CSHTML

@await Html.PartialAsync("_PartialName", customViewData)

Vous pouvez passer un modèle dans une vue partielle. Le modèle peut être un objet
personnalisé. Vous pouvez passer un modèle avec PartialAsync (envoie le rendu d’un
bloc de contenu à l’appelant) ou avec RenderPartialAsync (transmet le contenu à la
sortie) :

CSHTML

@await Html.PartialAsync("_PartialName", model)

Razor Pages

Le balisage suivant dans l’exemple d’application est extrait de la page


Pages/ArticlesRP/ReadRP.cshtml . La page contient deux vues partielles. La seconde vue

partielle passe un modèle et ViewData à la vue partielle. La surcharge de constructeur


ViewDataDictionary passe un nouveau dictionnaire ViewData tout en conservant le

dictionnaire ViewData existant.

CSHTML

@model ReadRPModel

<h2>@Model.Article.Title</h2>
@* Pass the author's name to Pages\Shared\_AuthorPartialRP.cshtml *@
@await Html.PartialAsync("../Shared/_AuthorPartialRP",
Model.Article.AuthorName)
@Model.Article.PublicationDate

@* Loop over the Sections and pass in a section and additional ViewData to
the strongly typed Pages\ArticlesRP\_ArticleSectionRP.cshtml partial
view. *@
@{
var index = 0;

foreach (var section in Model.Article.Sections)


{
await Html.PartialAsync("_ArticleSectionRP",
section,
new ViewDataDictionary(ViewData)
{
{ "index", index }
});

index++;
}
}

Pages/Shared/_AuthorPartialRP.cshtml est la première vue partielle référencée par le

ReadRP.cshtml fichier de balisage :

CSHTML

@model string
<div>
<h3>@Model</h3>
This partial view from /Pages/Shared/_AuthorPartialRP.cshtml.
</div>

Pages/ArticlesRP/_ArticleSectionRP.cshtml est la deuxième vue partielle référencée par

le ReadRP.cshtml fichier de balisage :

CSHTML

@using PartialViewsSample.ViewModels
@model ArticleSection

<h3>@Model.Title Index: @ViewData["index"]</h3>


<div>
@Model.Content
</div>

MVC
Dans l’exemple d’application, le balisage suivant montre la vue
Views/Articles/Read.cshtml . La vue contient deux vues partielles. La seconde vue

partielle passe un modèle et ViewData à la vue partielle. La surcharge de constructeur


ViewDataDictionary passe un nouveau dictionnaire ViewData tout en conservant le

dictionnaire ViewData existant.

CSHTML

@model PartialViewsSample.ViewModels.Article

<h2>@Model.Title</h2>
@* Pass the author's name to Views\Shared\_AuthorPartial.cshtml *@
@await Html.PartialAsync("_AuthorPartial", Model.AuthorName)
@Model.PublicationDate

@* Loop over the Sections and pass in a section and additional ViewData to
the strongly typed Views\Articles\_ArticleSection.cshtml partial view. *@
@{
var index = 0;

foreach (var section in Model.Sections)


{
@(await Html.PartialAsync("_ArticleSection",
section,
new ViewDataDictionary(ViewData)
{
{ "index", index }
}))

index++;
}
}

Views/Shared/_AuthorPartial.cshtml est la première vue partielle référencée par le

Read.cshtml fichier de balisage :

CSHTML

@model string
<div>
<h3>@Model</h3>
This partial view from /Views/Shared/_AuthorPartial.cshtml.
</div>

Views/Articles/_ArticleSection.cshtml est la deuxième vue partielle référencée par le

Read.cshtml fichier de balisage :

CSHTML
@using PartialViewsSample.ViewModels
@model ArticleSection

<h3>@Model.Title Index: @ViewData["index"]</h3>


<div>
@Model.Content
</div>

Au moment de l’exécution, le rendu des vues partielles est effectué dans la sortie rendue
du fichier de balisage parent, qui est elle-même rendue dans le fichier partagé
_Layout.cshtml . La première vue partielle affiche le nom de l’auteur et la date de

publication de l’article :

Abraham Lincoln

This partial view from <shared partial view file path>. 11/19/1863 12:00:00 AM

La seconde vue partielle affiche les sections de l’article :

Section One Index: 0

Four score and seven years ago ...

Section Two Index: 1

Now we are engaged in a great civil war, testing ...

Section Three Index: 2

But, in a larger sense, we can not dedicate ...

Ressources supplémentaires
Informations de référence sur la syntaxe Razor pour ASP.NET Core
Tag Helpers dans ASP.NET Core
Tag Helper Partial dans ASP.NET Core
Composants de vue dans ASP.NET Core
Zones dans ASP.NET Core

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
project. Select a link to provide
La source de ce contenu se feedback:
trouve sur GitHub, où vous
pouvez également créer et  Ouvrir un problème de
examiner les problèmes et les documentation
demandes de tirage. Pour plus
d’informations, consultez notre  Indiquer des commentaires sur
guide du contributeur.
le produit
Gérer les requêtes avec des contrôleurs
dans ASP.NET Core MVC
Article • 30/11/2023

Par Steve Smith et Scott Addie

Les contrôleurs, les actions et les résultats des actions sont une part fondamentale dans
la façon dont les développeurs créent des applications avec ASP.NET Core MVC.

Qu’est-ce qu’un contrôleur ?


Un contrôleur est utilisé pour définir et regrouper un ensemble d’actions. Une action (ou
méthode d’action) est une méthode sur un contrôleur qui gère les demandes. Les
contrôleurs regroupent de façon logique des actions similaires. Cette agrégation des
actions permet l’application collective de jeux de règles communs, comme le routage, la
mise en cache et les autorisations. Les demandes sont mappées à des actions via un
routage. Les contrôleurs sont activés et supprimés en fonction des requêtes.

Par convention, les classes de contrôleur :

Se trouvent dans le dossier Controllers au niveau de la racine du projet.


Héritez de Microsoft.AspNetCore.Mvc.Controller .

Un contrôleur est une classe instanciable, habituellement publique, dans laquelle au


moins une des conditions suivantes est vraie :

Le nom de classe a comme suffixe Controller .


La classe hérite d’une classe dont le nom est suivi du suffixe Controller .
L’attribut [Controller] est appliqué à la classe.

Une classe de contrôleur ne doit pas avoir d’attribut [NonController] associé.

Les contrôleurs doivent suivre le principe de dépendances explicites. Il existe deux


approches pour implémenter ce principe. Si plusieurs actions de contrôleur nécessitent
le même service, envisagez d’utiliser l’injection de constructeur pour demander ces
dépendances. Si le service est nécessaire pour une seule méthode d’action, envisagez
d’utiliser l’injection d’action pour demander la dépendance.

Dans le Modèle-Vue-Contrôleur, un contrôleur est responsable du traitement initial de


la demande et de l’instanciation du modèle. En règle générale, les décisions métier
doivent être prises dans le modèle.
Le contrôleur prend le résultat du traitement du modèle (le cas échéant) et retourne la
vue appropriée et les données associées à cette vue, ou bien le résultat de l’appel d’API.
Pour en savoir plus, consultez Vue d’ensemble d’ASP.NET Core MVC et Bien démarrer
avec ASP.NET Core MVC et Visual Studio.

Le contrôleur est une abstraction au niveau de l’interface utilisateur. Ses responsabilités


sont de garantir que les données de la demande sont valides et de choisir la vue (ou le
résultat d’API) à retourner. Dans les applications bien construites, il n’inclut pas
directement l’accès aux données ni la logique métier. Au lieu de cela, le contrôleur
délègue à des services la gestion de ces responsabilités.

Définition des actions


Les méthodes publiques sur un contrôleur, sauf celles avec l’attribut [NonAction] , sont
des actions. Les paramètres sur les actions sont liés aux données des demandes et sont
validés avec la liaison de modèle. La validation du modèle est effectuée pour tout ce qui
est lié au modèle. La valeur de la propriété ModelState.IsValid indique si la liaison de
modèle et la validation ont réussi.

Les méthodes d’action doivent contenir la logique nécessaire pour mapper une
demande à un problème métier. Les problèmes métier doivent généralement être
représentés comme des services auxquels le contrôleur accède via l’injection de
dépendances. Les actions mappent ensuite le résultat de l’action métier à un état de
l’application.

Les actions peuvent retourner des valeurs de n’importe quel type, mais elles retournent
souvent une instance de IActionResult (ou de Task<IActionResult> pour les méthodes
asynchrones) qui produit une réponse. La méthode d’action est responsable du choix du
type de réponse. Le résultat de l’action constitue la réponse.

Méthodes helper des contrôleurs


Les contrôleurs héritent généralement de la classe Controller, bien que ce ne soit pas
obligatoire. Le fait de dériver de Controller fournit l’accès à trois catégories de
méthodes helper :

1. Méthodes aboutissant à un corps de réponse vide


Aucune en-tête de réponse HTTP Content-Type n’est présente, étant donné que le corps
de la réponse n’a pas de contenu à décrire.
Il existe deux types de résultats dans cette catégorie : Redirection et Code d’état HTTP.

Code d’état HTTP

Ce type retourne un code d’état HTTP. BadRequest , NotFound et Ok sont des


méthodes helper de ce type. Par exemple, return BadRequest(); produit un code
d’état 400 quand elle est exécutée. Quand des méthodes comme BadRequest ,
NotFound et Ok sont surchargées, elles ne sont plus qualifiées comme répondeurs

de code d’état HTTP, étant donné que la négociation du contenu est en cours.

Rediriger

Ce type retourne une redirection vers une action ou une destination (avec
Redirect , LocalRedirect , RedirectToAction ou RedirectToRoute ). Par exemple,

return RedirectToAction("Complete", new {id = 123}); redirige vers Complete , en

passant un objet anonyme.

Le type de résultat Redirection diffère du type Code d’état HTTP principalement


par l’ajout d’un en-tête de réponse HTTP Location .

2. Méthodes aboutissant à un corps de réponse de non-vide avec


un type de contenu prédéfini
La plupart des méthodes helper de cette catégorie incluent une propriété ContentType ,
qui vous permet de définir l’en-tête de réponse Content-Type pour décrire le corps de la
réponse.

Il existe deux types de résultats dans cette catégorie : Vue et Réponse mise en forme.

Afficher

Ce type retourne une vue qui utilise un modèle pour rendre le HTML. Par exemple,
return View(customer); passe un modèle à la vue pour la liaison de données.

Réponse mise en forme

Ce type retourne un format JSON, ou un format d’échange de données similaire,


pour représenter un objet d’une manière spécifique. return Json(customer);
sérialise par exemple l’objet fourni au format JSON.

File et PhysicalFile sont des méthodes courantes de ce type. Par exemple,

return PhysicalFile(customerFilePath, "text/xml"); retourne PhysicalFileResult.


3. Méthodes aboutissant à un corps de réponse non-vide mise en
forme selon un type de contenu négocié avec le client

Cette catégorie est plus connue sous le nom de Négociation de contenu. La


négociation de contenu s’applique chaque fois qu’une action retourne un type
ObjectResult ou quelque chose d’autre qu’une implémentation de IActionResult. Une
action qui retourne une implémentation autre que IActionResult (par exemple object )
retourne également une Réponse mise en forme.

BadRequest , CreatedAtRoute et Ok sont des méthodes helper de ce type. return


BadRequest(modelState); , return CreatedAtRoute("routename", values, newobject); et

return Ok(value); sont des exemples respectifs de ces méthodes. Notez que
BadRequest et Ok effectuent une négociation de contenu seulement quand ils reçoivent

une valeur ; si aucune valeur ne leur est passée, ils délivrent à la place des types de
résultats Code d’état HTTP. La méthode CreatedAtRoute effectue quant à elle toujours
une négociation de contenu, car ses surcharges nécessitent toutes qu’une valeur soit
passée.

Problèmes transversaux
En règle générale, les applications partagent des parties de leur flux de travail. C’est par
exemple le cas d’une application qui exige une authentification pour l’accès au panier
d’achat ou qui met en cache les données de certaines pages. Pour exécuter la logique
avant ou après une méthode d’action, utilisez un filtre. L’utilisation de Filtres sur les
problèmes transversaux peut réduire la duplication.

La plupart des attributs des filtres, comme [Authorize] , peuvent être appliqués au
niveau du contrôleur ou de l’action, selon le niveau de granularité souhaité.

La gestion des erreurs et la mise en cache des réponses sont souvent des problèmes
transversaux :

Gérer les erreurs


Mise en cache des réponses

De nombreux problèmes transversaux peuvent être gérés en utilisant des filtres ou un


intergiciel (middleware) personnalisé.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub
La source de ce contenu se ASP.NET Core is an open source
trouve sur GitHub, où vous project. Select a link to provide
pouvez également créer et feedback:
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Routage vers les actions du contrôleur
dans ASP.NET Core
Article • 30/11/2023

Par Ryan Nowak , Kirk Larkin et Rick Anderson

Les contrôleurs ASP.NET Core utilise l’intergiciel de routage pour mettre en


correspondance les URL des requêtes entrantes et les mapper à des actions. Modèles de
route :

Sont définis au démarrage dans Program.cs ou dans les attributs.


Décrire comment les chemins d’accès d’URL sont mis en correspondance avec les
actions.
Sont utilisés pour générer des URL pour les liens. Les liens générés sont
généralement retournés dans les réponses.

Les actions sont routées de façon conventionnelle ou routées par attribut. Le fait de
placer une route sur le contrôleur ou sur l’action les rend « routés par attribut ». Pour
plus d’informations, consultez Routage mixte.

Ce document :

Explique les interactions entre MVC et le routage :


Comment les applications MVC classiques utilisent les fonctionnalités de
routage?
Couvre les deux :
Routage conventionnel généralement utilisé avec les contrôleurs et les vues.
Routage d’attributs utilisé avec REST les API. Si vous êtes principalement
intéressé par le routage pour REST les API, accédez à la section Routage des
attributs pour REST les API.
Pour plus d’informations sur le routage avancé, consultez Routage.
Fait référence au système de routage par défaut appelé routage de point de
terminaison. Il est possible d’utiliser des contrôleurs avec la version précédente du
routage à des fins de compatibilité. Pour obtenir des instructions, consultez le
guide de migration 2.2-3.0.

Configurer la route conventionnelle


Le modèle MVC ASP.NET Core génère un code de routage conventionnel semblable à ce
qui suit :
C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

MapControllerRoute est utilisé pour créer une route unique. La route unique est nommé
default route. La plupart des applications avec des contrôleurs et des vues utilisent un

modèle de route default similaire à la route. REST Les API doivent utiliser le routage
d’attributs.

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

Le modèle de route "{controller=Home}/{action=Index}/{id?}" :

Correspond à un chemin d’accès d’URL comme /Products/Details/5

Extrait les valeurs { controller = Products, action = Details, id = 5 }


d’itinéraire en jetons le chemin d’accès. L’extraction des valeurs de route entraîne
une correspondance si l’application a un contrôleur nommé ProductsController et
une Details action :

C#
public class ProductsController : Controller
{
public IActionResult Details(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

MyDisplayRouteInfo est fourni par le package NuGet


Rick.Docs.Samples.RouteInfo et affiche les informations de routage.

/Products/Details/5 model lie la valeur de id = 5 pour définir le id paramètre

sur 5 . Pour plus d’informations, consultez Liaison de modèle.

{controller=Home} définit Home comme controller par défaut.

{action=Index} définit Index comme action par défaut.

Le caractère ? dans {id?} définit id comme facultatif.


Les paramètres de route par défaut et facultatifs n’ont pas besoin d’être
présents dans le chemin d’URL pour qu’une correspondance soit établie. Pour
une description détaillée de la syntaxe du modèle de route, consultez
Informations de référence sur le modèle de route.

Correspond au chemin d’accès d’URL / .

Produit les valeurs { controller = Home, action = Index } de route.

Valeurs pour controller et action utiliser les valeurs par défaut. id ne produit pas de
valeur, car il n’existe pas de segment correspondant dans le chemin d’accès d’URL. /
Correspond uniquement s’il existe une HomeController action et Index :

C#

public class HomeController : Controller


{
public IActionResult Index() { ... }
}

En utilisant la définition du contrôleur et le modèle de route précédents, l’action


HomeController.Index est exécutée pour les chemins d’accès d’URL suivants :

/Home/Index/17

/Home/Index
/Home
/

Le chemin d’accès d’URL / utilise l’action et Home les contrôleurs par défaut Index du
modèle de route. Le chemin d’accès d’URL /Home utilise l’action par défaut Index du
modèle de route.

La méthode pratique MapDefaultControllerRoute :

C#

app.MapDefaultControllerRoute();

Remplace :

C#

app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

) Important

Le routage est configuré à l’aide de l’intergiciel UseRouting et .UseEndpoints Pour


utiliser des contrôleurs :

Appel MapControllers à des contrôleurs routés d’attribut de mappage.


Appelez MapControllerRoute ou MapAreaControllerRoute, pour mapper à la
fois les contrôleurs routés de manière conventionnelle et les contrôleurs
routés d’attribut .

Les applications n’ont généralement pas besoin d’appeler UseRouting ou


UseEndpoints . WebApplicationBuilder configure un pipeline d’intergiciels qui

encapsule l’intergiciel ajouté dans Program.cs avec UseRouting et UseEndpoints .


Pour plus d’informations, consultez Routage dans ASP.NET Core.

Routage conventionnel
Routage conventionnel est utilisé avec les contrôleurs et les vues. La route default :

C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

Le précédent est un exemple de route conventionnelle. Nous appelons ce style routage


conventionnel, car il établit une convention pour les chemins d’URL :

Le premier segment de tracé, {controller=Home} , correspond au nom du


contrôleur.
Le deuxième segment, {action=Index} , correspond au nom de l’action.
Le troisième segment, {id?} est utilisé pour un facultatif id . Le ? dans {id?} le
rend facultatif. id est utilisé pour mapper à une entité de modèle.

À l’aide de cette route default , le chemin d’accès d’URL :

/Products/List mappe à l’action ProductsController.List .

/Blog/Article/17 mappe à BlogController.Article et le modèle lie généralement

le id paramètre à 17.

Ce mappage :

Est basé uniquement sur les noms du contrôleur et des actions.


N’est pas basé sur des espaces de noms, des emplacements de fichiers sources ou
des paramètres de méthode.

L’utilisation du routage conventionnel avec la route par défaut permet de créer


l’application sans avoir à inventer un nouveau modèle d’URL pour chaque action. Pour
une application avec des actions de style CRUD , ayant une cohérence pour les URL
entre les contrôleurs :

Permet de simplifier le code.


Rend l’interface utilisateur plus prévisible.

2 Avertissement

Le id dans le code précédent est défini comme facultatif par le modèle de route.
Les actions peuvent s’exécuter sans l’ID facultatif fourni dans le cadre de l’URL. En
règle générale, quand id est omis de l’URL :

id est défini sur 0 par liaison de données.

Aucune entité n’est trouvée dans la base de données correspondant à id ==


0.
Le routage par attribut vous donne un contrôle précis pour rendre le code
obligatoire pour certaines actions et pas pour d’autres. Par convention, la
documentation inclut des paramètres facultatifs comme id quand ils sont
susceptibles d’apparaître dans une utilisation correcte.

La plupart des applications doivent choisir un schéma de routage de base et descriptif


pour que les URL soient lisibles et explicites. La route conventionnelle par défaut
{controller=Home}/{action=Index}/{id?} :

Prend en charge un schéma de routage de base et descriptif.


Est un point de départ pratique pour les applications basées sur une interface
utilisateur.
Est le seul modèle d’itinéraire nécessaire pour de nombreuses applications
d’interface utilisateur web. Pour les applications d’interface utilisateur web plus
volumineuses, une autre route utilisant Zones est souvent tout ce qui est
nécessaire.

MapControllerRoute et MapAreaRoute :

Attribuent automatiquement une valeur de commande à leurs points de


terminaison en fonction de l’ordre qu’ils appellent.

Routage des points de terminaison dans ASP.NET Core :

N’a pas de concept de routes.


Ne fournit pas de garanties de classement pour l’exécution de l’extensibilité, tous
les points de terminaison sont traités en même temps.

Activez la journalisation pour voir comment les implémentations de routage intégrées,


comme Route, établissent des correspondances avec les requêtes.

Le routage des attributs est expliqué plus loin dans ce document.

Plusieurs routes conventionnelles


Plusieurs routes conventionnelles peuvent être configurées en ajoutant d’autres appels à
MapControllerRoute et MapAreaControllerRoute. Ceci permet de définir plusieurs
conventions ou d’ajouter des routes conventionnelles qui sont dédiées à une action
spécifique, comme :

C#

app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

La route blog dans le code précédent est une route conventionnelle dédiée. Il s’agit
d’une route conventionnelle dédiée pour les raisons suivantes :

Elle utilise un routage conventionnel.


C’est dédié à une action spécifique.

Étant donné que controller et action n’apparaissent pas dans le modèle de route
"blog/{*article}" en tant que paramètres :

Ils peuvent uniquement avoir les valeurs { controller = "Blog", action =


"Article" } par défaut .

Cette route est toujours mappée à l’action BlogController.Article .

/Blog , /Blog/Article et /Blog/{any-string} sont les seuls chemins d’URL qui

correspondent à la route du blog.

L’exemple précédent :

blog la route a une priorité plus élevée pour les correspondances que la

route default , car elle est ajoutée en premier.


Il s’agit d’un exemple de routage de style Slug où il est courant d’avoir un nom
d’article dans l’URL.

2 Avertissement

Dans ASP.NET Core, le routage ne :

Définit pas un concept appelé route. UseRouting ajoute la correspondance de


routage au pipeline d’intergiciels. L’ UseRouting intergiciel examine l’ensemble
des points de terminaison définis dans l’application et sélectionne la meilleure
correspondance des points de terminaison en fonction de la requête.
Fournissez des garanties sur l’ordre d’exécution de l’extensibilité, comme
IRouteConstraint ou IActionConstraint.

Consultez Routage pour obtenir des informations de référence sur le routage.

Ordre de routage conventionnel


Le routage conventionnel correspond uniquement à une combinaison d’action et de
contrôleur qui sont définis par l’application. Ceci est conçu pour simplifier les cas où des
routes conventionnelles se chevauchent. Ajouter des routes en utilisant
MapControllerRoute, MapDefaultControllerRoute et MapAreaControllerRoute attribuent
automatiquement une valeur de l’ordre à leurs points de terminaison en fonction de
l’ordre qu’ils appellent. Les correspondances d’une route qui apparaît précédemment
ont une priorité plus élevée. Le routage conventionnel est dépendant de l’ordre. En
général, les routes avec des zones doivent être placées plus haut, car elles sont plus
spécifiques que les routes sans zone. Les routes classiques dédiées avec des paramètres
de route fourre-tout comme {*article} peuvent rendre un itinéraire trop gourmand, ce
qui signifie qu’il correspond aux URL que vous avez l’intention de mettre en
correspondance avec d’autres routes. Placez les routes globales plus loin dans la table
de routage pour éviter les correspondances globales.

Résoudre les actions ambiguës


Lorsque deux points de terminaison correspondent via le routage, le routage doit
effectuer l’une des opérations suivantes :

Choisissez le meilleur candidat.


Levée d'une exception.

Par exemple :

C#

public class Products33Controller : Controller


{
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[HttpPost]
public IActionResult Edit(int id, Product product)
{
return ControllerContext.MyDisplayRouteInfo(id, product.name);
}
}

Le contrôleur précédent définit deux actions qui correspondent :

Le chemin d’accès d’URL /Products33/Edit/17


Données de route { controller = Products33, action = Edit, id = 17 } .
Il s’agit d’un modèle classique pour les contrôleurs MVC :

Edit(int) affiche un formulaire pour modifier un produit.

Edit(int, Product) traite le formulaire publié.

Pour résoudre la route correcte :

Edit(int, Product) est sélectionné lorsque la requête est un http POST .

Edit(int) est sélectionné lorsque le verbe HTTP est autre chose. Edit(int) est

généralement appelé par le biais de GET .

Le HttpPostAttribute, [HttpPost] , est fourni pour le routage afin qu’il puisse choisir en
fonction de la méthode HTTP de la requête. Le HttpPostAttribute fait Edit(int,
Product) une meilleure correspondance que Edit(int) .

Il est important de comprendre le rôle des attributs tels que HttpPostAttribute . Des
attributs similaires sont définis pour d’autres verbes HTTP. Dans le routage
conventionnel, il est courant que les actions utilisent le même nom d’action lorsqu’elles
font partie d’un flux de travail de type montrer le formulaire, envoyer le formulaire. Par
exemple, consultez Examiner les deux méthodes d’action Modifier.

Si le routage ne peut pas choisir un meilleur candidat, un AmbiguousMatchException


est levée, répertoriant les plusieurs points de terminaison correspondants.

Noms de routes conventionnelles


Les chaînes "blog" et "default" dans les exemples suivants sont des noms de routes
conventionnelles :

C#

app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

Les noms de routes donnent à la route un nom logique. La route nommée peut être
utilisée pour la génération d’URL. Utiliser une route nommée simplifie considérablement
la création d’URL quand l’ordonnancement des routes peut rendre compliquée la
génération des URL. Les noms de routes doivent être unique à l’échelle de l’application.

Noms des routes :


N’ont aucun impact sur la correspondance d’URL ou la gestion des requêtes.
Sont utilisés uniquement pour la génération d’URL.

Le concept de nom de route est représenté dans le routage sous la forme


IEndpointNameMetadata. Les termes nom de la route et nom du point de terminaison :

Sont interchangeables.
Celui qui est utilisé dans la documentation et le code dépend de l’API décrite.

Routage des attributs pour REST les API


Les API REST doivent utiliser le routage d’attributs pour modéliser les fonctionnalités de
l’application sous la forme d’un ensemble de ressources dans lequel les opérations sont
représentées par des verbes HTTP.

Le routage par attributs utilise un ensemble d’attributs pour mapper les actions
directement aux modèles de routes. Le code suivant est classique pour une REST API et
est utilisé dans l’exemple suivant :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Dans le code précédent, MapControllers est appelé pour mapper les contrôleurs routés
d’attribut.

Dans l’exemple suivant :

HomeController correspond à un ensemble d’URL similaires à


{controller=Home}/{action=Index}/{id?} routes conventionnelles par défaut.

C#
public class HomeController : Controller
{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult Index(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

L’action HomeController.Index sera exécutée pour tous les chemins d’accès d’URL / ,
/Home , /Home/Index ou /Home/Index/3 .

Cet exemple met en évidence une différence importante en termes de programmation


entre le routage par attributs et le routage conventionnel. Le routage d’attributs
nécessite plus d’entrée pour spécifier une route. La route par défaut conventionnel gère
les routes de manière plus succincte. Cependant, le routage par attributs permet (et
nécessite) un contrôle précis des modèles de routes qui s’appliquent à chaque action.

Avec le routage d’attributs, les noms du contrôleur et des actions ne jouent aucun rôle
dans lequel l’action est mise en correspondance, sauf si le remplacement de jeton est
utilisé. L’exemple suivant correspond aux mêmes URL que l’exemple précédent :

C#

public class MyDemoController : Controller


{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult MyIndex(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

Le code suivant utilise le remplacement de jeton pour action et controller :

C#

public class HomeController : Controller


{
[Route("")]
[Route("Home")]
[Route("[controller]/[action]")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}

[Route("[controller]/[action]")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

Le code suivant applique le contrôleur [Route("[controller]/[action]")] :

C#

[Route("[controller]/[action]")]
public class HomeController : Controller
{
[Route("~/")]
[Route("/Home")]
[Route("~/Home/Index")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}

public IActionResult About()


{
return ControllerContext.MyDisplayRouteInfo();
}
}

Dans le code précédent, les modèles de Index méthode doivent être ajoutés / ou ~/
aux modèles d’itinéraire. Les modèles de routes appliqués à une action qui commencent
par / ou ~/ ne sont pas combinés avec les modèles de routes appliqués au contrôleur.
Pour plus d’informations sur la sélection du modèle de route, consultez Priorité du
modèle de route.

Noms de routage réservés


Les mots clés suivants sont des noms de paramètres de route réservés lors de
l’utilisation de contrôleurs ou Razor de pages :

action
area

controller
handler

page

L’utilisation page comme paramètre d’itinéraire avec le routage d’attributs est une
erreur courante. Cela entraîne un comportement incohérent et confus avec la
génération d’URL.

C#

public class MyDemo2Controller : Controller


{
[Route("/articles/{page}")]
public IActionResult ListArticles(int page)
{
return ControllerContext.MyDisplayRouteInfo(page);
}
}

Les noms de paramètres spéciaux sont utilisés par la génération d’URL pour déterminer
si une opération de génération d’URL fait référence à une Razor page ou à un
contrôleur.

Les mots clés suivants sont réservés dans le contexte d’une Razor vue ou d’une Razor
page :

page

using
namespace

inject

section
inherits

model
addTagHelper
removeTagHelper

Ces mots clés ne doivent pas être utilisés pour les générations de liens, les paramètres
liés au modèle ou les propriétés de niveau supérieur.

Modèles de verbes HTTP


ASP.NET Core a les modèles de verbes HTTP suivants :

[HttpGet]
[HttpPost]
[HttpPut]
[HttpDelete]
[HttpHead]
[HttpPatch]

Modèles de route
ASP.NET Core a les modèles d’itinéraire suivants :

Tous les modèles de verbe HTTP sont des modèles de routage.


[Route]

Routage par attributs avec des attributs du verbe Http


Examinons le contrôleur ci-dessous :

C#

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
[HttpGet] // GET /api/test2
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}

[HttpGet("{id}")] // GET /api/test2/xyz


public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
[HttpGet("int/{id:int}")] // GET /api/test2/int/3
public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[HttpGet("int2/{id}")] // GET /api/test2/int2/3


public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

Dans le code précédent :

Chaque action contient l’attribut [HttpGet] , qui limite la correspondance aux


requêtes HTTP GET uniquement.
L’action GetProduct inclut le "{id}" modèle et est donc id ajoutée au
"api/[controller]" modèle sur le contrôleur. Le modèle de méthodes est
"api/[controller]/{id}" . Par conséquent, cette action ne correspond qu’aux

demandes GET pour le formulaire /api/test2/xyz , /api/test2/123 , /api/test2/{any


string} , etc.

C#

[HttpGet("{id}")] // GET /api/test2/xyz


public IActionResult GetProduct(string id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

L’action GetIntProduct contient le modèle "int/{id:int}" . La :int partie du


modèle limite les valeurs de routage id aux chaînes qui peuvent être converties en
entier. Une requête d’obtention pour /api/test2/int/abc :
Ne correspond pas à cette action.
Retourne une erreur 404 Introuvable .

C#

[HttpGet("int/{id:int}")] // GET /api/test2/int/3


public IActionResult GetIntProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

L’action GetInt2Product contient {id} dans le modèle, mais ne limite id pas les
valeurs qui peuvent être converties en entier. Une requête d’obtention pour
/api/test2/int2/abc :

Correspond à cette route.


La liaison de données ne parvient pas à convertir abc en entier. Le paramètre
id de la méthode est entier.

Retourne une requête incorrecte 400 , car la liaison de données n’a pas pu
convertir abc en entier.

C#

[HttpGet("int2/{id}")] // GET /api/test2/int2/3


public IActionResult GetInt2Product(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

Le routage par attributs peut utiliser des attributs HttpMethodAttribute tels que
HttpPostAttribute, HttpPutAttribute, et HttpDeleteAttribute. Tous les attributs de verbe
HTTP acceptent un modèle de route. L’exemple suivant montre deux actions qui
correspondent au même modèle de route :

C#

[ApiController]
public class MyProductsController : ControllerBase
{
[HttpGet("/products3")]
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}

[HttpPost("/products3")]
public IActionResult CreateProduct(MyProduct myProduct)
{
return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
}
}

À l’aide du chemin d’accès d’URL /products3 :

L’action MyProductsController.ListProducts s’exécute lorsque le verbe HTTP est


GET .

L’action MyProductsController.CreateProduct s’exécute lorsque le verbe HTTP est


POST .
Lors de la génération d’une REST API, il est rare que vous deviez utiliser [Route(...)]
sur une méthode d’action, car l’action accepte toutes les méthodes HTTP. Il est
préférable d’utiliser les attributs des verbes HTTP plus spécifiques pour plus de précision
quant à ce qui est pris en charge par votre API. Les clients des REST API doivent
normalement connaître les chemins et les verbes HTTP qui correspondent à des
opérations logiques spécifiques.

REST API doivent utiliser le routage d’attributs pour modéliser les fonctionnalités de
l’application sous la forme d’un ensemble de ressources dans lequel les opérations sont
représentées par des verbes HTTP. Cela signifie que plusieurs opérations (comme GET et
POST) sur la même ressource logique utilisent la même URL. Le routage d’attributs
fournit le niveau de contrôle nécessaire pour concevoir avec soin la disposition des
points de terminaison publics d’une API.

Dans la mesure où une route d’attribut s’applique à une action spécifique, il est facile de
placer les paramètres nécessaires dans la définition du modèle de route. Dans l’exemple
suivant, id est obligatoire dans le chemin d’accès d’URL :

C#

[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

L’action Products2ApiController.GetProduct(int) :

Est exécuté avec le chemin d’accès d’URL comme /products2/3


N’est pas exécuté avec le chemin d’accès d’URL /products2 .

L’attribut [Consumes] permet à une action de limiter les types de contenu de la


demande pris en charge. Pour plus d’informations, consultez Définir les types de
contenu de la requête pris en charge avec l’attribut [Consumes].

Consultez Routage pour obtenir une description complète des modèles de routes et des
options associées.

Pour plus d’informations sur [ApiController] , consultez Attribut ApiController.


Nom de l’itinéraire
Le code suivant définit un nom de route Products_List :

C#

[ApiController]
public class Products2ApiController : ControllerBase
{
[HttpGet("/products2/{id}", Name = "Products_List")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

Les noms de routes peuvent être utilisés pour générer une URL basée sur une route
spécifique. Noms des routes :

N’ont aucun impact sur le comportement de correspondance d’URL du routage.


Sont utilisés uniquement pour la génération d’URL.

Les noms de routes doivent être unique à l’échelle de l’application.

Comparez le code précédent avec la route par défaut conventionnelle, qui définit le
paramètre id comme étant facultatif ( {id?} ). La possibilité de spécifier les API avec
précision présente des avantages, par exemple de permettre de diriger /products et
/products/5 vers des actions différentes.

Combiner des routes d’attributs


Pour rendre le routage par attributs moins répétitif, les attributs de route sont combinés
avec des attributs de route sur les actions individuelles. Les modèles de routes définis
sur le contrôleur sont ajoutés à des modèles de routes sur les actions. Placer un attribut
de route sur le contrôleur a pour effet que toutes les actions du contrôleur utilisent le
routage par attributs.

C#

[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
[HttpGet]
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}

[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

Dans l’exemple précédent :

Le chemin d’accès de /products l’URL peut correspondre


ProductsApi.ListProducts

Le chemin d’accès de /products/5 l’URL peut correspondre


ProductsApi.GetProduct(int) .

Ces deux actions correspondent seulement à HTTP GET , car elles sont marquées avec
l’attribut [HttpGet] .

Les modèles de routes appliqués à une action qui commencent par / ou ~/ ne sont pas
combinés avec les modèles de routes appliqués au contrôleur. L’exemple suivant met en
correspondance avec un ensemble de chemins d’accès d’URL similaires à la route par
défaut.

C#

[Route("Home")]
public class HomeController : Controller
{
[Route("")]
[Route("Index")]
[Route("/")]
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}

[Route("About")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

Le tableau suivant explique les [Route] attributs du code précédent :


Attribut Combine avec [Route("Home")] Définit le modèle de route

[Route("")] Oui "Home"

[Route("Index")] Oui "Home/Index"

[Route("/")] Non ""

[Route("About")] Oui "Home/About"

Ordre de route des attributs


Le routage génère une arborescence et correspond à tous les points de terminaison
simultanément :

Les entrées de route se comportent comme si elles sont placées dans un ordre
idéal.
Les routes les plus spécifiques ont une chance d’être exécutées avant les routes
plus générales.

Par exemple, une route d’attribut comme blog/search/{topic} est plus spécifique
qu’une route d’attribut comme blog/{*article} . La route blog/search/{topic} a une
priorité plus élevée, par défaut, car elle est plus spécifique. Avec le routage
conventionnel, le développeur est responsable du placement des routes dans l’ordre
souhaité.

Les routes d’attributs peuvent configurer un ordre à l’aide de la propriété Order. Tous les
attributs de route fournis par l’infrastructure incluent Order . Les routes sont traitées
selon un ordre croissant de la propriété Order . L’ordre par défaut est 0 . La définition
d’une route avec Order = -1 fait que cette route s’exécute avant les routes qui ne
définissent pas d’ordre. La définition d’une route avec Order = 1 fait que cette route
s’exécute après l’ordre des routes par défaut.

Évitez de dépendre de Order . Si l’espace d’URL d’une application nécessite des valeurs
d’ordre explicites pour router correctement, il est probable qu’il prête également à
confusion pour les clients. D’une façon générale, le routage par attributs sélectionne la
route correcte avec la mise en correspondance d’URL. Si l’ordre par défaut utilisé pour la
génération d’URL ne fonctionne pas, l’utilisation à titre de remplacement d’un nom de
route est généralement plus simple que d’appliquer la propriété Order .

Considérez les deux contrôleurs suivants qui définissent tous deux la correspondance
/home de route :
C#

public class HomeController : Controller


{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult Index(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

C#

public class MyDemoController : Controller


{
[Route("")]
[Route("Home")]
[Route("Home/Index")]
[Route("Home/Index/{id?}")]
public IActionResult MyIndex(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

La requête /home avec le code précédent lève une exception similaire à ce qui suit :

text

AmbiguousMatchException: The request matched multiple endpoints. Matches:

WebMvcRouting.Controllers.HomeController.Index
WebMvcRouting.Controllers.MyDemoController.MyIndex
L’ajout Order à l’un des attributs de route résout l’ambiguïté :

C#

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
return ControllerContext.MyDisplayRouteInfo();
}

Avec le code précédent, /home exécute le point de HomeController.Index terminaison.


Pour accéder à MyDemoController.MyIndex , demandez /home/MyIndex . Remarque :

Le code précédent est un exemple ou une conception de routage médiocre. Il a


été utilisé pour illustrer la propriété Order .
La propriété Order résout uniquement l’ambiguïté. Ce modèle ne peut pas être
mis en correspondance. Il serait préférable de supprimer le modèle
[Route("Home")] .

Pour plus d’informations sur l’ordre d’itinéraire avec Razor Pages, consultez Conventions
de route et d’application :Razor Ordre de routage.

Dans certains cas, une erreur HTTP 500 est retournée avec des routes ambiguës. Utilisez
la journalisation pour voir quels points de terminaison sont à l’origine de
AmbiguousMatchException .

Remplacement de jetons dans les modèles de


routes [contrôleur], [action], [zone]
Pour plus de commodité, les routes d’attribut prennent en charge le remplacement de
jetons, qui se fait via la mise entre crochets d’un jeton ( [ , ] ). Les jetons [action] ,
[area] et [controller] sont remplacés par les valeurs du nom d’action, du nom de la

zone et du nom du contrôleur de l’action où la route est définie :

C#

[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
[HttpGet]
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}

[HttpGet("{id}")]
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

Dans le code précédent :

C#

[HttpGet]
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}

Correspondances /Products0/List

C#

[HttpGet("{id}")]
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}

Correspondances /Products0/Edit/{id}

Le remplacement des jetons se produit à la dernière étape de la création des routes


d’attribut. L’exemple précédent se comporte de la même manière que le code suivant :

C#

public class Products20Controller : Controller


{
[HttpGet("[controller]/[action]")] // Matches '/Products20/List'
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}

[HttpGet("[controller]/[action]/{id}")] // Matches
'/Products20/Edit/{id}'
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

Si vous lisez ceci dans une autre langue que l’anglais, faites-le nous savoir dans ce
problème de discussion GitHub si vous souhaitez voir les commentaires de code dans
votre langue maternelle.

Les routes d’attribut peuvent aussi être combinées avec l’héritage. Combiné avec le
remplacement de jetons, c’est puissant. Le remplacement des jetons s’applique aussi aux
noms de routes définis par des routes d’attribut. [Route("[controller]/[action]",
Name="[controller]_[action]")] génère un nom de route unique pour chaque action :

C#

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller


{
[HttpGet] // /api/products11/list
public IActionResult List()
{
return ControllerContext.MyDisplayRouteInfo();
}

[HttpGet("{id}")] // /api/products11/edit/3
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

Pour faire correspondre le délimiteur littéral de remplacement de jetons [ ou ] , placez-


le en échappement en répétant le caractère ( [[ ou ]] ).

Utiliser un transformateur de paramètre pour


personnaliser le remplacement des jetons
Le remplacement des jetons peut être personnalisé à l’aide d’un transformateur de
paramètre. Un transformateur de paramètre implémente
IOutboundParameterTransformer et transforme la valeur des paramètres. Par exemple,
un transformateur de paramètre SlugifyParameterTransformer personnalisé transforme
la valeur de la route SubscriptionManagement en subscription-management :

C#

using System.Text.RegularExpressions;

public class SlugifyParameterTransformer : IOutboundParameterTransformer


{
public string? TransformOutbound(object? value)
{
if (value == null) { return null; }

return Regex.Replace(value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,

TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
}
}

RouteTokenTransformerConvention est une convention de modèle d’application qui :

Applique un transformateur de paramètre à toutes les routes d’attribut dans une


application.
Personnalise les valeurs de jeton de route d’attribut quand elles sont remplacées.

C#

public class SubscriptionManagementController : Controller


{
[HttpGet("[controller]/[action]")]
public IActionResult ListAll()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

La méthode ListAll précédente correspond à /subscription-management/list-all .

Le RouteTokenTransformerConvention est inscrit en tant qu’option :

C#

using Microsoft.AspNetCore.Mvc.ApplicationModels;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddControllersWithViews(options =>
{
options.Conventions.Add(new RouteTokenTransformerConvention(
new SlugifyParameterTransformer()));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Consultez la documentation web MDN sur Slug pour la définition de Slug.

2 Avertissement

Lorsque vous utilisez System.Text.RegularExpressions pour traiter une entrée non


approuvée, passez un délai d’expiration. Un utilisateur malveillant peut fournir une
entrée à RegularExpressions , provoquant une attaque par déni de service . Les
API d’infrastructure ASP.NET Core qui utilisent RegularExpressions passent un délai
d’expiration.

Routes d’attribut multiples


Le routage par attributs prend en charge la définition de plusieurs routes pour atteindre
la même action. L’utilisation la plus courante de ceci est d’imiter le comportement de la
route conventionnelle par défaut, comme le montre l’exemple suivant :

C#

[Route("[controller]")]
public class Products13Controller : Controller
{
[Route("")] // Matches 'Products13'
[Route("Index")] // Matches 'Products13/Index'
public IActionResult Index()
{
return ControllerContext.MyDisplayRouteInfo();
}

Le fait de placer plusieurs attributs de route sur le contrôleur signifie que chacun d’eux
se combine avec chacun des attributs de route sur les méthodes d’action :

C#

[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
[HttpPost("Buy")] // Matches 'Products6/Buy' and 'Store/Buy'
[HttpPost("Checkout")] // Matches 'Products6/Checkout' and
'Store/Checkout'
public IActionResult Buy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

Toutes les contraintes de routes de verbe HTTP implémentent IActionConstraint .

Lorsque plusieurs attributs de route qui implémentent IActionConstraint sont placés sur
une action :

Chaque contrainte d’action se combine avec le modèle de route appliqué au


contrôleur.

C#

[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
[HttpPut("Buy")] // Matches PUT 'api/Products7/Buy'
[HttpPost("Checkout")] // Matches POST 'api/Products7/Checkout'
public IActionResult Buy()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

L’utilisation de plusieurs routes sur des actions peut sembler utile et puissante. Il est
préférable de conserver l’espace URL de votre application de base et bien défini. Utilisez
plusieurs routes sur les actions seulement là où c’est nécessaire, par exemple pour
prendre en charge des clients existants.

Spécification facultative de paramètres, de valeurs par


défaut et de contraintes pour les routes d’attribut
Les routes d’attribut prennent en charge la même syntaxe inline que les routes
conventionnelles pour spécifier des paramètres, des valeurs par défaut et des
contraintes facultatifs.

C#

public class Products14Controller : Controller


{
[HttpPost("product14/{id:int}")]
public IActionResult ShowProduct(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}

Dans le code précédent, [HttpPost("product14/{id:int}")] applique une contrainte de


route. L’action Products14Controller.ShowProduct est mise en correspondance
uniquement par des chemins d’accès d’URL tels que /product14/3 . La partie {id:int}
modèle de routage limite ce segment à des entiers uniquement.

Pour une description détaillée de la syntaxe du modèle de route, consultez Informations


de référence sur le modèle de route.

Attributs de route personnalisés à l’aide de


IRouteTemplateProvider
Tous les attributs de route implémentent IRouteTemplateProvider. Le Runtime ASP.NET
Core :

Recherche des attributs sur les classes de contrôleur et les méthodes d’action au
démarrage de l’application.
Utilise les attributs qui implémentent IRouteTemplateProvider pour générer
l’ensemble initial de routes.

Implémentez IRouteTemplateProvider pour définir des attributs de route personnalisés.


Chaque IRouteTemplateProvider vous permet de définir une route avec un modèle, un
nom et un ordre de route personnalisés :
C#

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider


{
public string Template => "api/[controller]";
public int? Order => 2;
public string Name { get; set; } = string.Empty;
}

[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
// GET /api/MyTestApi
[HttpGet]
public IActionResult Get()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

La méthode précédente Get retourne Order = 2, Template = api/MyTestApi .

Utiliser le modèle d’application pour personnaliser les


routes des attributs
L’ancien modèle d’application :

Modèle objet créé au démarrage dans Program.cs .


Contient toutes les métadonnées utilisées par ASP.NET Core pour router et
exécuter les actions dans une application.

Le modèle d’application inclut toutes les données collectées à partir des attributs de
route. Les données des attributs de routage sont fournies par l’implémentation
IRouteTemplateProvider . Conventions :

Peut être écrit pour modifier le modèle d’application afin de personnaliser le


comportement du routage.
Sont lus au démarrage de l’application.

Cette section montre un exemple de base de personnalisation du routage avec le


modèle d’application. Le code suivant aligne approximativement les routes avec la
structure de dossiers du projet.

C#
public class NamespaceRoutingConvention : Attribute,
IControllerModelConvention
{
private readonly string _baseNamespace;

public NamespaceRoutingConvention(string baseNamespace)


{
_baseNamespace = baseNamespace;
}

public void Apply(ControllerModel controller)


{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel
!= null);
if (hasRouteAttributes)
{
return;
}

var namespc = controller.ControllerType.Namespace;


if (namespc == null)
return;
var template = new StringBuilder();
template.Append(namespc, _baseNamespace.Length + 1,
namespc.Length - _baseNamespace.Length - 1);
template.Replace('.', '/');
template.Append("/[controller]/[action]/{id?}");

foreach (var selector in controller.Selectors)


{
selector.AttributeRouteModel = new AttributeRouteModel()
{
Template = template.ToString()
};
}
}
}

Le code suivant empêche l’application de la namespace convention aux contrôleurs qui


sont routés par l’attribut :

C#

public void Apply(ControllerModel controller)


{
var hasRouteAttributes = controller.Selectors.Any(selector =>
selector.AttributeRouteModel !=
null);
if (hasRouteAttributes)
{
return;
}

Par exemple, le contrôleur suivant n’utilise pas NamespaceRoutingConvention :

C#

[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
// /managers/index
public IActionResult Index()
{
var template =
ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
return Content($"Index- template:{template}");
}

public IActionResult List(int? id)


{
var path = Request.Path.Value;
return Content($"List- Path:{path}");
}
}

La méthode NamespaceRoutingConvention.Apply :

Ne fait rien si le contrôleur est routé par l’attribut.


Définit le modèle de contrôleurs en fonction de namespace , avec la base namespace
supprimée.

Le NamespaceRoutingConvention peut être appliqué dans Program.cs :

C#

using My.Application.Controllers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
options.Conventions.Add(
new NamespaceRoutingConvention(typeof(HomeController).Namespace!));
});

var app = builder.Build();

Par exemple, prenons le contrôleur suivant :


C#

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
public class UsersController : Controller
{
// GET /admin/controllers/users/index
public IActionResult Index()
{
var fullname = typeof(UsersController).FullName;
var template =

ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
var path = Request.Path.Value;

return Content($"Path: {path} fullname: {fullname} template:


{template}");
}

public IActionResult List(int? id)


{
var path = Request.Path.Value;
return Content($"Path: {path} ID:{id}");
}
}
}

Dans le code précédent :

La base namespace est My.Application .


Le nom complet du contrôleur précédent est
My.Application.Admin.Controllers.UsersController .

Le NamespaceRoutingConvention définit le modèle de contrôleurs sur


Admin/Controllers/Users/[action]/{id? .

Le NamespaceRoutingConvention peut également être appliqué en tant qu’attribut sur un


contrôleur :

C#

[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
// /admin/controllers/test/index
public IActionResult Index()
{
var template =
ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
var actionname = ControllerContext.ActionDescriptor.ActionName;
return Content($"Action- {actionname} template:{template}");
}

public IActionResult List(int? id)


{
var path = Request.Path.Value;
return Content($"List- Path:{path}");
}
}

Routage mixte : routage conventionnel et


routage par attributs
Les applications ASP.NET Core peuvent combiner l’utilisation du routage conventionnel
et du routage par attributs. Il est courant d’utiliser des routes conventionnelles pour les
contrôleurs délivrant des pages HTML pour les navigateurs, et le routage par attributs
pour les contrôleurs délivrant des API REST.

Les actions sont routées de façon conventionnelle ou routées par attribut. Le fait de
placer une route sur le contrôleur ou sur l’action les rend « routés par attribut ». Les
actions qui définissent des routes d’attribut ne sont pas accessibles via les routes
conventionnelles et vice versa. Tout attribut de route sur le contrôleur a pour effet que
toutes les actions du contrôleur sont routées par attributs.

Le routage d’attributs et le routage conventionnel utilisent le même moteur de routage.

Routage avec des caractères spéciaux


Le routage avec des caractères spéciaux peut entraîner des résultats inattendus. Par
exemple, considérez un contrôleur avec la méthode d’action suivante :

C#

[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
var todoItem = await _context.TodoItems.FindAsync(id);

if (todoItem == null || todoItem.Name == null)


{
return NotFound();
}
return todoItem.Name;
}

Lorsque string id contient les valeurs encodées suivantes, des résultats inattendus
peuvent se produire :

ASCII Encoded

/ %2F

Les paramètres de routage ne sont pas toujours décodés par URL. Ce problème peut
être résolu à l’avenir. Pour plus d’informations, consultez ce problème GitHub ;

Génération d’URL et valeurs ambiantes


Les applications peuvent utiliser les fonctionnalités de génération d’URL de routage
pour générer des liens URL vers des actions. La génération d’URL élimine le codage en
dur des URL, ce qui rend un code plus robuste et plus facile à maintenir. Cette section
se concentre sur les fonctionnalités de génération d’URL fournies par MVC et couvre
seulement les principes de base du fonctionnement de la génération d’URL. Pour une
description détaillée de la génération d’URL, consultez Routage.

L’interface IUrlHelper est l’élément d’infrastructure sous-jacent entre MVC et le routage


pour la génération d’URL. Une instance de IUrlHelper est disponible par le biais de la
propriété Url dans les contrôleurs, les vues et les composants de vue.

Dans l’exemple suivant, l’interface IUrlHelper est utilisée par le biais de la propriété
Controller.Url pour générer une URL vers une autre action.

C#

public class UrlGenerationController : Controller


{
public IActionResult Source()
{
// Generates /UrlGeneration/Destination
var url = Url.Action("Destination");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}

public IActionResult Destination()


{
return ControllerContext.MyDisplayRouteInfo();
}
}

Si l’application utilise la route conventionnelle par défaut, la valeur de la variable url est
la chaîne de chemin d’URL /UrlGeneration/Destination . Ce chemin d’accès d’URL est
créé par routage en combinant :

Valeurs de routage de la requête actuelle, appelées valeurs ambiantes.


Valeurs transmises à Url.Action et en remplaçant ces valeurs dans le modèle de
route :

text

ambient values: { controller = "UrlGeneration", action = "Source" }


values passed to Url.Action: { controller = "UrlGeneration", action =
"Destination" }
route template: {controller}/{action}/{id?}

result: /UrlGeneration/Destination

La valeur de chaque paramètre de route du modèle de route est remplacée en


établissant une correspondance avec les valeurs et les valeurs ambiantes. Un paramètre
de route qui n’a pas de valeur peut :

Utilisez une valeur par défaut s’il en a une.


Être ignoré s’il est facultatif. Par exemple, le id à partir du modèle de
route {controller}/{action}/{id?} .

La génération d’URL échoue si un paramètre de route obligatoire n’a pas de valeur


correspondante. Si la génération d’URL échoue pour une route, la route suivante est
essayée, ceci jusqu’à ce que toutes les routes aient été essayées ou qu’une
correspondance soit trouvée.

L’exemple précédent de Url.Action suppose le routage conventionnel. La génération


d’URL fonctionne de façon similaire avec le routage par attributs, même si les concepts
sont différents. Avec un routage conventionnel :

Les valeurs de route sont utilisées pour développer un modèle.


Les valeurs de routage pour controller et action s’affichent généralement dans
ce modèle. Cela fonctionne, car les URL mises en correspondance par le routage
respectent une convention.

L’exemple suivant utilise un attribut de routage :


C#

public class UrlGenerationAttrController : Controller


{
[HttpGet("custom")]
public IActionResult Source()
{
var url = Url.Action("Destination");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}

[HttpGet("custom/url/to/destination")]
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
}

L’action Source dans le code précédent génère custom/url/to/destination .

LinkGeneratora été ajouté dans ASP.NET Core 3.0 comme alternative à IUrlHelper .
LinkGenerator offre des fonctionnalités similaires, mais plus flexibles. Chaque méthode

sur IUrlHelper a également une famille de méthodes correspondante sur


LinkGenerator .

Génération des URL par nom d’action


Url.Action, LinkGenerator.GetPathByAction et toutes les surcharges associées sont tous
conçus pour générer le point de terminaison cible en spécifiant un nom de contrôleur et
un nom d’action.

Lors de l’utilisation de Url.Action , les valeurs de routage actuelles pour controller et


action sont fournies par le runtime :

La valeur de controller et action fait partie des valeurs ambiantes et des valeurs.
La méthode Url.Action utilise toujours les valeurs actuelles de action et de
controller , et génère un chemin d’accès d’URL qui route vers l’action actuelle.

Le routage essaye d’utiliser les valeurs dans les valeurs ambiantes pour renseigner les
informations qui n’ont pas été fournies lors de la génération d’une URL. Considérez une
route comme {a}/{b}/{c}/{d} avec des valeurs ambiantes { a = Alice, b = Bob, c =
Carol, d = David } :

Le routage contient suffisamment d’informations pour générer une URL sans


valeurs supplémentaires.
Le routage contient suffisamment d’informations, car tous les paramètres de route
ont une valeur.

Si la valeur { d = Donovan } est ajoutée :

La valeur { d = David } est ignorée.


Le chemin d’accès d’URL généré est Alice/Bob/Carol/Donovan .

Avertissement : Les chemins d’accès d’URL sont hiérarchiques. Dans l’exemple


précédent, si la valeur { c = Cheryl } est ajoutée :

Les deux valeurs { c = Carol, d = David } sont ignorées.


Il n’y a plus de valeur pour d et la génération d’URL échoue.
Les valeurs souhaitées de c et d doivent être spécifiées pour générer une URL.

Vous pouvez vous attendre à rencontrer ce problème avec l’itinéraire


{controller}/{action}/{id?} par défaut. Ce problème est rare dans la pratique, car

Url.Action spécifie toujours explicitement une controller valeur et action .

Plusieurs surcharges de Url.Action prennent un objet de valeurs d’itinéraire pour fournir


des valeurs pour les paramètres de route autres que controller et action . L’objet
valeurs de route est fréquemment utilisé avec id . Par exemple : Url.Action("Buy",
"Products", new { id = 17 }) . Objet de valeurs de route :

Par convention est généralement un objet de type anonyme.


Il peut s’agir d’un IDictionary<> ou d’un POCO ).

Toutes les valeurs de route supplémentaires qui ne correspondent pas aux paramètres
de route sont placées dans la chaîne de requête.

C#

public IActionResult Index()


{
var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
return Content(url!);
}

Le code précédent génère /Products/Buy/17?color=red .

Le code suivant génère une URL absolue :

C#
public IActionResult Index2()
{
var url = Url.Action("Buy", "Products", new { id = 17 }, protocol:
Request.Scheme);
// Returns https://localhost:5001/Products/Buy/17
return Content(url!);
}

Pour créer une URL absolue, utilisez l’une des options suivantes :

Une surcharge qui accepte un protocol . Par exemple, le code précédent.


LinkGenerator.GetUriByAction, qui génère des URI absolus par défaut.

Générer des URL par route


Le code précédent a montré la génération d’une URL en passant le nom du contrôleur
et le nom de l’action. IUrlHelper fournit également la famille de méthodes Url.RouteUrl.
Ces méthodes sont similaires à Url.Action, mais elle ne copient pas les valeurs actuelles
de action et de controller vers les valeurs de route. Utilisation la plus courante pour
Url.RouteUrl :

Spécifie un nom d’itinéraire pour générer l’URL.


En règle générale, ne spécifie pas de contrôleur ou de nom d’action.

C#

public class UrlGeneration2Controller : Controller


{
[HttpGet("")]
public IActionResult Source()
{
var url = Url.RouteUrl("Destination_Route");
return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
}

[HttpGet("custom/url/to/destination2", Name = "Destination_Route")]


public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}

Le fichier suivant Razor génère un lien HTML vers Destination_Route :

CSHTML
<h1>Test Links</h1>

<ul>
<li><a href="@Url.RouteUrl("Destination_Route")">Test
Destination_Route</a></li>
</ul>

Générer des URL en HTML et Razor


IHtmlHelper fournit les HtmlHelperméthodes Html.BeginForm et Html.ActionLink pour
générer <form> et <a> éléments respectivement. Ces méthodes utilisent la méthode
Url.Action pour générer une URL et ils acceptent les arguments similaires. Les pendants
de Url.RouteUrl pour HtmlHelper sont Html.BeginRouteForm et Html.RouteLink , qui ont
des fonctionnalités similaires.

Les TagHelpers génèrent des URL via le TagHelper form et le TagHelper <a> . Ils utilisent
tous les deux IUrlHelper pour leur implémentation. Pour plus d’informations, consultez
Tag Helpers dans les formulaires .

Dans les vues, IUrlHelper est disponible via la propriété Url pour toute génération
d’URL ad hoc non couverte par ce qui figure ci-dessus.

Génération d’URL dans résultats de l’action


Les exemples précédents ont montré l’utilisation IUrlHelper dans un contrôleur.
L’utilisation la plus courante dans un contrôleur consiste à générer une URL dans le
cadre du résultat d’une action.

Les classes de base ControllerBase et Controller fournissent des méthodes pratiques


pour les résultats d’action qui référencent une autre action. Une utilisation typique est
de rediriger après acceptation de l’entrée utilisateur :

C#

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
if (ModelState.IsValid)
{
// Update DB with new details.
ViewData["Message"] = $"Successful edit of customer {id}";
return RedirectToAction("Index");
}
return View(customer);
}

Les méthodes de fabrique de résultats d’action comme RedirectToAction et


CreatedAtAction suivent un modèle similaire aux méthodes sur IUrlHelper .

Cas spécial pour les routes conventionnelles dédiées


Le routage conventionnel peut utiliser un type spécial de définition de route appelé
route conventionnelle dédiée. Dans l’exemple suivant, la route nommée blog est une
route conventionnelle dédiée :

C#

app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");

À l’aide des définitions de route précédentes, Url.Action("Index", "Home") génère le


chemin d’accès / d’URL à l’aide de l’itinéraire default , mais pourquoi? Vous pouvez
deviner que les valeurs de route { controller = Home, action = Index } seraient
suffisantes pour générer une URL avec blog , et que le résultat serait /blog?
action=Index&controller=Home .

Les routes conventionnelles dédiées s’appuient sur un comportement spécial des


valeurs par défaut qui n’ont pas de paramètre de route correspondant qui empêche la
route d’être trop globale avec la génération d’URL. Dans ce cas, les valeurs par défaut
sont { controller = Blog, action = Article } , et ni controller ni action
n’apparaissent comme paramètre de route. Quand le routage effectue une génération
d’URL, les valeurs fournies doivent correspondre aux valeurs par défaut. La génération
d’URL avec blog échoue, car les valeurs { controller = Home, action = Index } ne
correspondent pas à { controller = Blog, action = Article } . Le routage essaye alors
d’utiliser default , ce qui réussit.

Zones (Areas)
Les zones sont une fonctionnalité MVC utilisée pour organiser des fonctionnalités
connexes dans un groupe distinct :
Espace de noms de routage pour les actions du contrôleur.
Structure des dossiers pour les vues.

L’utilisation de zones permet à une application d’avoir plusieurs contrôleurs portant le


même nom, pour autant qu’ils soient dans des zones différentes. L’utilisation de zones
crée une hiérarchie qui permet le routage par ajout d’un autre paramètre de route,
area , à controller et à action . Cette section explique comment le routage interagit

avec les zones. Voir Zones pour plus de détails sur l’utilisation des zones dans les vues.

L’exemple suivant configure MVC pour utiliser la route conventionnelle par défaut et
une area route area nommée Blog :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapAreaControllerRoute("blog_route", "Blog",
"Manage/{controller}/{action}/{id?}");
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");

app.Run();

Dans le code précédent, MapAreaControllerRoute est appelé pour créer le


"blog_route" . Le deuxième paramètre, "Blog" , est le nom de la zone.

Lors de la correspondance d’un chemin d’URL comme /Manage/Users/AddUser , la route


"blog_route" génère les valeurs de route { area = Blog, controller = Users, action =

AddUser } . La valeur de route area est générée par une valeur par défaut pour area . La

route créée par MapAreaControllerRoute est équivalente à la suivante :

C#
app.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
defaults: new { area = "Blog" }, constraints: new { area = "Blog"
});
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");

MapAreaControllerRoute crée une route avec à la fois une valeur par défaut et une

contrainte pour area en utilisant le nom de la zone fournie, dans ce cas Blog . La valeur
par défaut garantit que la route produit toujours { area = Blog, ... } , et la contrainte
nécessite la valeur { area = Blog, ... } pour la génération d’URL.

Le routage conventionnel est dépendant de l’ordre. En général, les routes avec des
zones doivent être placées plus haut, car elles sont plus spécifiques que les routes sans
zone.

Avec l’exemple précédent, les valeurs de route { area = Blog, controller = Users,
action = AddUser } sont mises en correspondance avec l’action suivante :

C#

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
// GET /manage/users/adduser
public IActionResult AddUser()
{
var area =
ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName =
ControllerContext.ActionDescriptor.ControllerName;

return Content($"area name:{area}" +


$" controller:{controllerName} action name: {actionName}");
}
}
}

L’attribut [Zone] indique qu’un contrôleur fait partie d’une zone. Ce contrôleur se trouve
dans la zone Blog . Les contrôleurs sans attribut [Area] ne sont membres d’aucune zone
et ne sont pas trouvés en correspondance quand la valeur de route area est fournie par
le routage. Dans l’exemple suivant, seul le premier contrôleur répertorié peut
correspondre aux valeurs de route { area = Blog, controller = Users, action =
AddUser } .

C#

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
[Area("Blog")]
public class UsersController : Controller
{
// GET /manage/users/adduser
public IActionResult AddUser()
{
var area =
ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName =
ControllerContext.ActionDescriptor.ControllerName;

return Content($"area name:{area}" +


$" controller:{controllerName} action name: {actionName}");
}
}
}

C#

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
// Matches { area = Zebra, controller = Users, action = AddUser }
[Area("Zebra")]
public class UsersController : Controller
{
// GET /zebra/users/adduser
public IActionResult AddUser()
{
var area =
ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName =
ControllerContext.ActionDescriptor.ControllerName;

return Content($"area name:{area}" +


$" controller:{controllerName} action name: {actionName}");
}
}
}
C#

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace3
{
// Matches { area = string.Empty, controller = Users, action = AddUser }
// Matches { area = null, controller = Users, action = AddUser }
// Matches { controller = Users, action = AddUser }
public class UsersController : Controller
{
// GET /users/adduser
public IActionResult AddUser()
{
var area =
ControllerContext.ActionDescriptor.RouteValues["area"];
var actionName = ControllerContext.ActionDescriptor.ActionName;
var controllerName =
ControllerContext.ActionDescriptor.ControllerName;

return Content($"area name:{area}" +


$" controller:{controllerName} action name: {actionName}");
}
}
}

L’espace de noms de chaque contrôleur est affiché ici pour l’exhaustivité. Si les
contrôleurs précédents utilisaient le même espace de noms, une erreur du compilateur
serait générée. Les espaces de noms de classe n’ont pas d’effet sur le routage de MVC.

Les deux premiers contrôleurs sont membres de zones, et ils sont trouvés en
correspondance seulement quand le nom de leur zone respective est fourni par la valeur
de route area . Le troisième contrôleur n’est membre d’aucune zone et peut être trouvé
en correspondance seulement quand aucune valeur pour area n’est fournie par le
routage.

En termes de mise en correspondance avec aucune valeur, l’absence de la valeur area


est identique à une valeur null ou de chaîne vide pour area .

Lors de l’exécution d’une action à l’intérieur d’une zone, la valeur de route pour area est
disponible en tant que valeur ambiante, que le routage peut utiliser pour la génération
d’URL. Cela signifie que par défaut, les zones agissent par attraction pour la génération
d’URL, comme le montre l’exemple suivant.

C#

app.MapAreaControllerRoute(name: "duck_route",
areaName: "Duck",
pattern:
"Manage/{controller}/{action}/{id?}");
app.MapControllerRoute(name: "default",
pattern:
"Manage/{controller=Home}/{action=Index}/{id?}");

C#

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace4
{
[Area("Duck")]
public class UsersController : Controller
{
// GET /Manage/users/GenerateURLInArea
public IActionResult GenerateURLInArea()
{
// Uses the 'ambient' value of area.
var url = Url.Action("Index", "Home");
// Returns /Manage/Home/Index
return Content(url);
}

// GET /Manage/users/GenerateURLOutsideOfArea
public IActionResult GenerateURLOutsideOfArea()
{
// Uses the empty value for area.
var url = Url.Action("Index", "Home", new { area = "" });
// Returns /Manage
return Content(url);
}
}
}

Le code suivant génère une URL vers /Zebra/Users/AddUser :

C#

public class HomeController : Controller


{
public IActionResult About()
{
var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
return Content($"URL: {url}");
}

Définition d’action
Les méthodes publiques sur un contrôleur, sauf celles qui sont avec l’attribut NonAction,
sont des actions.

Exemple de code
MyDisplayRouteInfo est fourni par le package NuGet
Rick.Docs.Samples.RouteInfo et affiche les informations de routage.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Déboguer les diagnostics


Pour obtenir une sortie de diagnostic de routage détaillée, définissez
Logging:LogLevel:Microsoft sur Debug . Dans l’environnement de développement,

définissez le niveau de journal dans appsettings.Development.json :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Injection de dépendances dans les
contrôleurs dans ASP.NET Core
Article • 30/11/2023

Par Shadi Alnamrouti et Rick Anderson

Les contrôleurs ASP.NET Core MVC demandent les dépendances explicitement via des
constructeurs. ASP.NET Core offre une prise en charge intégrée de l’injection de
dépendances. L’injection de dépendances facilite le test et la maintenance des
applications.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Injection de constructeurs
Les services sont ajoutés sous forme de paramètre de constructeur, et le runtime résout
les services à partir du conteneur de services. Les services sont généralement définis à
partir d’interfaces. Par exemple, prenons le cas d’une application qui a besoin de l’heure
actuelle. L’interface suivante expose le service IDateTime :

C#

public interface IDateTime


{
DateTime Now { get; }
}

Le code suivant implémente l’interface IDateTime :

C#

public class SystemDateTime : IDateTime


{
public DateTime Now
{
get { return DateTime.Now; }
}
}

Ajoutez le service au conteneur de services :

C#
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IDateTime, SystemDateTime>();

services.AddControllersWithViews();
}

Pour plus d’informations sur AddSingleton, consultez Durée de vie des services
d’injonction de dépendances.

Le code suivant adresse une salutation à l’utilisateur qui varie en fonction de l’heure du
jour :

C#

public class HomeController : Controller


{
private readonly IDateTime _dateTime;

public HomeController(IDateTime dateTime)


{
_dateTime = dateTime;
}

public IActionResult Index()


{
var serverTime = _dateTime.Now;
if (serverTime.Hour < 12)
{
ViewData["Message"] = "It's morning here - Good Morning!";
}
else if (serverTime.Hour < 17)
{
ViewData["Message"] = "It's afternoon here - Good Afternoon!";
}
else
{
ViewData["Message"] = "It's evening here - Good Evening!";
}
return View();
}

Exécutez l’application et un message s’affiche en fonction de l’heure.

Injection d’action avec FromServices


FromServices
FromServicesAttribute permet d’injecter un service directement dans une méthode
d’action sans utiliser l’injection de constructeurs :

C#

public IActionResult About([FromServices] IDateTime dateTime)


{
return Content( $"Current server time: {dateTime.Now}");
}

Injection d’action avec FromServices


FromKeyedServices
Le code suivant montre comment accéder aux services à clé d’accès à partir du
conteneur d’injection de dépendances (DI) en utilisant l’attribut [FromKeyedServices] :

C#

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();

var app = builder.Build();

app.MapControllers();

app.Run();

public interface ICache


{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache


{
public object Get(string key) => $"Resolving {key} from small cache.";
}

[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big")]
public ActionResult<object> GetBigCache([FromKeyedServices("big")]
ICache cache)
{
return cache.Get("data-mvc");
}

[HttpGet("small")]
public ActionResult<object> GetSmallCache([FromKeyedServices("small")]
ICache cache)
{
return cache.Get("data-mvc");
}
}

Accéder aux paramètres à partir d’un


contrôleur
L’accès aux paramètres de configuration ou d’application à partir d’un contrôleur est un
modèle commun. Le modèle d'options décrit dans Modèle d'options dans ASP.NET Core
est l'approche préférée pour gérer les paramètres. En règle générale, n’injectez pas
directement IConfiguration dans un contrôleur.

Créez une classe qui représente les options. Par exemple :

C#

public class SampleWebSettings


{
public string Title { get; set; }
public int Updates { get; set; }
}

Ajoutez la classe de configuration à la collection de services :

C#

public void ConfigureServices(IServiceCollection services)


{
services.AddSingleton<IDateTime, SystemDateTime>();
services.Configure<SampleWebSettings>(Configuration);

services.AddControllersWithViews();
}

Configurez l'application pour lire les paramètres à partir d'un JSfichier au format ON :
C#

public class Program


{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>


Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("samplewebsettings.json",
optional: false,
reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}

Le code suivant demande les paramètres IOptions<SampleWebSettings> au conteneur de


services et les utilise dans la méthode Index :

C#

public class SettingsController : Controller


{
private readonly SampleWebSettings _settings;

public SettingsController(IOptions<SampleWebSettings> settingsOptions)


{
_settings = settingsOptions.Value;
}

public IActionResult Index()


{
ViewData["Title"] = _settings.Title;
ViewData["Updates"] = _settings.Updates;
return View();
}
}

Ressources supplémentaires
Consultez Tester la logique du contrôleur dans ASP.NET Core pour savoir comment
faciliter le test du code en demandant explicitement des dépendances dans les
contrôleurs.
Prise en charge du conteneur d’injection de dépendances de service à clé
Remplacez le conteneur d’injection de dépendances par défaut par une
implémentation tierce.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Injection de dépendances dans les vues
dans ASP.NET Core
Article • 30/11/2023

ASP.NET Core prend en charge l’injection de dépendances dans les vues. Cette
fonctionnalité peut être utile pour les services spécifiques à une vue, notamment la
localisation ou les données requises uniquement pour remplir les éléments de la vue. La
plupart des affichages de vues de données doivent être passés par le contrôleur.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Injection de configuration
Les valeurs dans les fichiers de paramètres, comme appsettings.json et
appsettings.Development.json , peuvent être injectées dans une vue. Considérez le
appsettings.Development.json à partir de l’exemple de code :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"MyRoot": {
"MyParent": {
"MyChildName": "Joe"
}
}
}

Le balisage suivant affiche la valeur de la configuration dans un mode Page Razor :

CSHTML

@page
@model PrivacyModel
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
ViewData["Title"] = "Privacy RP";
}
<h1>@ViewData["Title"]</h1>
<p>PR Privacy</p>

<h2>
MyRoot:MyParent:MyChildName:
@Configuration["MyRoot:MyParent:MyChildName"]
</h2>

Le balisage suivant affiche la valeur de la configuration dans une vue MVC :

CSHTML

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
ViewData["Title"] = "Privacy MVC";
}
<h1>@ViewData["Title"]</h1>

<p>MVC Use this page to detail your site's privacy policy.</p>

<h2>
MyRoot:MyParent:MyChildName:
@Configuration["MyRoot:MyParent:MyChildName"]
</h2>

Pour plus d’informations, consultez Configuration dans ASP.NET Core

Injection de service
Un service peut être injecté dans une vue en utilisant la directive @inject .

CSHTML

@using System.Threading.Tasks
@using ViewInjectSample.Model
@using ViewInjectSample.Model.Services
@model IEnumerable<ToDoItem>
@inject StatisticsService StatsService
<!DOCTYPE html>
<html>
<head>
<title>To Do Items</title>
</head>
<body>
<div>
<h1>To Do Items</h1>
<ul>
<li>Total Items: @StatsService.GetCount()</li>
<li>Completed: @StatsService.GetCompletedCount()</li>
<li>Avg. Priority: @StatsService.GetAveragePriority()</li>
</ul>
<table>
<tr>
<th>Name</th>
<th>Priority</th>
<th>Is Done?</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>@item.Name</td>
<td>@item.Priority</td>
<td>@item.IsDone</td>
</tr>
}
</table>
</div>
</body>
</html>

Cette vue affiche une liste d’instances ToDoItem et un récapitulatif de statistiques


générales. Le récapitulatif est rempli avec les données du service StatisticsService
injecté. Ce service est inscrit pour l’injection de dépendances sous ConfigureServices
dans Program.cs :

C#

using ViewInjectSample.Helpers;
using ViewInjectSample.Infrastructure;
using ViewInjectSample.Interfaces;
using ViewInjectSample.Model.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

builder.Services.AddTransient<IToDoItemRepository, ToDoItemRepository>();
builder.Services.AddTransient<StatisticsService>();
builder.Services.AddTransient<ProfileOptionsService>();
builder.Services.AddTransient<MyHtmlHelper>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.MapRazorPages();

app.MapDefaultControllerRoute();

app.Run();

StatisticsService effectue des calculs sur l’ensemble des instances ToDoItem , auquel il

accède par le biais d’un référentiel :

C#

using System.Linq;
using ViewInjectSample.Interfaces;

namespace ViewInjectSample.Model.Services
{
public class StatisticsService
{
private readonly IToDoItemRepository _toDoItemRepository;

public StatisticsService(IToDoItemRepository toDoItemRepository)


{
_toDoItemRepository = toDoItemRepository;
}

public int GetCount()


{
return _toDoItemRepository.List().Count();
}

public int GetCompletedCount()


{
return _toDoItemRepository.List().Count(x => x.IsDone);
}

public double GetAveragePriority()


{
if (_toDoItemRepository.List().Count() == 0)
{
return 0.0;
}

return _toDoItemRepository.List().Average(x => x.Priority);


}
}
}
Le référentiel de l’exemple utilise une collection en mémoire. Une implémentation en
mémoire ne doit pas être utilisée pour les jeux de données volumineux et accessibles à
distance.

L’exemple affiche les données fournies par le modèle lié à la vue et le service injecté
dans la vue :

Remplissage des données de recherche


L’injection dans les vues peut être utile pour remplir certaines options dans les éléments
d’interface utilisateur, telles que les listes déroulantes. Prenons l’exemple d’un formulaire
de profil utilisateur qui comporte des options permettant de spécifier le sexe, l’État et
d’autres préférences. Le rendu d’un tel formulaire avec une approche standard peut
nécessiter le contrôleur ou la page Razor pour :

Demander des services d’accès aux données pour chaque ensemble d’options.
Remplissez un modèle ou ViewBag avec chacun des ensembles d’options à lier.

Une autre approche consiste à injecter les services directement dans la vue pour obtenir
les options. Cela réduit la quantité de code requis par le contrôleur ou la page razor, car
la logique de construction de cet élément de vue est déplacée dans la vue proprement
dite. Pour afficher un formulaire de modification de profil, il suffit ainsi au contrôleur ou
à la page Razor de passer l’instance de profil au formulaire :

C#

using Microsoft.AspNetCore.Mvc;
using ViewInjectSample.Model;
namespace ViewInjectSample.Controllers;

public class ProfileController : Controller


{
public IActionResult Index()
{
// A real app would up profile based on the user.
var profile = new Profile()
{
Name = "Rick",
FavColor = "Blue",
Gender = "Male",
State = new State("Ohio","OH")
};
return View(profile);
}
}

Le formulaire HTML utilisé pour mettre à jour les préférences contient des listes
déroulantes pour trois des propriétés :

Ces listes sont remplies par un service qui a été injecté dans la vue :

CSHTML

@using System.Threading.Tasks
@using ViewInjectSample.Model.Services
@model ViewInjectSample.Model.Profile
@inject ProfileOptionsService Options
<!DOCTYPE html>
<html>
<head>
<title>Update Profile</title>
</head>
<body>
<div>
<h1>Update Profile</h1>
Name: @Html.TextBoxFor(m => m.Name)
<br/>
Gender: @Html.DropDownList("Gender",
Options.ListGenders().Select(g =>
new SelectListItem() { Text = g, Value = g }))
<br/>

State: @Html.DropDownListFor(m => m.State!.Code,


Options.ListStates().Select(s =>
new SelectListItem() { Text = s.Name, Value = s.Code}))
<br />

Fav. Color: @Html.DropDownList("FavColor",


Options.ListColors().Select(c =>
new SelectListItem() { Text = c, Value = c }))
</div>
</body>
</html>

ProfileOptionsService est un service au niveau de l’interface utilisateur qui est conçu

pour fournir uniquement les données nécessaires dans ce formulaire :

C#

namespace ViewInjectSample.Model.Services;

public class ProfileOptionsService


{
public List<string> ListGenders()
{
// Basic sample
return new List<string>() {"Female", "Male"};
}

public List<State> ListStates()


{
// Add a few states
return new List<State>()
{
new State("Alabama", "AL"),
new State("Alaska", "AK"),
new State("Ohio", "OH")
};
}

public List<string> ListColors()


{
return new List<string>() { "Blue","Green","Red","Yellow" };
}
}

Un type non inscrit lève une exception au moment de l’exécution, car le fournisseur de
services est interrogé en interne à travers GetRequiredService.
Substitution de services
En plus d’être une technique pouvant servir à injecter de nouveaux services, l’injection
de service permet de substituer des services ayant déjà été injectés dans une page. La
capture d’écran ci-dessous montre tous les champs disponibles dans la page utilisée
dans le premier exemple :

Les champs par défaut sont Html , Component et Url . Pour remplacer les assistants HTML
par défaut par une version personnalisée, utilisez @inject :

CSHTML

@using System.Threading.Tasks
@using ViewInjectSample.Helpers
@inject MyHtmlHelper Html
<!DOCTYPE html>
<html>
<head>
<title>My Helper</title>
</head>
<body>
<div>
Test: @Html.Value
</div>
</body>
</html>

Voir aussi
Blog de Simon Timms : Getting Lookup Data Into Your View

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.
 Indiquer des commentaires sur
le produit
Logique du contrôleur de test unitaire
dans ASP.NET Core
Article • 30/11/2023

Par Steve Smith

Les tests unitaires impliquent le test d’une partie d’une application de façon isolée par
rapport à son infrastructure et à ses dépendances. Lors du test de la logique d’un
contrôleur, seuls les contenus d’une action sont testés, et non pas le comportement de
ses dépendances ou du framework lui-même.

Contrôleurs de test unitaire


Configurez des tests unitaires d’actions de contrôleur pour qu’ils s’attachent
uniquement au comportement du contrôleur. Un test unitaire de contrôleur évite des
scénarios, tels que les filtres, le routage et la liaison de données. Les tests couvrant les
interactions entre les composants qui répondent collectivement à une requête sont
gérés par des tests d’intégration. Pour plus d’informations sur les tests d’intégration,
consultez Tests d’intégration dans ASP.NET Core.

Si vous écrivez des routes et des filtres personnalisés, testez-les de manière isolée avec
des tests unitaires plutôt que dans le cadre de tests exécutés sur une action de
contrôleur spécifique.

Pour illustrer les tests unitaires de contrôleur, examinez de plus près le contrôleur
suivant dans l’exemple d’application.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Le contrôleur Home affiche une liste de sessions de brainstorming et permet la création


de nouvelles sessions avec une requête POST :

C#

public class HomeController : Controller


{
private readonly IBrainstormSessionRepository _sessionRepository;

public HomeController(IBrainstormSessionRepository sessionRepository)


{
_sessionRepository = sessionRepository;
}
public async Task<IActionResult> Index()
{
var sessionList = await _sessionRepository.ListAsync();

var model = sessionList.Select(session => new


StormSessionViewModel()
{
Id = session.Id,
DateCreated = session.DateCreated,
Name = session.Name,
IdeaCount = session.Ideas.Count
});

return View(model);
}

public class NewSessionModel


{
[Required]
public string SessionName { get; set; }
}

[HttpPost]
public async Task<IActionResult> Index(NewSessionModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
else
{
await _sessionRepository.AddAsync(new BrainstormSession()
{
DateCreated = DateTimeOffset.Now,
Name = model.SessionName
});
}

return RedirectToAction(actionName: nameof(Index));


}
}

Le contrôleur précédent :

Suit le Principe des dépendances explicites.


Attend que l’injection de dépendance fournisse une instance de
IBrainstormSessionRepository .

Peut être testé avec un service IBrainstormSessionRepository fictif au moyen d’un


framework d’objets fictifs, tel que Moq . Un objet fictif est un objet fabriqué avec
un ensemble prédéterminé de comportements de propriété et de méthode utilisés
pour les tests. Pour plus d’informations, consultez Introduction aux tests
d’intégration.

La méthode HTTP GET Index n’a pas de boucle ni de branchement, et elle appelle
seulement une méthode. Le test unitaire pour cette action :

Simule le service IBrainstormSessionRepository à l’aide de la méthode


GetTestSessions . GetTestSessions crée deux sessions de brainstorming fictives

avec des dates et des noms de session.


Exécute la méthode Index .
Fait des assertions sur le résultat retourné par la méthode :
Un ViewResult est retourné.
Le ViewDataDictionary.Model est un StormSessionViewModel .
Deux sessions de brainstorming sont stockées dans le
ViewDataDictionary.Model .

C#

[Fact]
public async Task Index_ReturnsAViewResult_WithAListOfBrainstormSessions()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync())
.ReturnsAsync(GetTestSessions());
var controller = new HomeController(mockRepo.Object);

// Act
var result = await controller.Index();

// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<StormSessionViewModel>>(
viewResult.ViewData.Model);
Assert.Equal(2, model.Count());
}

C#

private List<BrainstormSession> GetTestSessions()


{
var sessions = new List<BrainstormSession>();
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 2),
Id = 1,
Name = "Test One"
});
sessions.Add(new BrainstormSession()
{
DateCreated = new DateTime(2016, 7, 1),
Id = 2,
Name = "Test Two"
});
return sessions;
}

Les tests de la méthode HTTP POST Index du contrôleur Home s’assurent que :

Lorsque ModelState.IsValid est false , la méthode d’action retourne un ViewResult


de Requête incorrecte 400 avec les données appropriées.
Lorsque ModelState.IsValid est true :
La méthode Add sur le dépôt est appelée.
Un RedirectToActionResult est retourné avec les arguments corrects.

Un état de modèle non valide est testé en ajoutant des erreurs avec AddModelError,
comme le montre le premier test ci-dessous :

C#

[Fact]
public async Task
IndexPost_ReturnsBadRequestResult_WhenModelStateIsInvalid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.ListAsync())
.ReturnsAsync(GetTestSessions());
var controller = new HomeController(mockRepo.Object);
controller.ModelState.AddModelError("SessionName", "Required");
var newSession = new HomeController.NewSessionModel();

// Act
var result = await controller.Index(newSession);

// Assert
var badRequestResult = Assert.IsType<BadRequestObjectResult>(result);
Assert.IsType<SerializableError>(badRequestResult.Value);
}

[Fact]
public async Task
IndexPost_ReturnsARedirectAndAddsSession_WhenModelStateIsValid()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.AddAsync(It.IsAny<BrainstormSession>()))
.Returns(Task.CompletedTask)
.Verifiable();
var controller = new HomeController(mockRepo.Object);
var newSession = new HomeController.NewSessionModel()
{
SessionName = "Test Name"
};

// Act
var result = await controller.Index(newSession);

// Assert
var redirectToActionResult = Assert.IsType<RedirectToActionResult>
(result);
Assert.Null(redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
mockRepo.Verify();
}

Lorsque ModelState n’est pas valide, le même ViewResult est retourné, comme pour
une requête GET. Le test ne tente pas de passer un modèle non valide. Le passage d’un
modèle non valide n’est pas une approche admise, car la liaison de modèle n’est pas en
cours d’exécution (même si un test d’intégration utilise bien la liaison de modèle). Dans
ce cas, la liaison de modèle n’est pas testée. Ces tests unitaires testent seulement le
code dans la méthode d’action.

Le second test vérifie cela quand le ModelState est valide :

Un nouveau BrainstormSession est ajouté (par le biais du référentiel).


La méthode retourne un RedirectToActionResult avec les propriétés attendues.

Les appels fictifs qui ne sont pas appelés sont normalement ignorés, mais l’appel de
Verifiable à la fin de l’appel de configuration autorise la validation fictive dans le test.

Ceci est effectué avec l’appel à mockRepo.Verify , qui échoue au test si la méthode
attendue n’a pas été appelée.

7 Notes

La bibliothèque Moq utilisée dans cet exemple permet la combinaison d’éléments


fictifs vérifiables (ou « stricts ») avec des éléments fictifs non vérifiables (également
appelés stubs ou éléments « lâches »). Découvrez plus d’informations sur la
personnalisation des éléments fictifs avec Moq .

Dans l’exemple d’application, SessionController affiche des informations relatives à


une session de brainstorming. Le contrôleur inclut la logique pour traiter des valeurs id
non valides (il existe deux scénarios return dans l’exemple suivant pour couvrir ces
scénarios). La dernière instruction return retourne un nouveau StormSessionViewModel
dans l’affichage ( Controllers/SessionController.cs ) :

C#

public class SessionController : Controller


{
private readonly IBrainstormSessionRepository _sessionRepository;

public SessionController(IBrainstormSessionRepository sessionRepository)


{
_sessionRepository = sessionRepository;
}

public async Task<IActionResult> Index(int? id)


{
if (!id.HasValue)
{
return RedirectToAction(actionName: nameof(Index),
controllerName: "Home");
}

var session = await _sessionRepository.GetByIdAsync(id.Value);


if (session == null)
{
return Content("Session not found.");
}

var viewModel = new StormSessionViewModel()


{
DateCreated = session.DateCreated,
Name = session.Name,
Id = session.Id
};

return View(viewModel);
}
}

Les tests unitaires comportent un test pour chaque scénario return dans l’action Index
du contrôleur Session :

C#

[Fact]
public async Task IndexReturnsARedirectToIndexHomeWhenIdIsNull()
{
// Arrange
var controller = new SessionController(sessionRepository: null);

// Act
var result = await controller.Index(id: null);

// Assert
var redirectToActionResult =
Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Home", redirectToActionResult.ControllerName);
Assert.Equal("Index", redirectToActionResult.ActionName);
}

[Fact]
public async Task
IndexReturnsContentWithSessionNotFoundWhenSessionNotFound()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new SessionController(mockRepo.Object);

// Act
var result = await controller.Index(testSessionId);

// Assert
var contentResult = Assert.IsType<ContentResult>(result);
Assert.Equal("Session not found.", contentResult.Content);
}

[Fact]
public async Task IndexReturnsViewResultWithStormSessionViewModel()
{
// Arrange
int testSessionId = 1;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSessions().FirstOrDefault(
s => s.Id == testSessionId));
var controller = new SessionController(mockRepo.Object);

// Act
var result = await controller.Index(testSessionId);

// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsType<StormSessionViewModel>(
viewResult.ViewData.Model);
Assert.Equal("Test One", model.Name);
Assert.Equal(2, model.DateCreated.Day);
Assert.Equal(testSessionId, model.Id);
}

En passant au contrôleur Ideas, l’application expose des fonctionnalités, comme une API
web sur la route api/ideas :
Une liste d’idées ( IdeaDTO ) associées à une session de brainstorming est retournée
par la méthode ForSession .
La méthode Create ajoute de nouvelles idées à une session.

C#

[HttpGet("forsession/{sessionId}")]
public async Task<IActionResult> ForSession(int sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);
if (session == null)
{
return NotFound(sessionId);
}

var result = session.Ideas.Select(idea => new IdeaDTO()


{
Id = idea.Id,
Name = idea.Name,
Description = idea.Description,
DateCreated = idea.DateCreated
}).ToList();

return Ok(result);
}

[HttpPost("create")]
public async Task<IActionResult> Create([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var session = await _sessionRepository.GetByIdAsync(model.SessionId);


if (session == null)
{
return NotFound(model.SessionId);
}

var idea = new Idea()


{
DateCreated = DateTimeOffset.Now,
Description = model.Description,
Name = model.Name
};
session.AddIdea(idea);

await _sessionRepository.UpdateAsync(session);

return Ok(session);
}
Évitez de retourner des entités de domaine métier directement via des appels d’API. Les
entités de domaine :

Incluent souvent plus de données que ce dont a besoin le client.


Couplent inutilement le modèle de domaine interne de l’application à l’API
exposée publiquement.

Le mappage entre des entités de domaine et les types retournés au client peut être
effectué :

Manuellement avec un Select LINQ, comme l’utilise l’exemple d’application. Pour


plus d’informations, consultez LINQ (Language-Integrated Query).
Automatiquement avec une bibliothèque, telle que AutoMapper .

Ensuite, l’exemple d’application décrit des tests unitaires pour les méthodes d’API
Create et ForSession du contrôleur Ideas.

L’exemple d’application contient deux tests ForSession . Le premier test détermine si


ForSession retourne un NotFoundObjectResult (HTTP Non trouvé) pour une session

non valide :

C#

[Fact]
public async Task ForSession_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new IdeasController(mockRepo.Object);

// Act
var result = await controller.ForSession(testSessionId);

// Assert
var notFoundObjectResult = Assert.IsType<NotFoundObjectResult>(result);
Assert.Equal(testSessionId, notFoundObjectResult.Value);
}

La deuxième test ForSession détermine si ForSession retourne une liste d’idées de


session ( <List<IdeaDTO>> ) pour une session valide. Les vérifications contrôlent
également la première idée pour confirmer que sa propriété Name est correcte :

C#
[Fact]
public async Task ForSession_ReturnsIdeasForSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSession());
var controller = new IdeasController(mockRepo.Object);

// Act
var result = await controller.ForSession(testSessionId);

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnValue = Assert.IsType<List<IdeaDTO>>(okResult.Value);
var idea = returnValue.FirstOrDefault();
Assert.Equal("One", idea.Name);
}

Pour tester le comportement de la méthode Create quand le ModelState n’est pas


valide, l’exemple d’application ajoute une erreur de modèle au contrôleur dans le cadre
de ce test. N’essayez pas de tester la validation de modèle ou la liaison de modèle dans
des tests unitaires. Testez simplement le comportement de votre méthode d’action
quand elle est confrontée à un ModelState non valide :

C#

[Fact]
public async Task Create_ReturnsBadRequest_GivenInvalidModel()
{
// Arrange & Act
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
controller.ModelState.AddModelError("error", "some error");

// Act
var result = await controller.Create(model: null);

// Assert
Assert.IsType<BadRequestObjectResult>(result);
}

Le deuxième test de Create dépend du retour de null par le dépôt, le dépôt fictif est
donc configuré pour retourner null . Il est inutile de créer une base de données de test
(dans la mémoire ou ailleurs) et de construire une requête qui retourne ce résultat. Le
test peut être effectué en une seule instruction, comme le montre l’exemple de code :
C#

[Fact]
public async Task Create_ReturnsHttpNotFound_ForInvalidSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync((BrainstormSession)null);
var controller = new IdeasController(mockRepo.Object);

// Act
var result = await controller.Create(new NewIdeaModel());

// Assert
Assert.IsType<NotFoundObjectResult>(result);
}

Le troisième test Create , Create_ReturnsNewlyCreatedIdeaForSession , vérifie que la


méthode UpdateAsync du dépôt est appelée. L’élément fictif est appelé avec Verifiable ,
puis la méthode Verify du dépôt fictif est appelée pour confirmer que la méthode
vérifiable est exécutée. La garantie que la méthode UpdateAsync a enregistré les
données ne relève pas de la responsabilité du test unitaire. Cette vérification peut être
effectuée au moyen d’un test d’intégration.

C#

[Fact]
public async Task Create_ReturnsNewlyCreatedIdeaForSession()
{
// Arrange
int testSessionId = 123;
string testName = "test name";
string testDescription = "test description";
var testSession = GetTestSession();
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(testSession);
var controller = new IdeasController(mockRepo.Object);

var newIdea = new NewIdeaModel()


{
Description = testDescription,
Name = testName,
SessionId = testSessionId
};
mockRepo.Setup(repo => repo.UpdateAsync(testSession))
.Returns(Task.CompletedTask)
.Verifiable();
// Act
var result = await controller.Create(newIdea);

// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var returnSession = Assert.IsType<BrainstormSession>(okResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnSession.Ideas.Count());
Assert.Equal(testName, returnSession.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription,
returnSession.Ideas.LastOrDefault().Description);
}

Tester ActionResult<T>
ActionResult<T> (ActionResult<TValue>) peut retourner un type dérivant de
ActionResult ou retourner un type spécifique.

L’exemple d’application comprend une méthode qui retourne un List<IdeaDTO> pour un


id de session donné. Si l’ id de session n’existe pas, le contrôleur retourne NotFound :

C#

[HttpGet("forsessionactionresult/{sessionId}")]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public async Task<ActionResult<List<IdeaDTO>>> ForSessionActionResult(int
sessionId)
{
var session = await _sessionRepository.GetByIdAsync(sessionId);

if (session == null)
{
return NotFound(sessionId);
}

var result = session.Ideas.Select(idea => new IdeaDTO()


{
Id = idea.Id,
Name = idea.Name,
Description = idea.Description,
DateCreated = idea.DateCreated
}).ToList();

return result;
}
Deux tests du contrôleur ForSessionActionResult sont inclus dans
ApiIdeasControllerTests .

Le premier test confirme que le contrôleur retourne un ActionResult , et non une liste
inexistante d’idées pour un id de session inexistant :

Le type ActionResult est ActionResult<List<IdeaDTO>> .


Le Result est un NotFoundObjectResult.

C#

[Fact]
public async Task
ForSessionActionResult_ReturnsNotFoundObjectResultForNonexistentSession()
{
// Arrange
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
var nonExistentSessionId = 999;

// Act
var result = await
controller.ForSessionActionResult(nonExistentSessionId);

// Assert
var actionResult = Assert.IsType<ActionResult<List<IdeaDTO>>>(result);
Assert.IsType<NotFoundObjectResult>(actionResult.Result);
}

Pour un id de session valide, le deuxième test confirme que la méthode retourne :

Un ActionResult avec un type List<IdeaDTO> .


ActionResult<T>.Value est un type List<IdeaDTO> .
Le premier élément dans la liste est une idée valide correspondant à l’idée stockée
dans la session fictive (obtenu en appelant GetTestSession ).

C#

[Fact]
public async Task ForSessionActionResult_ReturnsIdeasForSession()
{
// Arrange
int testSessionId = 123;
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(GetTestSession());
var controller = new IdeasController(mockRepo.Object);

// Act
var result = await controller.ForSessionActionResult(testSessionId);

// Assert
var actionResult = Assert.IsType<ActionResult<List<IdeaDTO>>>(result);
var returnValue = Assert.IsType<List<IdeaDTO>>(actionResult.Value);
var idea = returnValue.FirstOrDefault();
Assert.Equal("One", idea.Name);
}

L’exemple d’application comporte également une méthode pour créer un nouveau Idea
pour une session donnée. Le contrôleur retourne :

BadRequest pour un modèle non valide.


NotFound si la session n’existe pas.
CreatedAtAction lorsque la session est mise à jour avec la nouvelle idée.

C#

[HttpPost("createactionresult")]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
public async Task<ActionResult<BrainstormSession>>
CreateActionResult([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

var session = await _sessionRepository.GetByIdAsync(model.SessionId);

if (session == null)
{
return NotFound(model.SessionId);
}

var idea = new Idea()


{
DateCreated = DateTimeOffset.Now,
Description = model.Description,
Name = model.Name
};
session.AddIdea(idea);

await _sessionRepository.UpdateAsync(session);

return CreatedAtAction(nameof(CreateActionResult), new { id = session.Id


}, session);
}
Trois tests de CreateActionResult sont inclus dans ApiIdeasControllerTests .

Le premier test confirme qu’un BadRequest est retourné pour un modèle non valide.

C#

[Fact]
public async Task CreateActionResult_ReturnsBadRequest_GivenInvalidModel()
{
// Arrange & Act
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);
controller.ModelState.AddModelError("error", "some error");

// Act
var result = await controller.CreateActionResult(model: null);

// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>
(result);
Assert.IsType<BadRequestObjectResult>(actionResult.Result);
}

Le deuxième test vérifie qu’un NotFound est retourné si la session n’existe pas.

C#

[Fact]
public async Task
CreateActionResult_ReturnsNotFoundObjectResultForNonexistentSession()
{
// Arrange
var nonExistentSessionId = 999;
string testName = "test name";
string testDescription = "test description";
var mockRepo = new Mock<IBrainstormSessionRepository>();
var controller = new IdeasController(mockRepo.Object);

var newIdea = new NewIdeaModel()


{
Description = testDescription,
Name = testName,
SessionId = nonExistentSessionId
};

// Act
var result = await controller.CreateActionResult(newIdea);

// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>
(result);
Assert.IsType<NotFoundObjectResult>(actionResult.Result);
}

Pour un id de session valide, le dernier test confirme que :

La méthode retourne un ActionResult avec un type BrainstormSession .


ActionResult<T>.Result est un CreatedAtActionResult. CreatedAtActionResult est
analogue à une réponse Créée 201 avec un en-tête Location .
ActionResult<T>.Value est un type BrainstormSession .
L’appel fictif pour mettre à jour la session, UpdateAsync(testSession) , a été appelé.
L’appel de la méthode Verifiable est contrôlé en exécutant mockRepo.Verify()
dans les assertions.
Deux objets Idea sont retournés pour la session.
Le dernier élément ( Idea ajouté par l’appel fictif à UpdateAsync ) correspond à
newIdea ajouté à la session dans le test.

C#

[Fact]
public async Task CreateActionResult_ReturnsNewlyCreatedIdeaForSession()
{
// Arrange
int testSessionId = 123;
string testName = "test name";
string testDescription = "test description";
var testSession = GetTestSession();
var mockRepo = new Mock<IBrainstormSessionRepository>();
mockRepo.Setup(repo => repo.GetByIdAsync(testSessionId))
.ReturnsAsync(testSession);
var controller = new IdeasController(mockRepo.Object);

var newIdea = new NewIdeaModel()


{
Description = testDescription,
Name = testName,
SessionId = testSessionId
};
mockRepo.Setup(repo => repo.UpdateAsync(testSession))
.Returns(Task.CompletedTask)
.Verifiable();

// Act
var result = await controller.CreateActionResult(newIdea);

// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>
(result);
var createdAtActionResult = Assert.IsType<CreatedAtActionResult>
(actionResult.Result);
var returnValue = Assert.IsType<BrainstormSession>
(createdAtActionResult.Value);
mockRepo.Verify();
Assert.Equal(2, returnValue.Ideas.Count());
Assert.Equal(testName, returnValue.Ideas.LastOrDefault().Name);
Assert.Equal(testDescription,
returnValue.Ideas.LastOrDefault().Description);
}

Ressources supplémentaires
Tests d’intégration dans ASP.NET Core
Créer et exécuter des tests unitaires avec Visual Studio
MyTested.AspNetCore.Mvc – Bibliothèque de tests Fluent pour ASP.NET Core
MVC : bibliothèque de tests unitaires fortement typée, fournissant une interface
Fluent pour tester les applications MVC et d’API web. (Non géré ou pris en charge
par Microsoft.)
JustMockLite : framework de simulation pour les développeurs .NET. (Non géré
ou pris en charge par Microsoft.)

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
ASP.NET Core Blazor
Article • 09/02/2024

Bienvenue dans Blazor !

Blazor est une infrastructure web frontend .NET full-stack qui prend en charge à la fois le
rendu côté serveur et l'interactivité client dans un modèle de programmation unique :

Créez des interfaces utilisateur interactives enrichies à l’aide de C#.


Partagez la logique d’application côté serveur et côté client écrite dans .NET.
Affichez l’interface utilisateur en langage HTML et CSS pour une large prise en
charge des navigateurs, y compris les navigateurs mobiles.
Générez des applications de bureau et mobiles hybrides avec .NET et Blazor.

L’utilisation de .NET dans le développement web côté client offre les avantages
suivants :

Écrivez du code en C#, ce qui peut améliorer la productivité dans le


développement et la maintenance des applications.
Tirez parti de l’écosystème .NET existant des bibliothèques .NET.
Bénéficiez des performances, de la fiabilité et de la sécurité de .NET.
Restez productif sur Windows, Linux ou macOS avec un environnement de
développement tel que Visual Studio ou Visual Studio Code . Effectuez des
intégrations aux plateformes d’hébergement modernes, par exemple Docker.
Développez avec un ensemble commun de langages, de frameworks et d’outils
stables, riches en fonctionnalités et faciles à utiliser.

7 Notes

Pour accéder à un tutoriel de démarrage rapide sur Blazor, consultez Générer votre
première application Blazor .

Composants
Les applications Blazor sont basées sur des composants. Dans Blazor, un composant est
un élément d’IU, par exemple une page, une boîte de dialogue ou un formulaire
d’entrée de données.

Les composants sont des classes C# .NET intégrées dans des assemblys .NET qui :

Définissent la logique de rendu de l’interface utilisateur flexible.


Gèrent les événements de l’utilisateur.
Peuvent être imbriqués et réutilisés.
Peuvent être partagés et distribués en tant que bibliothèques de classes Razor ou
en tant que packages NuGet.

La classe de composant est généralement écrite sous la forme d’une page de balises
Razor avec l’extension de fichier .razor . Dans Blazor, les composants sont appelés
officiellement composants Razor, et officieusement composants Blazor. Razor est une
syntaxe qui combine des balises HTML à du code C# destiné à améliorer la productivité
des développeurs. Razor vous permet de basculer entre les balises HTML et C# dans le
même fichier avec la prise en charge de la programmation IntelliSense dans Visual
Studio.

Blazor utilise des balises HTML naturelles pour la composition de l’IU. Les balises Razor
suivantes illustrent un composant qui incrémente un compteur lorsque l’utilisateur
sélectionne un bouton.

razor

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Les composants s’affichent dans une représentation en mémoire du modèle DOM


(Document Object Model) du navigateur, appelée arborescence de rendu, qui permet
de mettre à jour l’IU de manière flexible et efficace.

Créer une application web de pile complète


avec Blazor
Blazor Les applications Web fournissent une architecture basée sur des composants avec
un rendu côté serveur et une interactivité complète côté client dans une seule solution,
où vous pouvez basculer entre les modes de rendu côté serveur et côté client et même
les mélanger dans la même page.

Les applications web Blazor peuvent fournir rapidement l’interface utilisateur au


navigateur en rendant statiquement du contenu HTML à partir du serveur en réponse
aux demandes. La page se charge rapidement car le rendu de l'interface utilisateur est
effectué rapidement sur le serveur sans qu'il soit nécessaire de télécharger un gros pack
JavaScript. Blazor peut également améliorer l’expérience utilisateur avec diverses
améliorations progressives du rendu du serveur, telles que la navigation améliorée avec
des billets de formulaire et le rendu en streaming de contenu généré de manière
asynchrone.

Blazor prend en charge le rendu côté serveur interactif du serveur, où les interactions de
l’interface utilisateur sont gérées à partir du serveur via une connexion en temps réel
avec le navigateur. Le rendu côté serveur interactif permet une expérience utilisateur
riche comme celle que l’on attendrait d’une application cliente, mais sans qu’il soit
nécessaire de créer des points de terminaison d’API pour accéder aux ressources du
serveur. Le contenu de page pour les pages interactives est prérendu, où le contenu sur
le serveur est initialement généré et envoyé au client sans activer les gestionnaires
d’événements pour les contrôles rendus. Le serveur génère l’interface utilisateur HTML
de la page dès que possible en réponse à la demande initiale, ce qui rend l’application
plus réactive pour les utilisateurs.

Les applications web Blazor prennent en charge l’interactivité avec le rendu côté client,
qui s’appuie sur un runtime .NET créé avec WebAssembly que vous pouvez
télécharger avec votre application. En exécutant Blazor sur WebAssembly, votre code
.NET peut accéder à toutes les fonctionnalités du navigateur et inter-opérer avec
JavaScript. Votre code .NET s’exécute dans le bac à sable de sécurité du navigateur avec
les protections offertes par le bac à sable contre les actions malveillantes sur l’ordinateur
client.

Les applications Blazor peuvent entièrement cibler l’exécution sur WebAssembly dans le
navigateur sans l’implication d’un serveur. Pour une application Blazor WebAssembly
autonome, les ressources sont déployées en tant que fichiers statiques sur un serveur
web ou un service capable de fournir un contenu statique aux clients. Une fois
téléchargées, les applications autonomes Blazor WebAssembly peuvent être mises en
cache et exécutées hors connexion en tant qu’application web progressive (PWA).

Créer une application cliente native avec Blazor


Hybrid
Blazor Hybrid permet d’utiliser des composants Razor dans une application cliente
native avec un mélange de technologies natives et web pour les plateformes web,
mobiles et de bureau. Le code s’exécute de manière native dans le processus .NET et
affiche l’interface utilisateur web dans un contrôle Web View incorporé à l’aide d’un
canal d’interopérabilité local. WebAssembly n’est pas utilisé dans les applications
hybrides. Les applications hybrides sont construites avec .NET Multi-platform App UI
(.NET MAUI), qui est une infrastructure multi-plateforme pour la création d’applications
mobiles et de bureau natives en C# et XAML.

Le Blazor Hybrid prend en charge Windows Presentation Foundation (WPF) et Windows


Forms pour assurer la transition des applications de la technologie antérieure vers .NET
MAUI.

Étapes suivantes
BlazorTutoriel : créer votre première Blazor application

ASP.NET plateformes prises en charge par CoreBlazor

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Plateformes prises en charge par
ASP.NET Core Blazor
Article • 09/02/2024

Blazor est pris en charge dans les navigateurs indiqués dans le tableau suivant sur les
plateformes mobiles et de bureau.

ノ Agrandir le tableau

Browser Version

Apple Safari Actuel†

Google Chrome Actuel†

Microsoft Edge Actuel†

Mozilla Firefox Actuel†

†Actuel fait référence à la dernière version du navigateur.

Pour Blazor Hybrid les applications, nous testons et prenons en charge les dernières
versions de contrôle de la plateforme Web View :

Microsoft Edge WebView2 sur Windows


Chrome sur Android
Safari sur iOS et macOS

Ressources supplémentaires
Modèles d’hébergement ASP.NET Core Blazor
Plateformes prises en charge SignalR par ASP.NET Core

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus
d’informations, consultez notre  Ouvrir un problème de
guide du contributeur. documentation

 Indiquer des commentaires sur


le produit
Outils pour ASP.NET Core Blazor
Article • 09/02/2024

Cet article décrit les outils permettant de créer des applications Blazor sur différentes
plateformes. Sélectionnez votre plateforme en haut de cet article.

Pour créer une application Blazor sur Windows, utilisez les instructions suivantes :

Installer la dernière version de Visual Studio avec la charge de travail


Développement web et ASP.NET.

Créez un projet à l’aide de l’un des modèles Blazor disponibles :


Application web Blazor : crée une application web Blazor qui prend en charge
le rendu interactif côté serveur (SSR interactif) et le rendu côté client (CSR). Le
modèle d’application web Blazor est recommandé pour prendre en main Blazor
et découvrir les fonctionnalités Blazor côté serveur et côté client.
Application Blazor WebAssembly autonome : crée une application web client
autonome qui peut être déployée en tant que site statique.

Cliquez sur Suivant.

Indiquez un nom de projet et vérifiez que l’emplacement est correct.

7 Notes

Les termes et concepts de rendu utilisés dans les instructions suivantes sont
présentés dans les sections suivantes de l’article de vue d’ensemble des Principes
de base :

Concepts de rendu côté serveur et client


Concepts de rendu statique et interactif
Modes de rendu

Des instructions détaillées sur les modes de rendu sont fournies par l’article sur les
modes de rendu Blazor ASP.NET Core.

Pour une application web Blazor dans la boîte de dialogue Informations


supplémentaires :

Liste déroulante Mode de rendu interactif


Le rendu interactif côté serveur (SSR interactif) est activé par défaut avec
l’option Serveur.
Pour activer uniquement l’interactivité avec le rendu côté client (CSR),
sélectionnez l’option WebAssembly.
Pour activer les deux modes de rendu interactif et la possibilité de basculer
automatiquement entre eux au moment de l’exécution, sélectionnez l’option
de mode de rendu (automatique) Automatique (Serveur et WebAssembly).
Si l’interactivité est définie sur None , l’application générée n’a aucune
interactivité. L’application est configurée uniquement pour le rendu statique
côté serveur.

Le mode de rendu interactif Automatique utilise initialement le SSR interactif


tandis que l’ensemble d’applications .NET et le runtime sont téléchargés dans le
navigateur. Une fois le runtime WebAssembly .NET activé, le mode de rendu
bascule vers le rendu WebAssembly interactif.

Par défaut, le modèle d’application web Blazor active le SSR statique et interactif
en utilisant un seul projet. Si vous activez également le CSR, le projet inclut un
projet client supplémentaire ( .Client ) pour vos composants basés sur
WebAssembly. La sortie générée du projet client est téléchargée dans le
navigateur et exécutée sur le client. Tous les composants utilisant les modes de
rendu WebAssembly ou Automatique doivent être générés à partir du projet
client.

Pour plus d’informations, consultez Modes de rendu ASP.NET Core Blazor.

Liste déroulante Emplacement d’interactivité


Par page/composant : la valeur par défaut configure l’interactivité par page
ou par composant.
Global : sélectionner cette option configure l’interactivité globalement pour
l’ensemble de l’application.

L’emplacement d’interactivité ne peut être défini que si le mode d’affichage


interactif n’est pas None et que l’authentification n’est pas activée.

Pour inclure des pages d’exemples et une disposition basée sur le style
Bootstrap, cochez la case Inclure les pages d’exemples. Désactivez cette option
pour des projets sans exemples de pages et de style Bootstrap.

Pour plus d’informations, consultez Modes de rendu ASP.NET Core Blazor.

Sélectionnez Créer.

Appuyez sur Ctrl + F5 (Windows) ou ⌘ + F5 (macOS) pour exécuter l’application.


Pour plus d’informations sur l’approbation du certificat de développement HTTPS
ASP.NET Core, consultez Appliquer HTTPS dans ASP.NET Core.

Fichier de solution Visual Studio ( .sln )


Une solution est un conteneur pour organiser un ou plusieurs projets de code associés.
Visual Studio utilise un fichier de solution ( .sln ) pour stocker les paramètres d’une
solution. Les fichiers solution utilisent un format unique et ne sont pas destinés à être
modifiés directement.

Les outils en dehors de Visual Studio peuvent interagir avec les fichiers de solution :

L’interface CLI .NET peut créer des fichiers solution et répertorier/modifier les
projets dans les fichiers de solution via la dotnet sln commande. D’autres
commandes CLI .NET utilisent le chemin d’accès du fichier solution pour diverses
commandes de publication, de test et d’empaquetage.
Visual Studio Code peut exécuter la dotnet sln commande et d’autres
commandes CLI .NET via son terminal intégré, mais n’utilise pas directement les
paramètres d’un fichier solution.

Pour plus d’informations, consultez les ressources suivantes dans la documentation de


Visual Studio :

Présentation des projets et solutions


Présentation des solutions et des projets dans Visual Studio

Utiliser Visual Studio Code pour le


développement multiplateforme Blazor
Visual Studio Code est un environnement de développement intégré (IDE)
multiplateforme open source qui peut être utilisé pour développer des applications
Blazor. Utilisez l’interface CLI .NET pour créer une application Blazor pour le
développement avec Visual Studio Code. Pour plus d’informations, consultez la Version
Linux/macOS de cet article.

Pour plus d’informations sur la configuration et l’utilisation de Visual Studio Code,


consultez la documentation de Visual Studio Code .

Options de modèle Blazor


L’infrastructure Blazor fournit des modèles pour la création d’applications. Les modèles
sont utilisés pour créer de nouveaux projets et solutions Blazor, quel que soit l’outil que
vous sélectionnez pour le développement Blazor (Visual Studio, Visual Studio Code ou
l’interface de ligne de commande (CLI).NET) :

BlazorModèle de projet Application web : blazor


Blazor WebAssembly Modèle de projet d’application autonome : blazorwasm

Pour plus d’informations sur les modèles de projet Blazor, consultez ASP.NET structure
de projet principale Blazor.

Pour plus d’informations sur les options de modèle, consultez les ressources suivantes :

L’article Modèles .NET par défaut pour dotnet new de la documentation .NET Core :
blazor
blazorwasm
Passage de l’option d’aide ( -h ou --help ) à la commande CLI dotnet new dans un
interpréteur de commandes :
dotnet new blazor -h

dotnet new blazorwasm -h

.NET WebAssembly Build Tools


Les outils de génération .NET WebAssembly sont basés sur Emscripten , une chaîne
d’outils de compilateur pour la plateforme web. Pour installer les outils de génération,
utilisez l’une des approches suivantes :

Pour la charge de travail ASP.NET et développement web dans le programme


d’installation de Visual Studio, sélectionnez l’option Outils de génération
WebAssembly .NET dans la liste des composants facultatifs.
Exécutez dotnet workload install wasm-tools dans un interpréteur de
commandes d’administration.

7 Notes

Outils de génération .NET WebAssembly pour les projets .NET 6

La charge de travail wasm-tools installe les outils de génération pour la version la


plus récente. Toutefois, la version actuelle des outils de génération est incompatible
avec les projets existants créés avec .NET 6. Les projets utilisant les outils de
génération qui doivent prendre en charge .NET 6, ou une version postérieure,
doivent utiliser le multi-ciblage.
Utilisez la charge de travail wasm-tools-net6 pour les projets .NET 6 lors du
développement d’applications avec le SDK .NET 7. Pour installer la charge de travail
wasm-tools-net6 , exécutez la commande suivante à partir d’un interpréteur de

commandes d’administration :

CLI .NET

dotnet workload install wasm-tools-net6

Pour plus d'informations, reportez-vous aux ressources suivantes :

Compilation Ahead-of-Time (AOT)


Nouvelle liaison du runtime
Dépendances natives ASP.NET Core Blazor WebAssembly

Compilation anticipée (AOT)


Pour activer la compilation anticipée (ahead-of-time/AOT), définissez
<RunAOTCompilation> sur true dans le fichier projet de l’application ( .csproj ) :

XML

<PropertyGroup>
<RunAOTCompilation>true</RunAOTCompilation>
</PropertyGroup>

Instruction unique, plusieurs données (Single Instruction,


Multiple Data/SIMD)
WebAssembly Single Instruction, Multiple Data (SIMD) peut améliorer le débit des
calculs vectorisés en effectuant une opération sur plusieurs éléments de données en
parallèle à l’aide d’une seule instruction. SIMD est activé par défaut.

Pour désactiver SIMD, par exemple lors du ciblage d’anciens navigateurs ou de


navigateurs sur des appareils mobiles qui ne prennent pas en charge SIMD, définissez la
propriété <WasmEnableSIMD> sur false dans le Fichier projet de l’application ( .csproj ) :

XML

<PropertyGroup>
<WasmEnableSIMD>false</WasmEnableSIMD>
</PropertyGroup>

Pour plus d’informations, consultez Configuration et hébergement d’applications .NET


WebAssembly : SIMD – Instruction unique, plusieurs données et notez que l’aide n’est
pas versionnée et s’applique à la dernière version publique.

Gestion des exceptions


La gestion des exceptions est activée par défaut. Pour désactiver la gestion des
exceptions, ajoutez la propriété <WasmEnableExceptionHandling> avec une valeur de
false dans le fichier projet de l’application ( .csproj ) :

XML

<PropertyGroup>
<WasmEnableExceptionHandling>false</WasmEnableExceptionHandling>
</PropertyGroup>

Ressources supplémentaires
Interface de ligne de commande (CLI) .NET
Prise en charge du rechargement à chaud .NET pour ASP.NET Core
Modèles d’hébergement ASP.NET Core Blazor
Structure de projet Blazor ASP.NET Core
Tutoriels ASP.NET Core Blazor Hybrid

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Modèles d’hébergement Blazor ASP.NET
Core
Article • 30/11/2023

Cet article explique les modèles d’hébergement Blazor, principalement axés sur les
applications Blazor Server et Blazor WebAssembly dans les versions de .NET antérieures
à .NET 8. L’aide apportée dans cet article est pertinente dans toutes les versions de .NET
pour les applications Blazor Hybrid qui s’exécutent sur des plateformes mobiles et de
bureau natives. Les applications web Blazor dans .NET 8 ou version ultérieure sont mieux
conceptualisées par l’affichage des composants Razor, qui est décrit comme leur mode
d’affichage. Les modes d’affichages sont brièvement abordés dans l’article de vue
d’ensemble des principes de base et abordés en détail dans les modes d’affichage Blazor
ASP.NET Core du nœud de composants.

Blazor est une infrastructure web permettant de créer des composants d’interface
utilisateur web (Razor composants) qui peuvent être hébergés de différentes manières.
Les composants Razor peuvent s’exécuter côté serveur dans ASP.NET Core (Blazor
Server) et côté client dans le navigateur sur un runtime .NET basé sur WebAssembly
(Blazor WebAssembly, WASM Blazor). Vous pouvez également héberger des composants
Razor dans des applications mobiles et de bureau natives qui s’affichent sur un contrôle
incorporé Web View (Blazor Hybrid). Quel que soit le modèle d’hébergement, la façon
dont vous générez les Razor composants est la même. Les mêmes composants Razor
peuvent être utilisés avec l’un des modèles d’hébergement inchangés.

Blazor Server
Avec le modèle d’hébergement Blazor Server, les composants sont exécutés sur le
serveur depuis une application ASP.NET Core. Les mises à jour de l’interface utilisateur,
la gestion des événements et les appels JavaScript sont gérés via une connexion SignalR
à l’aide du protocole WebSockets. L’état sur le serveur associé à chaque client connecté
est appelé circuit. Les circuits ne sont pas liés à une connexion réseau spécifique et
peuvent tolérer des interruptions de réseau temporaires et des tentatives du client de se
reconnecter au serveur lorsque la connexion est perdue.

Dans une application traditionnelle avec rendu serveur, l’ouverture de la même


application dans plusieurs écrans de navigateur (onglets ou iframes ) ne se traduit
généralement pas par des demandes de ressources supplémentaires sur le serveur. Pour
le modèle d’hébergement Blazor Server, chaque écran de navigateur nécessite un circuit
distinct et des instances distinctes de l’état du composant managé par le serveur. Blazor
envisage de fermer un onglet de navigateur ou d’accéder à une URL externe comme un
arrêt normal. En cas d’arrêt normal, le circuit et les ressources associées sont
immédiatement libérés. Un client peut également se déconnecter de manière non
naturelle, par exemple en raison d’une interruption du réseau. Blazor Server stocke les
circuits déconnectés pendant un intervalle configurable pour permettre au client de se
reconnecter.

Sur le client, le script Blazor établit la connexion SignalR avec le serveur. Le script est
servi à partir d’une ressource incorporée dans l’infrastructure partagée ASP.NET Core.

Le modèle d’hébergement Blazor Server offre plusieurs avantages :

La taille du téléchargement est beaucoup plus petite que lorsque le modèle


d’hébergement Blazor WebAssembly est utilisé, et l’application se charge
beaucoup plus rapidement.
L’application tire pleinement parti des fonctionnalités du serveur, notamment
l’utilisation d’API .NET Core.
.NET Core sur le serveur est utilisé pour exécuter l’application. Ainsi, les outils .NET
existants, comme le débogage, fonctionnent comme prévu.
Les clients légers sont pris en charge. Par exemple, Blazor Server fonctionne avec
les navigateurs qui ne prennent pas en charge WebAssembly et sur les appareils
limités en ressources.
La base de code .NET/C# de l’application, y compris le code du composant de
l’application, n’est pas servie aux clients.
Le modèle d’hébergement Blazor Server présente les limitations suivantes :

Une latence plus élevée existe généralement. Chaque interaction utilisateur


implique un tronçon réseau.
Il n’y a pas de prise en charge hors connexion. Si la connexion client échoue,
l’interactivité échoue.
La mise à l’échelle d’applications avec de nombreux utilisateurs nécessite des
ressources serveur pour gérer plusieurs connexions clients et l’état client.
Un serveur ASP.NET Core est requis pour servir l’application. Les scénarios de
déploiement serverless ne sont pas possibles, comme le service de l’application à
partir d’un réseau de diffusion de contenu (CDN).

Nous vous recommandons d’utiliser le service SignalR Azure pour les applications qui
adoptent le modèle d’hébergement Blazor Server. Le service permet d’effectuer un
scale-up d’une application Blazor Server à un grand nombre de connexions simultanées
SignalR.

Blazor WebAssembly
Le modèle d’hébergement Blazor WebAssembly exécute des composants côté client
dans le navigateur sur un runtime .NET basé sur WebAssembly. Les composants Razor,
leurs dépendances et le runtime .NET sont téléchargés sur le navigateur. Les
composants sont exécutés directement sur le thread d’interface utilisateur du
navigateur. Les mises à jour de l’interface utilisateur et la gestion des événements se
produisent dans le même processus. Les ressources sont déployées en tant que fichiers
statiques sur un serveur web ou un service capable de fournir un contenu statique aux
clients.
Les applications web Blazor peuvent utiliser le modèle d’hébergement Blazor
WebAssembly pour activer l’interactivité côté client. Lorsqu’une application qui s’exécute
exclusivement sur le modèle d’hébergement Blazor WebAssembly sans rendu et
interactivité côté serveur est créée, elle est appelée application Blazor
WebAssemblyautonome.

Lorsqu’une application autonome Blazor WebAssembly utilise une application


backend ASP.NET Core pour servir ses fichiers, l’application est appelée application
Blazor WebAssemblyhébergée. À l’aide de l’hébergement Blazor WebAssembly, vous
bénéficiez d’une expérience de développement web complète avec .NET, notamment la
possibilité de partager du code entre les applications client et serveur, la prise en charge
du pré-rendu et l’intégration avec MVC et Pages Razor. Une application client hébergée
peut interagir avec son application serveur back-end sur le réseau à l’aide d’une variété
d’infrastructures et de protocoles de messagerie, tels que l’API web, gRPC-web et
SignalR (Utilisez ASP.NET Core SignalR avec Blazor).

Une application Blazor WebAssembly générée en tant qu’application web progressive


(PWA) utilise des API de navigateur modernes pour activer la plupart des fonctionnalités
d’une application cliente native, telles que le fonctionnement hors connexion,
l’exécution dans sa propre fenêtre d’application, le lancement à partir du système
d’exploitation de l’hôte, la réception de notifications Push et la mise à jour automatique
en arrière-plan.
Le script Blazor gère :

Le téléchargement du runtime .NET, des composants Razor et des dépendances du


composant.
L’initialisation du runtime.

La taille de l’application publiée, c’est-à-dire sa taille de charge utile, est un facteur de


performance critique pour la facilité d’utilisation d’une application. Le téléchargement
d’une application volumineuse dans un navigateur prend un certain temps, ce qui nuit à
l’expérience utilisateur. Blazor WebAssembly optimise la taille de la charge utile pour
réduire les temps de téléchargement :

Le code inutilisé est retiré de l’application au moment de sa publication par


l’éditeur de liens IL (langage intermédiaire).
Réponses HTTP compressées.
Le runtime .NET et les assemblys sont mis en cache dans le navigateur.

Le modèle d’hébergement Blazor WebAssembly offre plusieurs avantages :

Pour les applications Blazor WebAssembly autonomes, il n’existe aucune


dépendance côté serveur .NET après le téléchargement de l’application depuis le
serveur. L’application reste donc fonctionnelle si le serveur est hors connexion.
Les ressources et les fonctionnalités clientes sont entièrement exploitées.
Le travail est déchargé du serveur vers le client.
Pour les applications autonomes Blazor WebAssembly, il n’est pas nécessaire
d’avoir un serveur web ASP.NET Core pour héberger l’application. Les scénarios de
déploiement serverless sont possibles, tels que le service de l’application à partir
d’un réseau de diffusion de contenu (CDN).

Le modèle d’hébergement Blazor WebAssembly présente les limitations suivantes :

Les composants Razor sont limités aux fonctionnalités du navigateur.


Du matériel client et des logiciels compatibles (par exemple, la prise en charge de
WebAssembly) sont requis.
La taille du téléchargement est plus grande et le chargement des composants
prend plus de temps.
Le code envoyé au client ne peut pas être protégé contre l’inspection et la
falsification par les utilisateurs.

L’interpréteur de Langage intermédiaire (IL) .NET inclut une prise en charge partielle du
runtime juste-à-temps (JAT) pour améliorer les performances du runtime. L’interpréteur
JAT optimise l’exécution des bytecodes d’interpréteur en les remplaçant par de petits
objets blob du code WebAssembly. L’interpréteur JAT est automatiquement activé pour
les applications Blazor WebAssembly, sauf lors du débogage.

Blazor prend en charge la compilation anticipée (AOT), qui vous permet de compiler
votre code .NET directement dans WebAssembly. La compilation AOT permet
d’améliorer les performances du runtime au détriment d’une plus grande taille
d’application. Pour plus d’informations, consultez Héberger et déployer ASP.NET Core
Blazor WebAssembly.

Les mêmes outils de build .NET WebAssembly utilisés pour la compilation AOT relient
également le runtime WebAssembly .NET pour découper le code inutilisé du runtime.
Blazor supprime également le code inutilisé des bibliothèques d’infrastructure .NET. Le
compilateur .NET pré-comprime en outre une application Blazor WebAssembly
autonome afin d'en réduire la charge utile.

Les composants rendus par WebAssembly Razor peuvent utiliser les dépendances
natives générées pour s’exécuter sur WebAssembly.

Blazor Hybrid
Blazor peut également être utilisé pour générer des applications clientes natives à l’aide
d’une approche hybride. Les applications hybrides sont des applications natives qui
tirent parti des technologies web pour leurs fonctionnalités. Dans une application Blazor
Hybrid, les composants Razor s’exécutent directement dans l’application native (et non
sur WebAssembly) ainsi que tout autre code .NET et affichent l’interface utilisateur web
basée sur HTML et CSS vers un contrôle incorporé Web View via un canal
d’interopérabilité local.
Les applications Blazor Hybrid peuvent être générées à l’aide de différentes
infrastructures d’applications natives .NET, notamment .NET MAUI, WPF et Windows
Forms. Blazor fournit des contrôles BlazorWebView permettant d’ajouter des composants
Razor aux applications générées avec ces infrastructures. L’utilisation de Blazor avec
.NET MAUI offre un moyen pratique de générer des applications multi-plateformes
Blazor Hybrid pour les appareils mobiles et de bureau, tandis que l’intégration Blazor
avec WPF et Windows Forms peut être un excellent moyen de moderniser des
applications existantes.

Étant donné que les applications Blazor Hybrid sont natives, elles peuvent prendre en
charge des fonctionnalités qui ne sont pas disponibles uniquement avec la plateforme
web. Les applications Blazor Hybrid ont un accès complet aux fonctionnalités de
plateforme natives via les API .NET normales. Les applications Blazor Hybrid peuvent
également partager et réutiliser des composants avec des applications Blazor Server ou
Blazor WebAssembly existantes. Les applications Blazor Hybrid combinent les avantages
du web, des applications natives et de la plateforme .NET.

Le modèle d’hébergement Blazor Hybrid offre plusieurs avantages :

Réutilisez les composants existants qui peuvent être partagés sur les appareils
mobiles, les appareils de bureau et le web.
Tirez profit des compétences, de l’expérience et des ressources de développement
web.
Les applications ont un accès complet aux fonctionnalités natives de l’appareil.

Le modèle d’hébergement Blazor Hybrid présente les limitations suivantes :

Des applications clientes natives distinctes doivent être générées, déployées et


gérées pour chaque plateforme cible.
La recherche, le téléchargement et l’installation des applications clientes natives
prennent généralement plus de temps pour accéder à une application web dans
un navigateur.

Pour plus d’informations, consultez ASP.NET Core Blazor Hybrid.

Pour plus d’informations sur les infrastructures clientes natives Microsoft, consultez les
ressources suivantes :

.NET Multi-platform App UI (.NET MAUI)


Windows Presentation Foundation (WPF)
Windows Forms
Quel modèle d’hébergement Blazor dois-je
choisir ?
Le modèle d’hébergement d’un composant est défini par son mode de rendu, soit au
moment de la compilation, soit au moment de l’exécution, qui est décrit avec des
exemples dans Modes de rendu Blazor ASP.NET Core. Le tableau suivant présente les
principales considérations relatives à la définition du mode de rendu pour déterminer le
modèle d’hébergement d’un composant. Pour les applications autonomes Blazor
WebAssembly, tous les composants de l’application sont rendus sur le client avec le
modèle d’hébergement Blazor WebAssembly.

Les applications Blazor Hybrid incluent .NET MAUI, WPF et les applications
d’infrastructure Windows Forms.

Fonctionnalité Blazor Blazor WebAssembly Blazor


Server (WASM) Hybrid

Compatibilité complète de l’API .NET ✔️ ❌ ✔️

Accès direct aux ressources serveur et réseau ✔️ ❌† ❌†

Petite taille de la charge utile avec un temps ✔️ ❌ ❌


de chargement initial rapide

Vitesse d’exécution quasi native ✔️ ✔️‡ ✔️

Code d’application sécurisé et privé sur le ✔️ ❌† ❌†


serveur

Exécuter des applications hors connexion ❌ ✔️ ✔️


une fois téléchargées

Hébergement de site statique ❌ ✔️ ❌

Décharge le traitement vers les clients ❌ ✔️ ✔️

Accès complet aux fonctionnalités du client ❌ ❌ ✔️


natif

Déploiement basé sur le web ✔️ ✔️ ❌

Les applications Blazor WebAssembly† et Blazor Hybrid peuvent utiliser des API basées
sur le serveur pour accéder aux ressources serveur/réseau et accéder au code
d’application privé et sécurisé.
‡Blazor WebAssembly atteint uniquement les performances quasi natives avec la
compilation anticipée (AOT).
Après avoir choisi le modèle d’hébergement de l’application, vous pouvez générer une
application Blazor Server ou Blazor WebAssembly à partir d’un modèle de projet Blazor.
Pour plus d’informations, consultez Outils pour ASP.NET Core Blazor.

Pour créer une application Blazor Hybrid, consultez les articles sous tutoriels Blazor
Hybrid ASP.NET Core.

Compatibilité complète de l’API .NET


Les composants rendus pour le modèle d’hébergement Blazor Server et les applications
Blazor Hybrid ont une compatibilité API .NET complète, tandis que les composants
rendus pour Blazor WebAssembly sont limités à un sous-ensemble d’API .NET. Quand la
spécification d’une application nécessite une ou plusieurs API .NET qui ne sont pas
disponibles pour les composants WebAssembly-rendered, choisissez alors de rendre des
composants pour Blazor Server ou utilisez Blazor Hybrid.

Accès direct aux ressources serveur et réseau


Les composants rendus pour le modèle d’hébergement Blazor Server ont un accès direct
aux ressources serveur et réseau où l’application s’exécute. Les composants hébergés en
utilisant Blazor WebAssembly ou Blazor Hybrid s’exécutant sur un client, ils n’ont pas
d’accès direct aux ressources serveur et réseau. Les composants peuvent accéder
indirectement aux ressources serveur et réseau via des API basées sur un serveur
protégé. Les API basées sur le serveur peuvent être disponibles via des bibliothèques,
packages et services tiers. Prenez en compte les considérations suivantes :

Les bibliothèques, packages et services tiers peuvent être coûteux à implémenter


et à gérer, être faiblement pris en charge ou présenter des risques en matière de
sécurité.
Si une ou plusieurs API basées sur le serveur sont développées en interne par votre
organisation, des ressources supplémentaires sont nécessaires pour les créer et les
gérer.

Utilisez le modèle d’hébergement Blazor Server pour éviter de devoir exposer des API
depuis l’environnement serveur.

Petite taille de la charge utile avec un temps de


chargement initial rapide
Rendre des composants depuis un serveur réduit la taille de la charge utile de
l’application et améliore les temps de chargement initiaux. Quand le temps de
chargement initial doit être court, utilisez le modèle d’hébergement Blazor Server ou
envisagez d’utiliser le rendu de serveur statique.

Vitesse d’exécution quasi native


Les applications Blazor Hybrid s’exécutent à l’aide du runtime .NET en mode natif sur la
plateforme cible, ce qui offre la meilleure vitesse possible.

Les composants rendus pour le modèle d’hébergement Blazor WebAssembly, y compris


les applications web progressives (Progressive Web Apps/PWA) et les applications
autonomes Blazor WebAssembly s’exécutent à l’aide du runtime .NET pour
WebAssembly, ce qui est plus lent que de s’exécuter directement sur la plateforme.
Envisagez d’utiliser la compilation anticipée (Ahead of Time/AOT) pour améliorer les
performances du runtime lors de l’utilisation de Blazor WebAssembly.

Code d’application sécurisé et privé sur le serveur


La gestion sécurisée et privée du code d’application sur le serveur est une fonctionnalité
intégrée des composants rendus pour le modèle d’hébergement Blazor Server. Les
composants rendus à l’aide des modèles d’hébergement Blazor WebAssembly ou Blazor
Hybrid peuvent utiliser des API basées sur serveur pour accéder aux fonctionnalités qui
doivent être conservées privées et sécurisées. Les considérations relatives au
développement et à la maintenance des API basées sur le serveur décrites dans la
section Accès direct aux ressources serveur et réseau s’appliquent. Si le développement
et la maintenance des API basées sur serveur ne sont pas souhaitables pour maintenir le
code d’application sécurisé et privé, adoptez le modèle d’hébergement Blazor Server.

Exécutez des applications hors connexion une fois


téléchargées
Les applications Blazor WebAssembly autonomes générées en tant qu’applications web
progressives (PWA) et les applications Blazor Hybrid peuvent s’exécuter hors connexion,
ce qui est particulièrement utile lorsque les clients ne sont pas en mesure de se
connecter à Internet. Les composants rendus pour le modèle d’hébergement Blazor
Server ne parviennent pas à s’exécuter lorsque la connexion au serveur est perdue. Si
une application doit s'exécuter hors connexion, Blazor WebAssembly et Blazor Hybrid
autonomes sont les plus adaptées.

Hébergement de site statique


L’hébergement de site statique est possible avec les applications Blazor WebAssembly
autonomes, car elles sont téléchargées vers les clients sous la forme d’un ensemble de
fichiers statiques. Les applications Blazor WebAssembly autonomes n’ont pas besoin de
serveur pour exécuter du code côté serveur afin de télécharger et d’exécuter, et peuvent
être remises via un réseau de distribution de contenu (Content Delivery
Network/CDN) (par exemple, Azure CDN ).

Bien que les applications Blazor Hybrid soient compilées en une ou plusieurs ressources
de déploiement autonomes, les ressources sont généralement fournies aux clients par le
biais d’une boutique d’applications tiers. Si l’hébergement statique est une exigence de
l’application, sélectionnez Blazor WebAssembly autonome.

Décharge le traitement vers les clients


Les applications Blazor WebAssembly et Blazor Hybrid s’exécutent sur les clients et
déchargent ainsi le traitement vers les clients. Les applications Blazor Server s’exécutent
sur un serveur, de sorte que la demande de ressources serveur augmente généralement
avec le nombre d’utilisateurs et la quantité de traitement requise par utilisateur. Lorsqu’il
est possible de décharger la plupart ou la totalité du traitement d’une application vers
des clients et que l’application traite une quantité importante de données, Blazor
WebAssembly ou Blazor Hybrid est le meilleur choix.

Accès complet aux fonctionnalités du client natif


Les applications Blazor Hybrid disposent d’un accès complet aux fonctionnalités de l’API
client native via les infrastructures d’application natives .NET. Dans les applications
Blazor Hybrid, les composants Razor s’exécutent directement dans l’application native,
et non sur WebAssembly . Lorsque des fonctionnalités client complètes sont requises,
Blazor Hybrid est le meilleur choix.

Déploiement basé sur le web


Les applications web Blazor sont mises à jour lors de l’actualisation suivante de
l’application depuis le navigateur.

Les applications Blazor Hybrid sont des applications clientes natives qui nécessitent
généralement un programme d’installation et un mécanisme de déploiement spécifique
à la plateforme.
Définir le modèle d’hébergement d’un
composant
Pour définir le modèle d’hébergement d’un composant sur Blazor Server ou Blazor
WebAssembly au moment de la compilation, ou dynamiquement au moment de
l’exécution, vous définissez son mode de rendu. Les modes de rendu sont entièrement
expliqués et illustrés dans l’article Modes de rendu ASP.NET CoreBlazor. Nous vous
déconseillons de passer directement de cet article à l’article Modes de rendu sans lire le
contenu des articles intermédiaires. Par exemple, les modes de rendu sont plus
facilement compris en examinant les exemples de composant Razor, mais la structure et
la fonction de base des composants Razor ne sont pas couvertes tant que l’article
Principes fondamentaux Blazor ASP.NET Core n’est pas atteint. Il est également utile
d’en savoir plus sur les modèles de projet et les outils de Blazor avant d’utiliser les
exemples de composant dans l’article Modes de rendu.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Tutoriels ASP.NET Core Blazor
Article • 30/11/2023

Les didacticiels suivants fournissent des expériences de travail de base pour la création
d'applications Blazor.

Pour une présentation de Blazor, consultez ASP.NET Core Blazor.

Créez votre première Blazor application

Créer une Blazor application de liste de tâches (Blazor Web App)

Utiliser ASP.NET Core SignalR avec Blazor (Blazor Web App)

Didacticiels ASP.NET Core Blazor Hybrid

Microsoft Learn
Blazor Parcours d'apprentissage
Blazor Modules d'apprentissage

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Générer une application de liste de
tâches Blazor
Article • 30/11/2023

Ce tutoriel fournit une expérience de travail de base pour créer et modifier une
application Blazor. Pour des conseils détaillés Blazor, consultez la Blazor documentation
de référence.

Découvrez comment :

" Créer un projet d’application Blazor de liste de tâches


" Modifier des composants Razor
" Utiliser la gestion des événements et la liaison de données dans des composants
" Utiliser le routage dans une application Blazor

À la fin de ce tutoriel, vous disposerez d’une application de liste de tâches fonctionnelle.

Prérequis
Téléchargez et installez .NET s’il n’est pas déjà installé sur le système ou si la version
installée sur celui-ci n’est pas la plus récente.

Créer une application Blazor


Créez une nouvelle Web App Blazor nommée TodoList dans un interpréteur de
commandes :

CLI .NET

dotnet new blazor -o TodoList

L’option -o|--output crée un dossier pour le projet. Si vous avez créé un dossier pour le
projet et que l’interpréteur de commandes est ouvert dans ce dossier, omettez l’option
et la valeur -o|--output pour créer le projet.

La commande précédente crée un dossier nommé TodoList avec l’option -o|--output


pour contenir l’application. Le dossier TodoList est le dossier racine du projet. Changez
de répertoire pour accéder au dossier TodoList à l’aide de la commande suivante :

CLI .NET
cd TodoList

Générer une application Blazor de liste de


tâches
Ajoutez un nouveau composant Todo Razor à l’application à l’aide de la commande
suivante :

CLI .NET

dotnet new razorcomponent -n Todo -o Components/Pages

L’option -n|--name de la commande précédente spécifie le nom du nouveau composant


Razor. Le nouveau composant est créé dans le dossier Components/Pages du projet avec
l’option -o|--output .

) Important

La première lettre des noms de fichiers des composants Razor doit être en
majuscule. Ouvrez le dossier Pages et vérifiez que le nom de fichier du composant
Todo commence par la lettre majuscule T . Le nom de fichier doit être Todo.razor .

Ouvrez le composant Todo dans n’importe quel éditeur de fichier et apportez les
modifications suivantes en haut du fichier :

Ajoutez une directive @page Razor avec une URL relative de /todo .
Activez l’interactivité sur la page afin qu’elle ne soit pas uniquement rendue
statiquement. Le mode de rendu de serveur interactif permet au composant de
gérer les évènements d’interface utilisateur depuis le serveur.
Ajoutez un titre de page avec le composant PageTitle , ce qui permet d’ajouter un
élément HTML <title> à la page.

Todo.razor :

razor
@page "/todo"
@rendermode InteractiveServer

<PageTitle>Todo</PageTitle>

<h3>Todo</h3>

@code {

Enregistrez le fichier Todo.razor .

Ajoutez le composant Todo à la barre de navigation.

Le composant NavMenu est utilisé dans la disposition de l’application. Les dispositions


sont des composants qui vous permettent d’éviter la duplication de contenu dans une
application. Le composant NavLink fournit un signal dans l’interface utilisateur de
l’application quand l’URL du composant est chargée par l’application.

Dans le contenu de l’élément de navigation ( <nav> ) du composant NavMenu , ajoutez


l’élément <div> suivant pour le composant Todo .

Dans Components/Layout/NavMenu.razor :

razor

<div class="nav-item px-3">


<NavLink class="nav-link" href="todo">
<span class="oi oi-list-rich" aria-hidden="true"></span> Todo
</NavLink>
</div>

Enregistrez le fichier NavMenu.razor .

Générez et exécutez l’application en exécutant la commande dotnet watch run dans


l’interpréteur de commandes à partir du dossier TodoList . Une fois l’application en
cours d’exécution, accédez à la nouvelle page Tâches en sélectionnant le lien Todo dans
la barre de navigation de l’application, ce qui permet de charger la page au niveau de
/todo .

Laissez l’application exécuter l’interpréteur de commandes. Chaque fois qu’un fichier est
enregistré, l’application est automatiquement regénérée, et la page est
automatiquement rechargée dans le navigateur.
Ajoutez un fichier TodoItem.cs à la racine du projet (dossier TodoList ) pour contenir
une classe qui représente un élément de tâche. Utilisez le code C# suivant pour la classe
TodoItem .

TodoItem.cs :

C#

public class TodoItem


{
public string? Title { get; set; }
public bool IsDone { get; set; }
}

7 Notes

Si vous utilisez Visual Studio pour créer le fichier TodoItem.cs et la classe TodoItem ,
utilisez l’une des approches suivantes :

Supprimez l’espace de noms généré par Visual Studio pour la classe.


Utilisez le bouton Copier dans le bloc de code précédent, puis remplacez
l’ensemble du contenu du fichier généré par Visual Studio.

Retournez au composant Todo , puis effectuez les tâches suivantes :

Ajoutez un champ pour les éléments todo dans le bloc @code . Le composant Todo
utilise ce champ pour maintenir l’état de la liste de tâches.
Ajoutez un balisage de liste non triée et une boucle foreach pour effectuer le
rendu de chaque élément todo en tant qu’élément de liste ( <li> ).

Components/Pages/Todo.razor :

razor

@page "/todo"
@rendermode InteractiveServer

<PageTitle>Todo</PageTitle>

<h3>Todo</h3>
<ul>
@foreach (var todo in todos)
{
<li>@todo.Title</li>
}
</ul>

@code {
private List<TodoItem> todos = new();
}

L’application nécessite des éléments d’interface utilisateur pour ajouter des éléments
todo à la liste. Ajoutez une entrée de texte ( <input> ) et un bouton ( <button> ) sous la
liste non ordonnée ( <ul>...</ul> ) :

razor

@page "/todo"
@rendermode InteractiveServer

<PageTitle>Todo</PageTitle>

<h3>Todo</h3>

<ul>
@foreach (var todo in todos)
{
<li>@todo.Title</li>
}
</ul>

<input placeholder="Something todo" />


<button>Add todo</button>

@code {
private List<TodoItem> todos = new();
}

Enregistrez le fichier TodoItem.cs ainsi que le fichier Todo.razor mis à jour. Dans
l’interpréteur de commandes, l’application est automatiquement regénérée quand les
fichiers sont enregistrés. Le navigateur recharge la page.

Quand le bouton Add todo est sélectionné, rien ne se produit, car aucun gestionnaire
d’événements n’est attaché au bouton.

Ajoutez une méthode AddTodo au composant Todo et inscrivez-la pour le bouton en


utilisant l’attribut @onclick . La méthode C# AddTodo est appelée lorsque le bouton est
sélectionné :
razor

<input placeholder="Something todo" />


<button @onclick="AddTodo">Add todo</button>

@code {
private List<TodoItem> todos = new();

private void AddTodo()


{
// Todo: Add the todo
}
}

Pour obtenir le titre du nouvel élément todo, ajoutez un champ de chaîne newTodo en
haut du bloc @code :

C#

private string? newTodo;

Modifiez l’élément de texte <input> pour lier newTodo avec l’attribut @bind :

razor

<input placeholder="Something todo" @bind="newTodo" />

Mettez à jour la méthode AddTodo pour ajouter TodoItem avec le titre spécifié à la liste.
Supprimez la valeur du texte d’entrée en définissant newTodo sur une chaîne vide :

razor

@page "/todo"
@rendermode InteractiveServer

<PageTitle>Todo</PageTitle>

<h3>Todo</h3>

<ul>
@foreach (var todo in todos)
{
<li>@todo.Title</li>
}
</ul>

<input placeholder="Something todo" @bind="newTodo" />


<button @onclick="AddTodo">Add todo</button>
@code {
private List<TodoItem> todos = new();
private string? newTodo;

private void AddTodo()


{
if (!string.IsNullOrWhiteSpace(newTodo))
{
todos.Add(new TodoItem { Title = newTodo });
newTodo = string.Empty;
}
}
}

Enregistrez le fichier Todo.razor . L’application est automatiquement regénérée dans


l’interpréteur de commandes, et la page est rechargée dans le navigateur.

Le texte du titre pour chaque élément todo peut être rendu modifiable et une case à
cocher peut aider l’utilisateur à effectuer le suivi des éléments terminés. Ajoutez une
entrée de case à cocher pour chaque élément todo et liez sa valeur à la propriété
IsDone . Changez @todo.Title en un élément <input> lié à todo.Title avec @bind :

razor

<ul>
@foreach (var todo in todos)
{
<li>
<input type="checkbox" @bind="todo.IsDone" />
<input @bind="todo.Title" />
</li>
}
</ul>

Mettez à jour l’en-tête <h3> pour afficher le nombre d’éléments todo qui ne sont pas
terminés ( IsDone est false ). L’expression Razor dans l’en-tête suivant est évaluée
chaque fois que Blazor réaffiche le composant.

razor

<h3>Todo (@todos.Count(todo => !todo.IsDone))</h3>

Le composant Todo terminé :

razor
@page "/todo"
@rendermode InteractiveServer

<PageTitle>Todo</PageTitle>

<h1>Todo (@todos.Count(todo => !todo.IsDone))</h1>

<ul>
@foreach (var todo in todos)
{
<li>
<input type="checkbox" @bind="todo.IsDone" />
<input @bind="todo.Title" />
</li>
}
</ul>

<input placeholder="Something todo" @bind="newTodo" />


<button @onclick="AddTodo">Add todo</button>

@code {
private List<TodoItem> todos = new();
private string? newTodo;

private void AddTodo()


{
if (!string.IsNullOrWhiteSpace(newTodo))
{
todos.Add(new TodoItem { Title = newTodo });
newTodo = string.Empty;
}
}
}

Enregistrez le fichier Todo.razor . L’application est automatiquement regénérée dans


l’interpréteur de commandes, et la page est rechargée dans le navigateur.

Ajoutez des éléments, modifiez des éléments et marquez des éléments todo effectués
pour tester le composant.

Une fois que vous avez fini, arrêtez l’application dans l’interpréteur de commandes. De
nombreux interpréteurs de commandes acceptent la commande clavier Ctrl + C

(Windows) ou ⌘ + C (macOS) pour arrêter une application.

Publier sur Azure


Pour plus d’informations sur le déploiement sur Azure, consultez Démarrage rapide :
Déployer une application web ASP.NET.
Étapes suivantes
Dans ce didacticiel, vous avez appris à :

" Créer un projet d’application Blazor de liste de tâches


" Modifier des composants Razor
" Utiliser la gestion des événements et la liaison de données dans des composants
" Utiliser le routage dans une application Blazor

Découvrez les outils pour ASP.NET Core Blazor :

ASP.NET Core Blazor

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Utiliser ASP.NET Core SignalR avec
Blazor
Article • 09/02/2024

Ce didacticiel fournit une expérience de travail de base pour créer une application en
temps réel SignalR à l'aide de Blazor. Cet article s’adresse aux développeurs qui
connaissent déjà SignalR et qui cherchent à savoir comment utiliser SignalR dans une
application Blazor. Pour obtenir une aide détaillée sur les frameworks SignalR et Blazor,
consultez les ensembles de documentation de référence suivants et la documentation
des API :

Vue d’ensemble d’ASP.NET Core SignalR


ASP.NET Core Blazor
Navigateur d’API .NET

Apprenez à :

" Créer une application Blazor


" Ajouter la bibliothèque cliente SignalR
" Ajouter un hub SignalR
" Ajouter des services SignalR et un point de terminaison pour le hub SignalR
" Ajouter un code de composant Razor pour la conversation

À la fin de ce tutoriel, vous disposerez d’une application de conversation opérationnelle.

Prérequis
Visual Studio

Visual Studio 2022 version 16.4 ou ultérieure avec la charge de travail


Développement ASP.NET et web

Exemple d’application
Le téléchargement de l’exemple d’application de conversation du tutoriel n’est pas
requis pour ce tutoriel. L’exemple d’application est l’application de travail opérationnelle
finale produite en suivant les étapes de ce tutoriel.

Afficher ou télécharger l’exemple de code


Créer une application Web Blazor
Suivez l’aide pour votre choix d’outils :

Visual Studio

7 Notes

Visual Studio 2022 ou version ultérieure et .NET Core SDK 8.0.0 ou version
ultérieure sont requis.

Créez un projet.

Sélectionnez le modèle d'Blazorapplication Web. Sélectionnez Suivant.

Entrez BlazorSignalRApp dans le champ Nom du projet. Vérifiez que


l’entrée Emplacement est correcte ou indiquez un emplacement pour le projet.
Sélectionnez Suivant.

Confirmez que le Framework est .NET 8.0 ou version ultérieure. Sélectionnez Create
(Créer).

Ajouter la bibliothèque cliente SignalR


Visual Studio

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le


projet BlazorSignalRApp , puis sélectionnez Gérer les packages NuGet.

Dans la boîte de dialogue Gérer les packages NuGet, confirmez que la source du
package est définie sur nuget.org .

Avec Parcourir sélectionné, entrez Microsoft.AspNetCore.SignalR.Client dans la


zone de recherche.

Dans les résultats de la recherche, sélectionnez la dernière version du package


Microsoft.AspNetCore.SignalR.Client . Cliquez sur Installer.

Si la fenêtre de dialogue Aperçu des modifications s’affiche, sélectionnez OK.


Si la boîte de dialogue Acceptation de la licence s’affiche, sélectionnez J’accepte si
vous acceptez les termes du contrat de licence.

Ajouter un hub SignalR


Créez un dossier (plural) Hubs et ajoutez la classe ChatHub ( Hubs/ChatHub.cs ) suivante à
la racine de l’application :

C#

using Microsoft.AspNetCore.SignalR;

namespace BlazorSignalRApp.Hubs;

public class ChatHub : Hub


{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}

Ajouter des services et un point de terminaison


au hub SignalR
Ouvrez le fichier Program .

Ajoutez l’espace de noms de Microsoft.AspNetCore.ResponseCompression et de la


classe ChatHub en haut du fichier :

C#

using Microsoft.AspNetCore.ResponseCompression;
using BlazorSignalRApp.Hubs;

Ajouter des services de middleware de compression de réponse :

C#

builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});

Utilisez Response Compression Middleware en haut de la configuration du pipeline de


traitement :

C#

app.UseResponseCompression();

Ajoutez un point de terminaison pour le hub immédiatement après la ligne routant les
composants Razor ( app.MapRazorComponents<T>() ) :

C#

app.MapHub<ChatHub>("/chathub");

Ajouter le code de composant Razor pour la


conversation
Ouvrez le fichier Components/Pages/Home.razor .

Remplacez la balise par le code suivant :

razor

@page "/"
@rendermode InteractiveServer
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable

<PageTitle>Home</PageTitle>

<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>

<hr>

<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>

@code {
private HubConnection? hubConnection;
private List<string> messages = new List<string>();
private string? userInput;
private string? messageInput;

protected override async Task OnInitializedAsync()


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();

hubConnection.On<string, string>("ReceiveMessage", (user, message)


=>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
InvokeAsync(StateHasChanged);
});

await hubConnection.StartAsync();
}

private async Task Send()


{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput,
messageInput);
}
}

public bool IsConnected =>


hubConnection?.State == HubConnectionState.Connected;

public async ValueTask DisposeAsync()


{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
7 Notes

Lorsque vous utilisez le Rechargement à chaud, désactivez l’intergiciel de


compression de réponse dans l’environnement Development . Pour plus
d’informations, consultez Aide relative à ASP.NET Core BlazorSignalR.

Exécuter l’application
Suivez l’aide relative à vos outils :

Visual Studio

Appuyez sur F5 pour exécuter l’application avec le débogage ou sur Ctrl + F5

(Windows)/ ⌘ + F5 (macOS) pour exécuter l’application sans débogage.

Copiez l’URL à partir de la barre d’adresse, ouvrez un autre onglet ou instance du


navigateur, puis collez l’URL dans la barre d’adresse.

Choisissez un des navigateurs, entrez un nom et un message, puis sélectionnez le


bouton pour envoyer le message. Le nom et le message sont affichés instantanément
dans les deux pages :

Citations : Star Trek VI : Terre inconnue ©1991 Paramount

Étapes suivantes
Dans ce didacticiel, vous avez appris à :

" Créer une application Blazor


" Ajouter la bibliothèque cliente SignalR
" Ajouter un hub SignalR
" Ajouter des services SignalR et un point de terminaison pour le hub SignalR
" Ajouter un code de composant Razor pour la conversation

Pour obtenir une aide détaillée sur les frameworks SignalR et Blazor, consultez les
ensembles de documentation de référence suivants :

Vue d’ensemble d’ASP.NET Core SignalR ASP.NET Core Blazor

Ressources supplémentaires
Authentification par jeton du porteur avec Identity Server, WebSockets et Server-
Sent Events
Sécurisez un hub SignalR dans les applications Blazor WebAssembly hébergées
Négociation cross-origin SignalR pour l’authentification
Configuration SignalR
Déboguer des applications ASP.NET Core Blazor
Conseils d’atténuation des menaces pour le rendu statique côté serveur de Blazor
ASP.NET Core
Conseils d’atténuation des menaces pour le rendu interactif côté serveur de Blazor
ASP.NET Core
Blazor exemples de référentiel GitHub (dotnet/blazor-samples)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
ASP.NET Core Blazor Hybrid
Article • 09/02/2024

Cet article décrit ASP.NET Core Blazor Hybrid, un moyen de créer une interface
utilisateur web interactive côté client à l’aide de .NET dans une application ASP.NET
Core.

Utilisez Blazor Hybrid pour combiner des infrastructures clientes natives de bureau et
mobiles avec .NET et Blazor.

Dans une application Blazor Hybrid, les composants Razor s’exécutent en mode natif sur
l’appareil. Les composants affichent dans un contrôle Web View incorporé via un canal
d’interopérabilité local. Les composants ne s’exécutent pas dans le navigateur et
WebAssembly n’est pas impliqué. Les composants Razor chargent et exécutent
rapidement du code, et les composants ont un accès complet aux fonctionnalités
natives de l’appareil via la plateforme .NET. Les styles de composants rendus dans un
Web View sont dépendants de la plateforme et peuvent vous obliger à prendre en
compte les différences de rendu entre les plateformes à l’aide de feuilles de style
personnalisées.

Les articles sur Blazor Hybrid couvrent des sujets relatifs à l’intégration des composants
Razor dans les infrastructures clientes natives.

Applications Blazor Hybrid avec .NET MAUI


La prise en charge de Blazor Hybrid est intégrée à l’infrastructure .NET Multi-platform
App UI (.NET MAUI). .NET MAUI inclut le contrôle BlazorWebView qui autorise le rendu
des composants Razor dans un élément Web View incorporé. En utilisant .NET MAUI et
Blazor ensemble, vous pouvez réutiliser un ensemble de composants d’interface
utilisateur web sur les composants mobiles, de bureau et web.

Applications Blazor Hybrid avec WPF et


Windows Forms
Les applications Blazor Hybrid peuvent être générées avec Windows Presentation
Foundation (WPF) et Windows Forms. Blazor fournit des contrôles BlazorWebView pour
ces deux infrastructures (WPF BlazorWebView, Windows Forms BlazorWebView). Les
composants Razor s’exécutent en mode natif sur le bureau Windows et affichent sur un
élément Web View incorporé. L’utilisation de Blazor dans WPF et Windows Forms vous
permet d’ajouter une nouvelle interface utilisateur à vos applications de bureau
Windows existantes, qui peut être réutilisée sur des plateformes avec .NET MAUI ou sur
le web.

Configuration Web View


Blazor Hybrid expose la configuration Web View sous-jacente pour différentes
plateformes par le biais d’événements du contrôle BlazorWebView :

BlazorWebViewInitializing fournit l’accès aux paramètres utilisés pour créer

l’élément Web View sur chaque plateforme, si ces paramètres sont disponibles.
BlazorWebViewInitialized fournit l’accès à l’élément Web View pour permettre de

poursuivre la configuration des paramètres.

Utilisez les modèles préférés sur chaque plateforme pour attacher des gestionnaires
d’événements aux événements afin d’exécuter votre code personnalisé.

Documentation sur l’API :

.NET MAUI
BlazorWebViewInitializing
BlazorWebViewInitialized
WPF
BlazorWebViewInitializing
BlazorWebViewInitialized
Windows Forms
BlazorWebViewInitializing
BlazorWebViewInitialized

Exceptions non prise en charge dans les


applications Windows Forms et WPF
Cette section s’applique uniquement aux Blazor Hybridapplications Windows Forms et
WPF.

Créez un rappel pour UnhandledException sur la propriété


System.AppDomain.CurrentDomain. L’exemple suivant utilise une directive de
compilateur pour afficher un MessageBox qui avertit l’utilisateur qu’une erreur s’est
produite ou affiche les informations d’erreur au développeur. Consignez les
informations d’erreur dans error.ExceptionObject .
C#

AppDomain.CurrentDomain.UnhandledException += (sender, error) =>


{
#if DEBUG
MessageBox.Show(text: error.ExceptionObject.ToString(), caption:
"Error");
#else
MessageBox.Show(text: "An error has occurred.", caption: "Error");
#endif

// Log the error information (error.ExceptionObject)


};

Globalisation et localisation
Cette section s’applique uniquement aux applications .NET MAUIBlazor Hybrid.

.NET MAUI configure le CurrentCulture et le CurrentUICulture en fonction des


informations ambiantes de l’appareil.

IStringLocalizer et d’autres API de l’espace de noms Microsoft.Extensions.Localization


fonctionnent généralement comme prévu, ainsi que le formatage, l’analyse et la liaison
de globalisation qui s’appuient sur la culture de l’utilisateur.

Lors de la modification dynamique de la culture de l’application au moment de


l’exécution, l’application doit être rechargée pour refléter le changement de culture, ce
qui permet d’afficher le composant racine et la transmission de la nouvelle culture aux
composants enfants re-rendus.

Le système de ressources .NET prend en charge l’incorporation d’images localisées (en


tant qu’objets blob) dans une application, mais Blazor Hybrid ne peut pas afficher les
images incorporées dans les composants Razor pour le moment. Même si un utilisateur
lit les octets d’une image dans un Stream à l’aide de ResourceManager, l’infrastructure
ne prend actuellement pas en charge le rendu de l’image récupérée dans un
composantRazor.

Une approche spécifique à la plateforme pour inclure des images localisées est une
fonctionnalité du système de ressources .NET, mais les éléments de navigateur d’un
composant Razor dans une application .NET MAUIBlazor Hybrid ne sont pas en mesure
d’interagir avec de telles images.

Pour plus d’informations, consultez les ressources suivantes :


Xamarin.Forms String and Image Localization : les instructions s’appliquent
généralement aux applications Blazor Hybrid. Tous les scénarios ne sont pas pris en
charge pour le moment.
Blazor Composant image pour afficher des images qui ne sont pas accessibles via
des points de terminaison HTTP (dotnet/aspnetcore #25274)

Accédez aux services délimités à partir de


l’interface utilisateur native
BlazorWebView a une méthode TryDispatchAsync qui appelle une méthode
Action<ServiceProvider> spécifiée de manière asynchrone et transmet les services

délimités disponibles dans les composants Razor. Cela permet au code de l’interface
utilisateur native d’accéder aux services délimités tels que NavigationManager :

C#

private async void MyMauiButtonHandler(object sender, EventArgs e)


{
var wasDispatchCalled = await _blazorWebView.TryDispatchAsync(sp =>
{
var navMan = sp.GetRequiredService<NavigationManager>();
navMan.CallSomeNavigationApi(...);
});

if (!wasDispatchCalled)
{
...
}
}

Quand wasDispatchCalled est false , pensez à ce qu’il faut faire si l’appel n’a pas été
distribué. En règle générale, la distribution ne doit pas échouer. En cas d’échec, les
ressources du système d’exploitation peuvent être épuisées. Si les ressources sont
épuisées, envisagez de journaliser un message, de lever une exception et peut-être
d’alerter l’utilisateur.

Ressources supplémentaires
Didacticiels ASP.NET Core Blazor Hybrid
.NET Multi-platform App UI (.NET MAUI)
Windows Presentation Foundation (WPF)
Windows Forms
6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Tutoriels ASP.NET Core Blazor Hybrid
Article • 09/02/2024

Les didacticiels suivants fournissent une expérience de travail de base pour la création
d'une application Blazor Hybrid :

Créer une application .NET MAUIBlazor Hybrid

Créer une application Windows Forms Blazor

Créer une application Windows Presentation Foundation (WPF) Blazor

Pour obtenir une vue d’ensemble de Blazor et des articles de référence, consultez
ASP.NET Core Blazor et les articles qui le suivent dans la table des matières.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Créer une application .NET MAUIBlazor
Hybrid
Article • 30/11/2023

Ce tutoriel vous montre comment générer et exécuter une application .NET MAUIBlazor
Hybrid. Vous allez apprendre à effectuer les actions suivantes :

" Créez un projet d’application .NET MAUIBlazor Hybrid dans Visual Studio


" Exécutez l’application sur Windows
" Exécutez l’application sur un appareil mobile émulé dans l’Émulateur Android

Prérequis
Plateformes prises en charge (documentation .NET MAUI)
Visual Studio avec la charge de travail de .NET Multi-platform App
UIdéveloppement.
Microsoft Edge WebView2: WebView2 est nécessaire sur Windows lors de
l’exécution d’une application native. Lorsque vous développez des applications
.NET MAUIBlazor Hybrid et que vous les exécutez uniquement dans les émulateurs
de Visual Studio, WebView2 n’est pas nécessaire.
Activez l’accélération matérielle pour améliorer le niveau de performance de
l’émulateur Android.

Pour plus d’informations sur les prérequis et l’installation de logiciels pour ce tutoriel,
consultez les ressources suivantes dans la documentation .NET MAUI :

Plateformes prises en charge pour les applications .NET MAUI


Installation (Visual Studio)

Créer une application .NET MAUIBlazor Hybrid


Lancez Visual Studio. Dans la fenêtre Démarrer, sélectionnez Créer un nouveau projet :
Dans la fenêtre Créer un nouveau projet, utilisez la liste déroulante Type de projet pour
filtrer les modèles MAUI :

Sélectionnez le modèle.NET MAUIBlazor Hybrid Application, puis sélectionnez le


bouton Suivant :
7 Notes

Dans .NET 7.0 ou version antérieure, le modèle est nommé .NET


MAUIBlazorApplication.

Dans la boîte de dialogue Configurer votre nouveau projet :

Définissez le Nom du projet sur MauiBlazor.


Choisissez un emplacement approprié pour le projet.
Cliquez sur le bouton Suivant.
Dans la boîte de dialogue Informations supplémentaires, sélectionnez la version du
framework avec la liste déroulante Framework. Sélectionnez le bouton Créer :

Attendez que Visual Studio crée le projet et restaure les dépendances du projet.
Surveillez la progression dans Explorateur de solutions en ouvrant l’entrée
Dépendances.

Restauration des dépendances :

Dépendances restaurées :

Exécutez l’application sur Windows


Dans la barre d’outils Visual Studio, sélectionnez le bouton Ordinateur Windows pour
générer et démarrer le projet :

Si le Mode développeur n’est pas activé, vous êtes invité à l’activer dans
Paramètres>Pour les développeurs>Mode Développeur (Windows 10) ou
Paramètres>Privacy& sécurité>Pour les développeurs>Mode Développeur (Windows
11). Définissez le commutateur sur Activé.

L’application s’exécutant en tant qu’application de bureau Windows :


Exécutez l’application dans l’Émulateur Android
Si vous avez suivi les instructions de la section Exécuter l’application sur Windows,
sélectionnez le bouton Arrêter le débogage dans la barre d’outils pour arrêter
l’application Windows en cours d’exécution :

Dans la barre d’outils Visual Studio, sélectionnez le bouton déroulant Démarrer la


configuration. Sélectionnez Émulateur Android>Émulateur Android :

Les kits de développement logiciel Android sont nécessaires pour générer des
applications pour Android. Dans le panneau Liste d’erreurs, un message s’affiche vous
demandant de double-cliquer sur le message pour installer les kits de développement
logiciel Android requis :
La fenêtre Acceptation de licence du kit de développement logiciel Android s’affiche,
puis sélectionnez le bouton Accepter pour chaque licence qui s’affiche. Une fenêtre
supplémentaire s’affiche pour les licences Émulateur Android et Applicateur de
correctifs du kit de développement logiciel. Cliquez sur le bouton Accepter.

Attendez que Visual Studio télécharge le kit de développement logiciel Android et


l’Émulateur Android. Vous pouvez suivre la progression en sélectionnant l’indicateur des
tâches en arrière-plan dans le coin inférieur gauche de l’interface utilisateur de Visual
Studio :

L’indicateur affiche une coche lorsque les tâches en arrière-plan sont terminées :

Dans la barre d’outils, sélectionnez le bouton Émulateur Android :

Dans la fenêtre Créer un appareil Android par défaut, sélectionnez le bouton Créer :
Attendez que Visual Studio télécharge, décompresse et crée un Émulateur Android.
Lorsque l’émulateur de téléphone Android est prêt, sélectionnez le bouton Démarrer.

7 Notes

Activez l’accélération matérielle pour améliorer le niveau de performance de


l’émulateur Android.

Fermez la fenêtre Gestionnaire d'appareils Android. Attendez que la fenêtre du


téléphone émulé s’affiche, que le système d’exploitation Android se charge et que
l’écran d’accueil s’affiche.

) Important

Le téléphone émulé doit être sous tension avec le système d’exploitation Android
chargé pour charger et exécuter l’application dans le débogueur. Si le téléphone
émulé n’est pas en cours d’exécution, activez le téléphone à l’aide du raccourci
clavier Ctrl + P ou en sélectionnant le bouton Marche/Arrêt dans l’interface
utilisateur :
Dans la barre d’outils Visual Studio, sélectionnez le bouton Pixel 5 - {VERSION} pour
générer et exécuter le projet, où l’espace réservé {VERSION} est la version Android. Dans
l’exemple suivant, la version d’Android est API 30 (Android 11.0 - API 30), et une version
ultérieure s’affiche en fonction du kit de développement logiciel Android installé :

Visual Studio génère le projet et déploie l’application sur l’émulateur.

Le démarrage de l’émulateur, le chargement du téléphone et du système d’exploitation


émulés, ainsi que le déploiement et l’exécution de l’application peuvent prendre
plusieurs minutes en fonction de la vitesse du système et de l’activation ou non de
l’accélération matérielle. Vous pouvez surveiller la progression du déploiement en
inspectant la barre d’état de Visual Studio en bas de l’interface utilisateur. L’indicateur
Prêt est coché et les indicateurs de déploiement et de chargement de l’application de
l’émulateur disparaissent lorsque l’application est en cours d’exécution :

Pendant le déploiement :

Au démarrage de l’application :

L’application s’exécutant dans l’Émulateur Android :


Étapes suivantes
Dans ce didacticiel, vous avez appris à :

" Créez un projet d’application .NET MAUIBlazor Hybrid dans Visual Studio


" Exécutez l’application sur Windows
" Exécutez l’application sur un appareil mobile émulé dans l’Émulateur Android

En savoir plus sur les applications Blazor Hybrid :

ASP.NET Core Blazor Hybrid

6 Collaborer avec nous sur


GitHub
La source de ce contenu se
trouve sur GitHub, où vous
pouvez également créer et
examiner les problèmes et les
demandes de tirage. Pour plus
d’informations, consultez notre
guide du contributeur.
ASP.NET Core feedback
ASP.NET Core is an open source
project. Select a link to provide
feedback:

 Ouvrir un problème de
documentation

 Indiquer des commentaires sur


le produit
Créer une application Blazor Windows
Forms
Article • 30/11/2023

Ce tutoriel vous montre comment créer et exécuter une application Windows Forms
Blazor. Vous allez apprendre à effectuer les actions suivantes :

" Créer un projet d’application Windows Forms Blazor


" Exécutez l’application sur Windows

Prérequis
Plateformes prises en charge (documentation Windows Forms)
Visual Studio 2022 avec la charge de travail Développement .NET Desktop

Charge de travail Visual Studio


Si la charge de travail Développement de bureau .NET Desktop n’est pas installée,
utilisez le programme d’installation de Visual Studio pour installer la charge de travail.
Pour plus d’informations, consultez Modifier les charges de travail, composants et
modules linguistiques de Visual Studio.

Créer un projet Windows Forms Blazor


Lancez Visual Studio. Dans la fenêtre Démarrer, sélectionnez Créer un nouveau projet :
Dans la boîte de dialogue Créer un projet, filtrez la liste déroulante Type de projet sur
Bureau. Sélectionnez le modèle de projet C# pour Application Windows Forms, puis
sélectionnez le bouton Suivant :

Dans la boîte de dialogue Configurer votre nouveau projet :

Définissez le Nom du projet sur WinFormsBlazor.


Choisissez un emplacement approprié pour le projet.
Cliquez sur le bouton Suivant.

Dans la boîte de dialogue Informations supplémentaires, sélectionnez la version du


framework avec la liste déroulante Framework. Sélectionnez le bouton Créer :
Utilisez le Gestionnaire de package NuGet pour installer le package NuGet
Microsoft.AspNetCore.Components.WebView.WindowsForms :

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le nom du projet,
WinFormsBlazor, puis sélectionnez Modifier le fichier projet pour ouvrir le fichier projet
( WinFormsBlazor.csproj ).

En haut du fichier projet, remplacez le SDK par Microsoft.NET.Sdk.Razor :

XML

<Project Sdk="Microsoft.NET.Sdk.Razor">

Enregistrez les modifications apportées au fichier projet ( WinFormsBlazor.csproj ).

Ajoutez un fichier _Imports.razor à la racine du projet avec une directive @using pour
Microsoft.AspNetCore.Components.Web.

_Imports.razor :

razor

@using Microsoft.AspNetCore.Components.Web

Enregistrez le fichier _Imports.razor .

Ajoutez un dossier wwwroot au projet.

Ajoutez un fichier index.html au dossier wwwroot avec le balisage suivant.

wwwroot/index.html :

HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WinFormsBlazor</title>
<base href="/" />
<link href="css/app.css" rel="stylesheet" />
<link href="WinFormsBlazor.styles.css" rel="stylesheet" />
</head>

<body>

<div id="app">Loading...</div>

<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

<script src="_framework/blazor.webview.js"></script>

</body>

</html>

Dans le dossier wwwroot , créez un dossier css pour contenir des feuilles de style.

Ajoutez une feuille de style app.css au dossier wwwroot/css avec le contenu suivant.

wwwroot/css/app.css :

css

html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

h1:focus {
outline: none;
}

a, .btn-link {
color: #0071c1;
}

.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}

.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}

.invalid {
outline: 1px solid red;
}

.validation-message {
color: red;
}

#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}

#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

Ajoutez le composant Counter suivant à la racine du projet, qui est le composant


Counter par défaut trouvé dans les modèles de projet Blazor.

Counter.razor :

razor

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}
Enregistrez le composant Counter ( Counter.razor ).

Dans l’Explorateur de solutions, double-cliquez sur le fichier Form1.cs pour ouvrir le


concepteur :

Ouvrez la Boîte à outils en sélectionnant le bouton Boîte à outils le long du bord


gauche de la fenêtre Visual Studio ou en sélectionnant la commande de menu
Afficher>Boîte à outils.

Recherchez le contrôle BlazorWebView sous


Microsoft.AspNetCore.Components.WebView.WindowsForms . Faites glisser le BlazorWebView

à partir de la Boîte à outils dans le concepteur Form1 . Veillez à ne pas faire glisser
accidentellement un contrôle WebView2 dans le formulaire.

Visual Studio affiche le contrôle BlazorWebView dans le concepteur de formulaires


comme WebView2 et nomme automatiquement le contrôle blazorWebView1 :
Dans Form1 , sélectionnez le BlazorWebView ( WebView2 ) en un seul clic.

Dans les Propriétés de BlazorWebView, vérifiez que le contrôle est nommé


blazorWebView1 . Si le nom n’est pas blazorWebView1 , le mauvais contrôle a été déplacé à

partir de la Boîte à outils. Supprimez le contrôle WebView2 dans Form1 et faites glisser le
contrôle BlazorWebView dans le formulaire.

Dans les propriétés du contrôle, remplacez la valeur Dock de BlazorWebView par Fill :
Dans le concepteur Form1 , cliquez avec le bouton droit sur Form1 , puis sélectionnez
Afficher le code.

Ajoutez des espaces de noms pour


Microsoft.AspNetCore.Components.WebView.WindowsForms et
Microsoft.Extensions.DependencyInjection en haut du fichier Form1.cs :

C#

using Microsoft.AspNetCore.Components.WebView.WindowsForms;
using Microsoft.Extensions.DependencyInjection;

À l’intérieur du constructeur Form1 , après l’appel de méthode InitializeComponent ,


ajoutez le code suivant :

C#

var services = new ServiceCollection();


services.AddWindowsFormsBlazorWebView();
blazorWebView1.HostPage = "wwwroot\\index.html";
blazorWebView1.Services = services.BuildServiceProvider();
blazorWebView1.RootComponents.Add<Counter>("#app");

7 Notes
La méthode InitializeComponent est générée par un générateur source au
moment de la génération de l’application et ajoutée à l’objet de compilation pour
la classe appelante.

Le code C# final et complet de Form1.cs avec un espace de noms inclus dans l’étendue
de fichier :

C#

using Microsoft.AspNetCore.Components.WebView.WindowsForms;
using Microsoft.Extensions.DependencyInjection;

namespace WinFormsBlazor;

public partial class Form1 : Form


{
public Form1()
{
InitializeComponent();

var services = new ServiceCollection();


services.AddWindowsFormsBlazorWebView();
blazorWebView1.HostPage = "wwwroot\\index.html";
blazorWebView1.Services = services.BuildServiceProvider();
blazorWebView1.RootComponents.Add<Counter>("#app");
}
}

Exécuter l’application
Sélectionnez le bouton Démarrer dans la barre d’outils de Visual Studio :

L’application s’exécutant sur Windows :


Étapes suivantes
Dans ce didacticiel, vous avez appris à :

" Créer un projet d’application Windows Forms Blazor


" Exécutez l’application sur Windows

En savoir plus sur les applications Blazor Hybrid :

ASP.NET Core Blazor Hybrid

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Générez une application Blazor
Windows Presentation Foundation
(WPF)
Article • 09/02/2024

Ce tutoriel vous montre comment générer et exécuter une application WPF Blazor. Vous
allez apprendre à effectuer les actions suivantes :

" Créer un projet Blazor d’application WPF


" Ajouter un Razor composant au projet
" Exécutez l’application sur Windows

Prérequis
Plateformes prises en charge (documentation WPF)
Visual Studio 2022 avec la charge de travail de développement de bureau .NET

Charge de travail Visual Studio


Si la charge de travail Développement de bureau .NET Desktop n’est pas installée,
utilisez le programme d’installation de Visual Studio pour installer la charge de travail.
Pour plus d’informations, consultez Modifier les charges de travail, composants et
modules linguistiques de Visual Studio.

Créer un projet Blazor WPF


Lancez Visual Studio. Dans la fenêtre Démarrer, sélectionnez Créer un nouveau projet :
Dans la boîte de dialogue Créer un projet, filtrez la liste déroulante Type de projet sur
Bureau. Sélectionnez le modèle de projet C# pour l’application WPF, puis sélectionnez
le bouton Suivant :

Dans la boîte de dialogue Configurer votre nouveau projet :

Définissez le Nom du projet sur WpfBlazor.


Choisissez un emplacement approprié pour le projet.
Cliquez sur le bouton Suivant.

Dans la boîte de dialogue Informations supplémentaires, sélectionnez la version du


framework avec la liste déroulante Framework. Sélectionnez le bouton Créer :
Utilisez le Gestionnaire de package NuGet pour installer le package NuGet
Microsoft.AspNetCore.Components.WebView.Wpf :

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le nom du projet,
WpfBlazor, puis sélectionnez Modifier le fichier projet pour ouvrir le fichier projet
( WpfBlazor.csproj ).

En haut du fichier projet, remplacez le SDK par Microsoft.NET.Sdk.Razor :

XML

<Project Sdk="Microsoft.NET.Sdk.Razor">

Dans le <PropertyGroup> existant du fichier projet, ajoutez le balisage suivant pour


définir l’espace de noms racine de l’application, qui est WpfBlazor dans ce tutoriel :

XML

<RootNamespace>WpfBlazor</RootNamespace>

7 Notes

Les instructions précédentes sur la définition de l’espace de noms racine du projet


constituent une solution de contournement temporaire. Pour plus d’informations,
consultez [Blazor][Wpf] Problème lié à l’espace de noms racine (dotnet/maui
#5861) .

Enregistrez les modifications apportées au fichier projet ( WpfBlazor.csproj ).

Ajoutez un fichier _Imports.razor à la racine du projet avec une directive @using pour
Microsoft.AspNetCore.Components.Web.

_Imports.razor :
razor

@using Microsoft.AspNetCore.Components.Web

Enregistrez le fichier _Imports.razor .

Ajoutez un dossier wwwroot au projet.

Ajoutez un fichier index.html au dossier wwwroot avec le balisage suivant.

wwwroot/index.html :

HTML

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WpfBlazor</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="WpfBlazor.styles.css" rel="stylesheet" />
</head>

<body>
<div id="app">Loading...</div>

<div id="blazor-error-ui" data-nosnippet>


An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webview.js"></script>
</body>

</html>

Dans le dossier wwwroot , créez un dossier css .

Ajoutez une feuille de style app.css au dossier wwwroot/css avec le contenu suivant.

wwwroot/css/app.css :

css

html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

h1:focus {
outline: none;
}

a, .btn-link {
color: #0071c1;
}

.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}

.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}

.invalid {
outline: 1px solid red;
}

.validation-message {
color: red;
}

#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}

#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

Ajoutez le composant Counter suivant à la racine du projet, qui est le composant


Counter par défaut trouvé dans les modèles de projet Blazor.

Counter.razor :
razor

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Enregistrez le composant Counter ( Counter.razor ).

Si le concepteur MainWindow n’est pas ouvert, ouvrez-le en double-cliquant sur le fichier


MainWindow.xaml dans l’Explorateur de solutions. Dans le concepteur MainWindow ,

remplacez le code XAML par ce qui suit :

XAML

<Window x:Class="WpfBlazor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-
compatibility/2006"
xmlns:blazor="clr-
namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.Asp
NetCore.Components.WebView.Wpf"
xmlns:local="clr-namespace:WpfBlazor"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<blazor:BlazorWebView HostPage="wwwroot\index.html" Services="
{DynamicResource services}">
<blazor:BlazorWebView.RootComponents>
<blazor:RootComponent Selector="#app" ComponentType="{x:Type
local:Counter}" />
</blazor:BlazorWebView.RootComponents>
</blazor:BlazorWebView>
</Grid>
</Window>

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur MainWindow.xaml et


sélectionnez Voir le code :
Ajoutez l’espace de noms Microsoft.Extensions.DependencyInjection en haut du fichier
MainWindow.xaml.cs :

C#

using Microsoft.Extensions.DependencyInjection;

À l’intérieur du constructeur MainWindow , après l’appel de méthode


InitializeComponent , ajoutez le code suivant :

C#

var serviceCollection = new ServiceCollection();


serviceCollection.AddWpfBlazorWebView();
Resources.Add("services", serviceCollection.BuildServiceProvider());

7 Notes

La méthode InitializeComponent est générée par un générateur source au


moment de la génération de l’application et ajoutée à l’objet de compilation pour
la classe appelante.

Le code C# final et complet de MainWindow.xaml.cs avec un espace de noms inclus dans


l'étendue de fichier et sans commentaires :

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Extensions.DependencyInjection;

namespace WpfBlazor;

public partial class MainWindow : Window


{
public MainWindow()
{
InitializeComponent();

var serviceCollection = new ServiceCollection();


serviceCollection.AddWpfBlazorWebView();
Resources.Add("services", serviceCollection.BuildServiceProvider());
}
}

Exécuter l’application
Sélectionnez le bouton Démarrer dans la barre d’outils de Visual Studio :

L’application s’exécutant sur Windows :


Étapes suivantes
Dans ce didacticiel, vous avez appris à :

" Créer un projet Blazor d’application WPF


" Ajouter un Razor composant au projet
" Exécutez l’application sur Windows

En savoir plus sur les applications Blazor Hybrid :

ASP.NET Core Blazor Hybrid

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Routage et navigation dans ASP.NET
Core Blazor Hybrid
Article • 09/02/2024

Cet article explique comment gérer le routage et la navigation des requêtes dans les
applications Blazor Hybrid.

Comportement de routage des requêtes d’URI


Comportement de routage des requêtes d’URI par défaut :

Un lien est interne si le nom d’hôte et le schéma correspondent entre l’URI


d’origine de l’application et l’URI de requête. Lorsque les noms d’hôte et les
schémas ne correspondent pas ou si le lien définit target="_blank" , le lien est
considéré comme externe.
Si le lien est interne, le lien est ouvert dans BlazorWebView par l’application.
Si le lien est externe, le lien est ouvert par une application déterminée par
l’appareil en fonction du gestionnaire inscrit de l’appareil pour le schéma du lien.
Pour les liens internes qui semblent requérir un fichier, car le dernier segment de
l’URI utilise la notation par points (par exemple, /file.x , /Maryia.Melnyk ,
/image.gif ) mais qui ne pointent pas vers un contenu statique :

WPF et Windows Forms : le contenu de la page hôte est renvoyé.


.NET MAUI : une réponse 404 est renvoyée.

Pour modifier le comportement de gestion des liens pour les liens qui ne définissent pas
target="_blank" , inscrivez l’événement UrlLoading et définissez la propriété
UrlLoadingEventArgs.UrlLoadingStrategy. L’énumération UrlLoadingStrategy permet de
définir le comportement de gestion des liens sur l’une des valeurs suivantes :

OpenExternally : chargez l’URL à l’aide d’une application déterminée par l’appareil.


Il s’agit de la stratégie par défaut pour les URI avec un hôte externe.
OpenInWebView : chargez l’URL dans le BlazorWebView . Il s’agit de la stratégie par
défaut pour les URL avec un hôte correspondant à l’origine de l’application.
N’utilisez pas cette stratégie pour les liens externes, sauf si vous pouvez vous
assurer que l’URI de destination est entièrement fiable.
CancelLoad : annule la tentative de chargement d’URL en cours.

La propriété UrlLoadingEventArgs.Url est utilisée pour obtenir ou définir


dynamiquement l’URL.
2 Avertissement

Par défaut, les liens externes sont ouverts dans une application déterminée par
l’appareil. L’ouverture de liens externes dans un BlazorWebView peut introduire des
failles de sécurité et ne doit pas être activée, sauf si vous pouvez vous assurer que
les liens externes sont entièrement fiables.

Documentation sur l’API :

.NET MAUI: BlazorWebView.UrlLoading


WPF : BlazorWebView.UrlLoading
Windows Forms : BlazorWebView.UrlLoading

L’espace de noms Microsoft.AspNetCore.Components.WebView est requis pour les


exemples suivants :

C#

using Microsoft.AspNetCore.Components.WebView;

Ajoutez le gestionnaire d’événements suivant au constructeur du Page où le


BlazorWebView est créé, qui se trouve MainPage.xaml.cs dans une application créée à

partir du modèle de projet .NET MAUI.

C#

blazorWebView.UrlLoading +=
(sender, urlLoadingEventArgs) =>
{
if (urlLoadingEventArgs.Url.Host != "0.0.0.0")
{
urlLoadingEventArgs.UrlLoadingStrategy =
UrlLoadingStrategy.OpenInWebView;
}
};

Obtenir ou définir un chemin pour la


navigation initiale
Utilisez la propriété BlazorWebView.StartPath pour obtenir ou définir le chemin de la
navigation initiale dans le contexte de navigation Blazor quand le composant Razor est
chargé. Le chemin de début par défaut est le chemin d’URL racine relatif ( / ).
Dans le balisage XAML MainPage ( MainPage.xaml ), spécifiez le chemin de début.
L’exemple suivant définit le chemin sur une page d’accueil à l’adresse /welcome :

XAML

<BlazorWebView ... StartPath="/welcome" ...>


...
<BlazorWebView>

Vous pouvez également définir le chemin d’accès de démarrage dans le constructeur


MainPage ( MainPage.xaml.cs ) :

C#

blazorWebView.StartPath = "/welcome";

Navigation entre les pages et les composants


Razor
Cette section explique comment naviguer entre les pages de contenu .NET MAUI et les
composants Razor.

Le modèle de projet hybride .NET MAUIBlazor n’est pas une application basée sur un
interpréteur de commandes. Par conséquent, la navigation basée sur l’URI pour les
applications basées sur un interpréteur de commandes n’est pas adaptée à un projet
basé sur le modèle de projet. Les exemples de cette section utilisent un NavigationPage
pour effectuer une navigation sans modèle ou modale.

Dans l’exemple suivant :

L’espace de noms de l’application est MauiBlazor , ce qui correspond au nom de


projet suggéré dans le tutorielGénérer une .NET MAUIBlazor Hybrid application.
Un ContentPage est placé dans un nouveau dossier ajouté à l’application nommée
Views .

Dans App.xaml.cs , créez le MainPage en tant que NavigationPage en effectuant la


modification suivante :

diff

- MainPage = new MainPage();


+ MainPage = new NavigationPage(new MainPage());
Views/NavigationExample.xaml :

XAML

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiBlazor"
x:Class="MauiBlazor.Views.NavigationExample"
Title="Navigation Example"
BackgroundColor="{DynamicResource PageBackgroundColor}">
<StackLayout>
<Label Text="Navigation Example"
VerticalOptions="Center"
HorizontalOptions="Center"
FontSize="24" />
<Button x:Name="CloseButton"
Clicked="CloseButton_Clicked"
Text="Close" />
</StackLayout>
</ContentPage>

Dans le fichier de code NavigationExample suivant, le gestionnaire d’événements


CloseButton_Clicked du bouton de fermeture appelle PopAsync pour désactiver

ContentPage de la pile de navigation.

Views/NavigationExample.xaml.cs :

C#

namespace MauiBlazor.Views;

public partial class NavigationExample : ContentPage


{
public NavigationExample()
{
InitializeComponent();
}

private async void CloseButton_Clicked(object sender, EventArgs e)


{
await Navigation.PopAsync();
}
}

Dans un composant Razor :

Ajoutez l’espace de noms pour les pages de contenu de l’application. Dans


l’exemple suivant, l’espace de noms est MauiBlazor.Views .
Ajoutez un élément HTML button avec un @onclickgestionnaire d’événements
pour ouvrir la page de contenu. La méthode du gestionnaire d’événements est
nommée OpenPage .
Dans le gestionnaire d’événements, appelez PushAsync pour envoyer (push) le
ContentPage, NavigationExample , sur la pile de navigation.

L’exemple suivant est basé sur le composant Index dans le modèle de projet .NET
MAUIBlazor.

Pages/Index.razor :

razor

@page "/"
@using MauiBlazor.Views

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

<button class="btn btn-primary" @onclick="OpenPage">Open</button>

@code {
private async void OpenPage()
{
await App.Current.MainPage.Navigation.PushAsync(new
NavigationExample());
}
}

Pour modifier l’exemple précédent en navigation modale :

Dans la méthode CloseButton_Clicked ( Views/NavigationExample.xaml.cs ),


remplacez PopAsync par PopModalAsync :

diff

- await Navigation.PopAsync();
+ await Navigation.PopModalAsync();

Dans la méthode OpenPage ( Pages/Index.razor ), remplacez PushAsync par


PushModalAsync :

diff
- await App.Current.MainPage.Navigation.PushAsync(new
NavigationExample());
+ await App.Current.MainPage.Navigation.PushModalAsync(new
NavigationExample());

Pour plus d’informations, consultez les ressources suivantes :

NavigationPage article (.NET MAUI documentation)


NavigationPage (Documentation sur l’API)

Liens profonds
L’aide de cette section décrit les approches de lien profond pour les appareils Android
et iOS.

Exemple d’application
Pour obtenir un exemple d’implémentation des instructions suivantes, consultez
l’application MAUI.AppLinks.Sample .

Android
Android prend en charge la gestion des liens d’application Android avec des filtres
Intent sur les activités.

Les liens peuvent être basés sur un schéma personnalisé (par exemple, myappname:// ) ou
utiliser un schéma http / https . L’écriture de code personnalisé n’est pas nécessaire
pour gérer des liens de schéma personnalisé. L’approche suivante illustre comment
prendre en charge la gestion des URL http / https . Un fichier d’association connu est
hébergé sur le domaine qui décrit la relation du domaine à l’application.

Hébergement du fichier d’association :

Prouve la propriété du domaine.


Permet à Android de vérifier que l’application cherchant à gérer l’URL dispose de la
propriété du domaine de l’URL. Cette opération empêche une application arbitraire
d’intercepter des liens.

Vérifier la propriété du domaine


Vérifiez la propriété du domaine dans la console de recherche Google .
Héberger un fichier d’association .well-known

Créez un fichier assetlinks.json hébergé sur le serveur du domaine sous le dossier


/.well-known/ . L'URL doit ressembler à https://redth.dev/.well-
known/assetlinks.json .

Voici un exemple du contenu du fichier :

JSON

[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "dev.redth.applinkssample",
"sha256_cert_fingerprints":
[

"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:10:11:12:13:14:15:16:17:18:
19:20:21:22:23:24:25"
]
}
}
]

Recherchez les empreintes digitales .keystore SHA256 de l’application. Dans cet


exemple, seule l’empreinte digitale du fichier androiddebug.keystore est incluse, qui est
utilisée par défaut pour signer des applications .NET Android.

Vous pouvez utiliser l’outil Générateur de listes d’instructions pour générer et valider
le fichier.

Configurer le Activity Android


Réutiliser Platforms/Android/MainActivity.cs dans l’application .NET MAUI en y
ajoutant l’attribut de classe suivant. Mettez à jour le paramètre DataHost de votre
application :

C#

[IntentFilter(
new string[] { Intent.ActionView },
AutoVerify = true,
Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataScheme = "https",
DataHost = "redth.dev")]

Utilisez votre propre schéma de données et vos propres valeurs d’hôte. Il est possible
d’associer plusieurs schémas/hôtes.

Pour marquer l’activité comme exportable, ajoutez la propriété Exported = true à


l’attribut [Activity(...)] existant.

Gérer les événements de cycle de vie pour l’activation de Intent

Dans le fichier MauiProgram.cs , configurez les événements de cycle de vie avec le


générateur d’applications :

C#

builder.ConfigureLifecycleEvents(lifecycle =>
{
#if IOS || MACCATALYST
// ...
#elif ANDROID
lifecycle.AddAndroid(android => {
android.OnCreate((activity, bundle) =>
{
var action = activity.Intent?.Action;
var data = activity.Intent?.Data?.ToString();

if (action == Intent.ActionView && data is not null)


{
HandleAppLink(data);
}
});
});
#endif
});

Tester une URL


Utilisez adb pour simuler l’ouverture d’une URL afin de vérifier que les liens de
l’application fonctionnent correctement, comme l’illustre l’exemple de commande
d’interpréteur de commandes suivant. Mettez à jour l’URI de données ( -d ) afin qu’il
corresponde à un lien dans l’application pour des tests :

shell
adb shell am start -a android.intent.action.VIEW -c
android.intent.category.BROWSABLE -d "https://redth.dev/items/1234"

Arguments d’intention dans la commande précédente :

-a Action :

-c Catégorie :
-d : URI de données

Pour obtenir plus d’informations, consultez la documentation Android Debug Bridge


(ADB) (Documentation destinée aux développeurs Android).

iOS
Apple prend en charge l’inscription d’une application pour gérer à la fois les schémas
d’URI personnalisés (par exemple, myappname:// ) et les schémas http / https . L’exemple
de cette section se concentre sur http / https . Les schémas personnalisés nécessitent
une configuration supplémentaire dans le fichier Info.plist , ce qui n’est pas abordé ici.

Apple fait référence à la gestion des URL http / https comme prenant en charge les liens
universels . Apple exige que vous hébergiez un fichier connu apple-app-site-
association dans le domaine qui décrit la relation du domaine à l’application.

Hébergement du fichier d’association :

Prouve la propriété du domaine.


Permet à Apple de vérifier que l’application cherchant à gérer l’URL dispose de la
propriété du domaine de l’URL. Cette opération empêche une application arbitraire
d’intercepter des liens.

Héberger un fichier d’association .well-known


Créez un apple-app-site-association JSFichier ON hébergé sur le serveur du domaine
sous le dossier /.well-known/ . L'URL doit ressembler à https://redth.dev/.well-
known/apple-app-site-association .

Le contenu du fichier doit inclure le JSON suivant. Remplacez les identificateurs


d’application par les valeurs correctes pour votre application :

JSON
{
"activitycontinuation": {
"apps": [ "85HMA3YHJX.dev.redth.applinkssample" ]
},
"applinks": {
"apps": [],
"details": [
{
"appID": "85HMA3YHJX.dev.redth.applinkssample",
"paths": [ "*", "/*" ]
}
]
}
}

Cette étape peut nécessiter un essai et une erreur pour fonctionner. Les conseils
d’implémentation publique indiquent que la propriété activitycontinuation est
requise.

Ajouter des droits droit d’utilisation d’association de domaine à


l’application

Ajoutez des droits d’utilisation personnalisés à l’application pour déclarer un ou


plusieurs domaines associés. Pour ce faire, ajoutez un fichier Entitlements.plist à
l’application ou ajoutez l’élément <ItemGroup> suivant au fichier du Fichier projet de
l’application ( .csproj ).

Remplacez applinks:redth.dev par la valeur de domaine correcte. Notez que l’élément


Condition uniquement inclut le droit d’utilisation lorsque l’application est générée pour

iOS ou MacCatalyst.

XML

<ItemGroup
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
== 'ios' Or $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
== 'maccatalyst'">

<!-- For debugging, use '?mode=developer' for debug to bypass apple's


CDN cache -->
<CustomEntitlements
Condition="$(Configuration) == 'Debug'"
Include="com.apple.developer.associated-domains"
Type="StringArray"
Value="applinks:redth.dev?mode=developer" />

<!-- Non debugging, use normal applinks:url value -->


<CustomEntitlements
Condition="$(Configuration) != 'Debug'"
Include="com.apple.developer.associated-domains"
Type="StringArray"
Value="applinks:redth.dev" />

</ItemGroup>

Ajouter des gestionnaires de cycle de vie


Dans le fichier MauiProgram.cs , ajoutez des événements de cycle de vie avec le builder .
Si l’application n’utilise pas Scenes pour la prise en charge de plusieurs fenêtres,
omettez les gestionnaires de cycle de vie pour les méthodes Scene .

C#

builder.ConfigureLifecycleEvents(lifecycle =>
{
#if IOS || MACCATALYST
lifecycle.AddiOS(ios =>
{
ios.FinishedLaunching((app, data)
=> HandleAppLink(app.UserActivity));

ios.ContinueUserActivity((app, userActivity, handler)


=> HandleAppLink(userActivity));

if (OperatingSystem.IsIOSVersionAtLeast(13) ||
OperatingSystem.IsMacCatalystVersionAtLeast(13))
{
ios.SceneWillConnect((scene, sceneSession,
sceneConnectionOptions)
=>
HandleAppLink(sceneConnectionOptions.UserActivities.ToArray()
.FirstOrDefault(
a => a.ActivityType ==
NSUserActivityType.BrowsingWeb)));

ios.SceneContinueUserActivity((scene, userActivity)
=> HandleAppLink(userActivity));
}
});
#elif ANDROID
// ...
#endif
});

Tester une URL


Il est possible que les tests sur iOS soient plus fastidieux que ceux sur Android. Il existe
plusieurs rapports publics de résultats mixtes avec des simulateurs iOS en
fonctionnement. Par exemple, le simulator n’a pas fonctionné lorsque ces instructions
ont été testées. Même si un simulateur arbitraire fonctionne pendant les tests, nous
recommandons d’effectuer des tests avec un appareil iOS.

Une fois l’application déployée sur un appareil, testez les URL en accédant à
Paramètres>Développeur>Liens universels, puis activez Développement de domaines
associés. Ouvrez Diagnostics. Entrez l’URL à tester. Pour la démonstration dans cette
section, l’URL test est https://redth.dev . Vous devriez voir une coche verte avec Ouvre
l’application installée et l’ID d’application de l’application.

Il vaut également la peine de noter à partir de l’étape Ajouter des droits d’utilisation
d’association de domaine à l’application que l’ajout du droit d’utilisation applink avec ?
mode=developer à l’application entraîne le contournement du cache CDN d’Apple par

l’application lors des tests et du débogage, ce qui est utile pour l’itération sur votre
fichier apple-app-site-association JSON.

Applications lancées via un lien profond


Si l’application est lancée via un lien profond, définissez le chemin d’accès pour la
navigation initiale dans la propriété BlazorWebView.StartPath.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Fichiers statiques ASP.NET Core Blazor
Hybrid
Article • 30/11/2023

Cet article explique comment consommer des fichiers de ressources statiques dans les
applications Blazor Hybrid.

Dans une application Blazor Hybrid, les fichiers statiques sont des ressources
d’application accessibles par les composants Razor à l’aide des approches suivantes :

.NET MAUI: .NET MAUI file system helpers


WPF et Windows Forms : ResourceManager

Lorsque les ressources statiques sont utilisées uniquement dans les composants Razor,
les ressources statiques peuvent être consommées à partir de la racine web (dossier
wwwroot ) de la même manière que Blazor WebAssembly et les applications Blazor

Server. Pour plus d’informations, consultez la section Ressources statiques limitées aux
Razor composants.

.NET MAUI
Dans les applications .NET MAUI, les ressources brutes utilisant l’action de génération
MauiAsset et .NET MAUI file system helpers sont utilisées pour les ressources statiques.

7 Notes

Les interfaces, les classes et les types de prise en charge permettant d'utiliser le
stockage sur les appareils sur toutes les plates-formes prises en charge pour des
fonctionnalités telles que le choix d'un fichier, l'enregistrement des préférences et
l'utilisation du stockage sécurisé se trouvent dans l'espace de noms
Microsoft.Maui.Storage. L'espace de noms est disponible dans toute une
application MAUI Blazor Hybrid, il n'est donc pas nécessaire de spécifier une
instruction using dans un fichier de classe ou une directive @using Razor dans un
composant Razor pour l'espace de noms.

Placez les ressources brutes dans le dossier Resources/Raw de l’application. L’exemple de


cette section utilise un fichier texte statique.

Resources/Raw/Data.txt :
text

This is text from a static text file resource.

Le composant Razor suivant :

Appels OpenAppPackageFileAsync pour obtenir un Stream pour la ressource.


Lit leStream avec un StreamReader.
Appels StreamReader.ReadToEndAsync pour lire le fichier.

Pages/StaticAssetExample.razor :

razor

@page "/static-asset-example"
@using System.IO
@using Microsoft.Extensions.Logging
@inject ILogger<StaticAssetExample> Logger

<h1>Static Asset Example</h1>

<p>@dataResourceText</p>

@code {
public string dataResourceText = "Loading resource ...";

protected override async Task OnInitializedAsync()


{
try
{
using var stream =
await FileSystem.OpenAppPackageFileAsync("Data.txt");
using var reader = new StreamReader(stream);

dataResourceText = await reader.ReadToEndAsync();


}
catch (FileNotFoundException ex)
{
dataResourceText = "Data file not found.";
Logger.LogError(ex, "'Resource/Raw/Data.txt' not found.");
}
}
}

Pour plus d’informations, consultez les ressources suivantes :

Ciblez plusieurs plateformes à partir d’un seul projet .NET MAUI (documentation
.NET MAUI)
Améliorez la cohérence avec le redimensionneur (dotnet/maui #4367)
WPF
Placez la ressource dans un dossier de l’application, généralement à la racine du projet,
par exemple un dossier Resources . L’exemple de cette section utilise un fichier texte
statique.

Resources/Data.txt :

text

This is text from a static text file resource.

Si aucun dossier Properties n’existe dans l’application, créez un dossier Properties à la


racine de l’application.

Si le dossier Properties ne contient pas de fichier de ressources ( Resources.resx ), créez


le fichier dans Explorateur de solutions avec la commande de menu contextuel
Ajouter>Nouvel élément.

Double-cliquez sur le fichier Resource.resx .

Sélectionnez Chaînes>Fichiersdans la liste déroulante.

Sélectionnez Ajouter une ressource>Ajouter un fichier existant. Si vous êtes invité par
Visual Studio à confirmer la modification du fichier, sélectionnez Oui. Accédez au dossier
Resources , sélectionnez le fichier Data.txt , puis sélectionnez Ouvrir.

Dans l’exemple de composant suivant, ResourceManager.GetString obtient le texte de la


ressource de chaîne à afficher.

2 Avertissement

N’utilisez jamais de méthodes ResourceManager avec des données non fiables.

StaticAssetExample.razor :

razor

@page "/static-asset-example"
@using System.Resources

<h1>Static Asset Example</h1>

<p>@dataResourceText</p>
@code {
public string dataResourceText = "Loading resource ...";

protected override void OnInitialized()


{
var resources =
new ResourceManager(typeof(WpfBlazor.Properties.Resources));

dataResourceText = resources.GetString("Data") ?? "'Data' not


found.";
}
}

Windows Forms
Placez la ressource dans un dossier de l’application, généralement à la racine du projet,
par exemple un dossier Resources . L’exemple de cette section utilise un fichier texte
statique.

Resources/Data.txt :

text

This is text from a static text file resource.

Examinez les fichiers associés à Form1 dans Explorateur de solutions. Si Form1 n’a pas
de fichier de ressources ( .resx ), ajoutez un fichier Form1.resx à l’aide de la commande
de menu contextuel Ajouter>Nouvel élément.

Double-cliquez sur le fichier Form1.resx .

Sélectionnez Chaînes>Fichiersdans la liste déroulante.

Sélectionnez Ajouter une ressource>Ajouter un fichier existant. Si vous êtes invité par
Visual Studio à confirmer la modification du fichier, sélectionnez Oui. Accédez au dossier
Resources , sélectionnez le fichier Data.txt , puis sélectionnez Ouvrir.

Dans l’exemple de composant suivant :

Le nom de l’assembly de l’application est WinFormsBlazor . Le nom de base de


ResourceManager est défini sur le nom de l’assembly de Form1 (
WinFormsBlazor.Form1 ).

ResourceManager.GetString obtient le texte de la ressource de chaîne à afficher.


2 Avertissement

N’utilisez jamais de méthodes ResourceManager avec des données non fiables.

StaticAssetExample.razor :

razor

@page "/static-asset-example"
@using System.Resources

<h1>Static Asset Example</h1>

<p>@dataResourceText</p>

@code {
public string dataResourceText = "Loading resource ...";

protected override async Task OnInitializedAsync()


{
var resources =
new ResourceManager("WinFormsBlazor.Form1",
this.GetType().Assembly);

dataResourceText = resources.GetString("Data") ?? "'Data' not


found.";
}
}

Ressources statiques limitées aux composants


Razor
Un contrôle BlazorWebView a un fichier hôte configuré (HostPage), généralement
wwwroot/index.html . Le chemin d’accès HostPage est relatif au projet. Toutes les

ressources web statiques (scripts, fichiers CSS, images et autres fichiers) qui sont
référencées à partir d’un BlazorWebView sont relatives à sa configuration HostPage.

Les ressources web statiques d’une Razorbibliothèque de classes (RCL) utilisent des
chemins d’accès spéciaux : _content/{PACKAGE ID}/{PATH AND FILE NAME} . L’espace
réservé {PACKAGE ID} est l’ID de package de la bibliothèque. L’ID de package est défini
par défaut sur le nom d’assembly du projet si <PackageId> n’est pas spécifié dans le
fichier projet. L’espace réservé {PATH AND FILE NAME} correspond au chemin d’accès et
au nom de fichier sous wwwroot . Ces chemins d’accès sont logiquement des sous-
chemins d’accès du dossier wwwroot de l’application, même s’ils proviennent en fait
d’autres packages ou projets. Les ensembles de styles CSS spécifiques à un composant
sont également générés à la racine du dossier wwwroot .

La racine web de HostPage détermine quel sous-ensemble de ressources statiques sont


disponibles :

wwwroot/index.html (recommandé) : tous les éléments du dossier de l'application

wwwroot sont disponibles (par exemple : wwwroot/image.png est disponible à partir

de /image.png ), y compris les sous-dossiers (par exemple :


wwwroot/subfolder/image.png est disponible à partir de /subfolder/image.png ). Les

ressources statiques RCL dans le dossier RCL wwwroot sont disponibles (par
exemple : wwwroot/image.png est disponible à partir du chemin d'accès
_content/{PACKAGE ID}/image.png ), y compris les sous-dossiers (par exemple :
wwwroot/subfolder/image.png est disponible à partir du chemin d’accès

_content/{PACKAGE ID}/subfolder/image.png ).
wwwroot/{PATH}/index.html : toutes les ressources du dossier de l’application

wwwroot/{PATH} sont disponibles à l’aide des chemins d’accès relatifs de la racine

web de l’application. Les ressources statiques RCL dans wwwroot/{PATH} ne sont


pas disponibles, car elles se trouveraient dans un emplacement théorique
inexistant, tel que ../../_content/{PACKAGE ID}/{PATH} , qui n’est pas un chemin
d’accès relatif pris en charge.
wwwroot/_content/{PACKAGE ID}/index.html : toutes les ressources du dossier RCL

wwwroot/{PATH} sont disponibles à l’aide des chemins d’accès relatifs à la racine


web RCL. Les ressources statiques de l’application dans wwwroot/{PATH} ne sont
pas disponibles, car elles se trouveraient dans un emplacement théorique
inexistant, tel que ../../{PATH} , qui n’est pas un chemin d’accès relatif pris en
charge.

Pour la plupart des applications, nous vous recommandons de placer le HostPage à la


racine du dossier wwwroot de l’application, ce qui offre la plus grande flexibilité pour
fournir des ressources statiques à partir de l’application, des RCL et via les sous-dossiers
de l’application et des RCL.

Les exemples suivants illustrent le référencement de ressources statiques à partir de la


racine web (dossier wwwroot ) de l’application avec une racine HostPage dans le dossier
wwwroot .

wwwroot/data.txt :

text
This is text from a static text file resource.

wwwroot/scripts.js :

JavaScript

export function showPrompt(message) {


return prompt(message, 'Type anything here');
}

L’image Jeep® suivante est également utilisée dans l’exemple de cette section. Vous
pouvez faire un clic droit sur l’image suivante pour l’enregistrer localement pour une
utilisation dans une application de test locale.

wwwroot/jeep-yj.png :

Dans un composant Razor :

Le contenu du fichier texte statique peut être lu à l’aide des techniques suivantes :
.NET MAUI : .NET MAUI file system helpers (OpenAppPackageFileAsync)
WPF et Windows Forms : StreamReader.ReadToEndAsync
Les fichiers JavaScript sont disponibles dans les sous-chemins d’accès logiques de
wwwroot à l’aide des chemins d’accès ./ .

L’image peut être l’attribut source ( src ) d’une balise d’image ( <img> ).

StaticAssetExample2.razor :

razor

@page "/static-asset-example-2"
@using Microsoft.Extensions.Logging
@implements IAsyncDisposable
@inject IJSRuntime JS
@inject ILogger<StaticAssetExample2> Logger

<h1>Static Asset Example 2</h1>

<h2>Read a file</h2>

<p>@dataResourceText</p>

<h2>Call JavaScript</h2>

<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>@result</p>

<h2>Show an image</h2>

<p><img alt="1991 Jeep YJ" src="/jeep-yj.png" /></p>

<p>
<em>Jeep</em> and <em>Jeep YJ</em> are registered trademarks of
<a href="https://www.stellantis.com">FCA US LLC (Stellantis NV)</a>.
</p>

@code {
private string dataResourceText = "Loading resource ...";
private IJSObjectReference module;
private string result;

protected override async Task OnInitializedAsync()


{
try
{
dataResourceText = await ReadData();
}
catch (FileNotFoundException ex)
{
dataResourceText = "Data file not found.";
Logger.LogError(ex, "'wwwroot/data.txt' not found.");
}
}

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}

private async Task TriggerPrompt()


{
result = await Prompt("Provide some text");
}

public async ValueTask<string> Prompt(string message) =>


module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;

async ValueTask IAsyncDisposable.DisposeAsync()


{
if (module is not null)
{
await module.DisposeAsync();
}
}
}

Dans les applications .NET MAUI, ajoutez la méthode suivante ReadData au bloc @code
du composant précédent :

C#

private async Task<string> ReadData()


{
using var stream = await
FileSystem.OpenAppPackageFileAsync("wwwroot/data.txt");
using var reader = new StreamReader(stream);

return await reader.ReadToEndAsync();


}

Dans les applications WPF et Windows Forms, ajoutez la méthode suivante ReadData au
bloc @code du composant précédent :

C#

private async Task<string> ReadData()


{
using var reader = new StreamReader("wwwroot/data.txt");

return await reader.ReadToEndAsync();


}

Les fichiers JavaScript colocalisés sont également accessibles dans les sous-chemins
d’accès logiques de wwwroot . Au lieu d’utiliser le script décrit précédemment pour la
fonction showPrompt dans wwwroot/scripts.js , le fichier JavaScript colocalisé suivant
pour le composant StaticAssetExample2 rend également la fonction disponible.

Pages/StaticAssetExample2.razor.js :
JavaScript

export function showPrompt(message) {


return prompt(message, 'Type anything here');
}

Modifiez la référence d’objet de module dans le composant StaticAssetExample2 pour


utiliser le chemin d’accès du fichier JavaScript colocalisé
( ./Pages/StaticAssetExample2.razor.js ) :

C#

module = await JS.InvokeAsync<IJSObjectReference>("import",


"./Pages/StaticAssetExample2.razor.js");

Marques déposées
Jeep et Jeep YJ sont des marques déposées de FCA US LLC (Stellantis NV) .

Ressources supplémentaires
ResourceManager
Créez des fichiers de ressources pour les applications .NET (documentation
Notions de base de .NET)
Guide pratique pour utiliser des ressources dans des applications localisables
(documentation WPF)

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Utilisez les outils de développement du
navigateur avec ASP.NET Core Blazor
Hybrid
Article • 30/11/2023

Cet article explique comment utiliser les outils de développement du navigateur avec
des applications Blazor Hybrid.

Outils de développement du navigateur avec


.NET MAUIBlazor
Vérifiez que le projet Blazor Hybrid est configuré pour prendre en charge les outils de
développement du navigateur. Vous pouvez confirmer la prise en charge des outils de
développement en recherchant AddBlazorWebViewDeveloperTools dans l’application.

Si le projet n’est pas déjà configuré pour les outils de développement du navigateur,
ajoutez la prise en charge en :

1. Localisant l’endroit où l’appel AddMauiBlazorWebView est effectué, probablement


dans le fichier de l’application MauiProgram.cs .

2. En haut du fichier MauiProgram.cs , vérifiez la présence d’une instruction using


pour Microsoft.Extensions.Logging. Si l’instruction using n’est pas présente,
ajoutez-la en haut du fichier :

C#

using Microsoft.Extensions.Logging;

3. Après l’appel à AddMauiBlazorWebView, ajoutez le code suivant :

C#

#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
builder.Logging.AddDebug();
#endif

Pour utiliser les outils de développement du navigateur avec une application Windows :
1. Exécutez l’application .NET MAUIBlazor Hybrid pour Windows et accédez à une
page d’application qui utilise un BlazorWebView. La console des outils de
développement n’est pas disponible à partir des ContentPage sans BlazorWeb
View.

2. Utilisez le raccourci clavier Ctrl + Maj + I pour ouvrir les outils de développement
du navigateur.

3. Les outils de développement fournissent diverses fonctionnalités permettant


d’utiliser des applications, notamment les ressources demandées par la page, la
durée de chargement des ressources et le contenu des ressources chargées.
L’exemple suivant montre l’onglet Console pour afficher les messages de console,
qui inclut tous les messages d’exception générés par l’infrastructure ou le code du
développeur :

Ressources supplémentaires
Outils de développement de Chrome
Présentation des outils de développement de Microsoft Edge
Aide au développeur Safari

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre  Indiquer des commentaires sur
guide du contributeur. le produit
Réutilisez des composants Razor dans
ASP.NET Core Blazor Hybrid
Article • 09/02/2024

Cet article explique comment créer et organiser des composants Razor pour le web et
les applications Web Views dans Blazor Hybrid.

Les composants Razor fonctionnent sur des modèles d’hébergement (Blazor


WebAssembly, Blazor Serveret dans le Web View de Blazor Hybrid) et sur toutes les
plateformes (Android, iOS et Windows). Les modèles et plateformes d’hébergement ont
des fonctionnalités uniques que les composants peuvent exploiter, mais les composants
qui s’exécutent entre les modèles d’hébergement et les plateformes doivent tirer parti
de fonctionnalités uniques séparément, comme le démontrent les exemples suivants :

Blazor WebAssembly prend en charge l’interopérabilité JavaScript synchrone (JS),


qui n’est pas prise en charge par le canal de communication d’interopérabilité JS
strictement asynchrone dans Blazor Server et Web Views des applications Blazor
Hybrid.
Les composants d’une application Blazor Server peuvent accéder aux services
disponibles uniquement sur le serveur, tels qu’un contexte de base de données
Entity Framework.
Les composants d’un BlazorWebView peuvent accéder directement aux
fonctionnalités natives de bureau et d’appareil mobile, telles que les services de
géolocalisation. Les applications Blazor Server et Blazor WebAssembly doivent
s’appuyer sur les interfaces d’API web d’applications sur des serveurs externes pour
fournir des fonctionnalités similaires.

Principes de conception
Pour créer des composants Razor qui peuvent fonctionner en toute transparence sur les
modèles et plateformes d’hébergement, respectez les principes de conception suivants :

Placez le code d’interface utilisateur partagée dans les bibliothèques de classes


(RCL) Razor, qui sont des conteneurs conçus pour gérer des éléments réutilisables
d’interface utilisateur à utiliser sur différents modèles d’hébergement et
plateformes.
Les implémentations de fonctionnalités uniques ne doivent pas exister dans les
RCL. Au lieu de cela, la RCL doit définir des abstractions (interfaces et classes de
base) que les modèles d’hébergement et les plateformes implémentent.
Optez uniquement pour des fonctionnalités uniques en hébergeant un modèle ou
une plateforme. Par exemple, Blazor WebAssembly prend en charge l’utilisation de
IJSInProcessRuntime et IJSInProcessObjectReference dans un composant en tant
qu’optimisation, mais uniquement avec des casts conditionnels et des
implémentations de secours qui s’appuient sur les abstractions universelles
IJSRuntime et IJSObjectReference que tous les modèles et plateformes
d’hébergement prennent en charge. Pour plus d’informations sur
IJSInProcessRuntime, consultez Appeler des fonctions JavaScript à partir de
méthodes .NET dans ASP.NET Core Blazor. Pour plus d’informations sur
IJSInProcessObjectReference, consultez Appeler des fonctions JavaScript à partir de
méthodes .NET dans ASP.NET Core Blazor.
En règle générale, utilisez CSS pour le style HTML dans les composants. Le cas le
plus courant est celui de la cohérence dans l’apparence d’une application. Dans les
endroits où les styles d’interface utilisateur doivent différer d’un modèle
d’hébergement ou d’une plateforme à l’autre, utilisez CSS pour mettre en forme
les différences.
Si une partie de l’interface utilisateur nécessite du contenu supplémentaire ou
différent pour un modèle ou une plateforme d’hébergement cible, le contenu peut
être encapsulé à l’intérieur d’un composant et rendu à l’intérieur de la RCL à l’aide
de DynamicComponent. Une interface utilisateur supplémentaire peut également
être fournie aux composants via des instances RenderFragment. Pour plus
d’informations sur RenderFragment, consultez Fragments de rendu de contenu
enfant et Rendu de fragments pour une logique de rendu réutilisable.

Organisation du code de projet


Autant que possible, placez du code et du contenu statique dans des bibliothèques de
classes (RCL) Razor. Chaque modèle d’hébergement ou plateforme fait référence à la
RCL et enregistre les implémentations individuelles dans la collection de services de
l’application qu’un composant Razor pourrait requérir.

Chaque assembly cible doit contenir uniquement le code spécifique à ce modèle


d’hébergement ou à cette plateforme, ainsi que le code qui permet de démarrer
l’application.
Utilisez des abstractions pour les
fonctionnalités uniques
L’exemple suivant montre comment utiliser une abstraction pour un service de
géolocalisation en hébergeant un modèle et une plateforme.

Dans une bibliothèque de classes (RCL) Razor utilisée par l’application pour obtenir
des données de géolocalisation pour l’emplacement de l’utilisateur sur une carte,
le composant MapComponent Razor injecte une abstraction de service
ILocationService .

App.Web pour Blazor WebAssembly et les projets Blazor Server implémentent


ILocationService en tant que WebLocationService , qui utilise des appels d’API web

pour obtenir des données de géolocalisation.


App.Desktop pour .NET MAUI, WPF et Windows Forms, implémentez
ILocationService en tant que DesktopLocationService . DesktopLocationService

utilise des fonctionnalités d’appareil spécifiques à la plateforme pour obtenir des


données de géolocalisation.
code spécifique à la plateforme.NET
MAUIBlazor
Un modèle courant dans .NET MAUI consiste à créer des implémentations distinctes
pour différentes plateformes, telles que la définition de classes partielles avec des
implémentations spécifiques à la plateforme. Par exemple, consultez le diagramme
suivant, où les classes partielles pour CameraService sont implémentées dans chaque
CameraService.Windows.cs , CameraService.iOS.cs , CameraService.Android.cs , et

CameraService.cs :

Lorsque vous souhaitez intégrer des fonctionnalités spécifiques à la plateforme dans


une bibliothèque de classes pouvant être consommées par d’autres applications, nous
vous recommandons de suivre une approche similaire à celle décrite dans l’exemple
précédent et de créer une abstraction pour le composant Razor :

Placez le composant dans une bibliothèque de classes (RCL) Razor.


À partir d’une bibliothèque de classes .NET MAUI, référencez la RCL et créez les
implémentations spécifiques à la plateforme.
Dans l’application consommatrice, référencez la bibliothèque de classes .NET
MAUI.
L’exemple suivant illustre les concepts des images dans une application qui organise des
photographies :

Une application .NET MAUIBlazor Hybrid utilise InputPhoto à partir d’une RCL
qu’elle référence.
L’application .NET MAUI fait également référence à une bibliothèque de classes
.NET MAUI.
InputPhoto dans la RCL injecte une interface ICameraService , qui est définie dans

la RCL.
Les implémentations de classes partielles CameraService pour ICameraService sont
dans la bibliothèque de classes .NET MAUI ( CameraService.Windows.cs ,
CameraService.iOS.cs , CameraService.Android.cs ), qui fait référence à la RCL.

Ressources supplémentaires
Exemple d’application de podcast .NET MAUIBlazor
Code source (Référentiel GitHub microsoft/dotnet-podcasts)
Application en direct

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Partagez des ressources entre des
clients web et natifs à l’aide d’une
bibliothèque de classes (RCL) Razor
Article • 30/11/2023

Utilisez une bibliothèque de classes (RCL) Razor pour partager des composants Razor,
du code C# et des ressources statiques entre des projets clients web et natifs.

Cet article s’appuie sur les concepts généraux trouvés dans les articles suivants :

Consommer des composants Razor ASP.NET Core d’une bibliothèque de classes


Razor (RCL)
Interface utilisateur Razor réutilisable dans les bibliothèques de classes avec
ASP.NET Core

Les exemples de cet article partagent des ressources entre une application Blazor Server
et une application .NET MAUIBlazor Hybriddans la même solution :

Bien qu’une application Blazor Server soit utilisée, les directives s’appliquent
également aux applications Blazor WebAssembly qui partagent des ressources
avec une application Blazor Hybrid.
Les projets se trouvent dans la même solution, mais une RCL peut fournir des
ressources partagées aux projets en dehors d’une solution.
La RCL est ajoutée en tant que projet à la solution, mais n’importe quelle RCL peut
être publiée en tant que package NuGet. Un package NuGet peut fournir des
ressources partagées à des projets clients natifs et web.
L’ordre de création des projets n’est pas important. Toutefois, les projets qui
s’appuient sur une RCL pour les ressources doivent créer une référence de projet à
la RCL après la création de la LRC.

Pour obtenir des conseils sur la création d’une RCL, consultez Utiliser des composants
Razor ASP.NET Core à partir Razor d’une bibliothèque de classes (RCL). Si vous le
souhaitez, accédez aux conseils supplémentaires sur les RCL qui s’appliquent largement
aux applications ASP.NET Core dans l’interface utilisateur Razor réutilisable dans les
bibliothèques de classes avec ASP.NET Core.

Exemple d’application
Pour obtenir un exemple des scénarios décrits dans cet article, consultez l’exemple
d’application Podcasts .NET :
Référentiel GitHub (microsoft/dotnet-podcasts)
Exemple d’application en cours d’exécution (Azure Container Apps Service)

L’application .NET Podcasts présente les technologies suivantes :

.NET
ASP.NET Core
Blazor
.NET MAUI
Azure Container Apps
Orleans

Partagez des composants d’interface utilisateur


web Razor, du code et des ressources statiques
Les composants d’une RCL peuvent être partagés simultanément par des applications
clientes web et natives créées à l’aide de Blazor. Les instructions fournies dans Utiliser
des composants ASP.NET Core Razor à partir d’une bibliothèque de classes (RCL) Razor
expliquent comment partager des composants Razor à l’aide d’une bibliothèque de
classes (RCL) Razor. Les mêmes instructions s’appliquent à la réutilisation des
composants Razor d’une RCL dans une application Blazor Hybrid.

Les espaces de noms des composants sont dérivés de l’ID du package ou du nom de
l’assembly de la RCL et du chemin d’accès au dossier du composant dans la RCL. Pour
plus d’informations, consultez Composants ASP.NET Core Razor. Les directives @using
peuvent être placées dans des fichiers _Imports.razor pour les composants et le code,
comme le montre l’exemple suivant pour une RCL nommée SharedLibrary avec un
dossier Shared de composants Razor partagés et un dossier Data de classes de données
partagées :

razor

@using SharedLibrary
@using SharedLibrary.Shared
@using SharedLibrary.Data

Placez les ressources statiques partagées dans le dossier wwwroot de la RCL et mettez à
jour les chemins d’accès aux ressources statiques dans l’application pour utiliser le
format de chemin d’accès suivant :

_content/{PACKAGE ID/ASSEMBLY NAME}/{PATH}/{FILE NAME}


Espaces réservés :

{PACKAGE ID/ASSEMBLY NAME} : l’ID de package ou nom d’assembly de la RCL.

{PATH} : le chemin d’accès facultatif dans le dossier wwwroot de la RCL.


{FILE NAME} : le nom de fichier de la ressource statique.

Le format de chemin d’accès précédent est également utilisé dans l’application pour les
ressources statiques fournies par un package NuGet ajouté à la RCL.

Pour une RCL nommée SharedLibrary et utilisant la feuille de style de démarrage


minifiée comme exemple :

_content/SharedLibrary/css/bootstrap/bootstrap.min.css

Pour plus d’informations sur la façon de partager des ressources statiques entre des
projets, consultez les articles suivants :

Consommer des composants Razor ASP.NET Core d’une bibliothèque de classes


Razor (RCL)
Interface utilisateur Razor réutilisable dans les bibliothèques de classes avec
ASP.NET Core

Le fichier racine index.html est généralement spécifique à l’application et doit rester


dans l’application Blazor Hybrid ou l’application Blazor WebAssembly. Le fichier
index.html n’est généralement pas partagé.

Le composant racine Razor ( App.razor ou Main.razor ) peut être partagé, mais peut
souvent avoir besoin d’être spécifique à l’application d’hébergement. Par exemple,
App.razor est différent dans les modèles de projet Blazor Server et Blazor WebAssembly

lorsque l’authentification est activée. Vous pouvez ajouter le


AdditionalAssembliesparamètre pour spécifier l’emplacement de tous les composants
routables partagés, et vous pouvez spécifier un composant de disposition par défaut
partagé pour le routeur par nom de type.

Fournissez du code et des services


indépendamment du modèle d’hébergement
Lorsque le code doit différer selon les modèles d’hébergement ou les plateformes
cibles, abstrayez le code en tant qu’interfaces et injectez les implémentations de service
dans chaque projet.
L’exemple de données météorologiques suivant présente différentes implémentations
de services de prévisions météorologiques :

Utilisation d’une requête HTTP pour Blazor Hybrid et Blazor WebAssembly.


Requête de données directement pour Blazor Server.

L’exemple utilise les spécifications et conventions suivantes :

La RCL est nommée SharedLibrary et contient les dossiers et espaces de noms


suivants :
Data : contient la classe WeatherForecast , qui sert de modèle pour les données

météorologiques.
Interfaces : contient l’interface de service pour les implémentations de service,

nommée IWeatherForecastService .
Le composant FetchData est conservé dans le dossier Pages de la RCL, qui est
routable par toutes les applications qui consomment la RCL.
Chaque application Blazor gère une implémentation de service qui implémente
l’interface IWeatherForecastService .

Data/WeatherForecast.cs dans la RCL :

C#

namespace SharedLibrary.Data;

public class WeatherForecast


{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}

Interfaces/IWeatherForecastService.cs dans la RCL :

C#

using SharedLibrary.Data;

namespace SharedLibrary.Interfaces;

public interface IWeatherForecastService


{
Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate);
}
Le fichier _Imports.razor dans la RCL inclut les espaces de noms ajoutés suivants :

razor

@using SharedLibrary.Data
@using SharedLibrary.Interfaces

Services/WeatherForecastService.cs dans les applications Blazor Hybrid et Blazor

WebAssembly :

C#

using System.Net.Http.Json;
using SharedLibrary.Data;
using SharedLibrary.Interfaces;

namespace {APP NAMESPACE}.Services;

public class WeatherForecastService : IWeatherForecastService


{
private readonly HttpClient http;

public WeatherForecastService(HttpClient http)


{
this.http = http;
}

public async Task<WeatherForecast[]?> GetForecastAsync(DateTime


startDate) =>
await http.GetFromJsonAsync<WeatherForecast[]?>("WeatherForecast");
}

Dans l’exemple précédent, l’espace réservé {APP NAMESPACE} est l’espace de noms de
l’application.

Services/WeatherForecastService.cs dans l’application Blazor Server :

C#

using SharedLibrary.Data;
using SharedLibrary.Interfaces;

namespace {APP NAMESPACE}.Services;

public class WeatherForecastService : IWeatherForecastService


{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy",
"Hot"
};

public async Task<WeatherForecast[]?> GetForecastAsync(DateTime


startDate) =>
await Task.FromResult(Enumerable.Range(1, 5)
.Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToArray());
}

Dans l’exemple précédent, l’espace réservé {APP NAMESPACE} est l’espace de noms de
l’application.

Les applications Blazor Hybrid, Blazor WebAssemblyet Blazor Server inscrivent leurs
implémentations de service de prévision météorologique
( Services.WeatherForecastService ) pour IWeatherForecastService .

Le projet Blazor WebAssembly inscrit également un HttpClient. Le HttpClient inscrit par


défaut dans une application créée à partir du modèle de projet Blazor WebAssembly est
suffisant à cet effet. Pour plus d’informations, consultez Appeler une API web à partir
d’une application Blazor ASP.NET Core.

Pages/FetchData.razor dans la RCL :

razor

@page "/fetchdata"
@inject IWeatherForecastService ForecastService

<PageTitle>Weather forecast</PageTitle>

<h1>Weather forecast</h1>

@if (forecasts == null)


{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}

@code {
private WeatherForecast[]? forecasts;

protected override async Task OnInitializedAsync()


{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
}

Ressources supplémentaires
Consommer des composants Razor ASP.NET Core d’une bibliothèque de classes
Razor (RCL)
Interface utilisateur Razor réutilisable dans les bibliothèques de classes avec
ASP.NET Core
Prise en charge de l’isolation CSS avec les bibliothèques de classes Razor

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Passer les paramètres du composant
racine dans ASP.NET Core Blazor Hybrid
Article • 30/11/2023

Cet article explique comment transmettre des paramètres de composant racine dans
une application Blazor Hybrid.

La classe RootComponent d’une BlazorWebView définit une propriété Parameters de type


IDictionary<string, object?>? , qui représente un dictionnaire facultatif de paramètres

à passer au composant racine :

.NET MAUI: Microsoft.AspNetCore.Components.WebView.Maui.RootComponent


WPF : Microsoft.AspNetCore.Components.WebView.Wpf.RootComponent
Windows Forms :
Microsoft.AspNetCore.Components.WebView.WindowsForms.RootComponent

L’exemple suivant transmet un modèle de vue au composant racine, qui transmet à son
tour le modèle de vue en tant que type en cascade à un composant Razor dans la partie
Blazor de l’application. L’exemple est basé sur l’exemple de pavé numérique dans la
documentation .NET MAUI :

Liaison de données et MVVM : Commande (documentation .NET MAUI) : explique


la liaison de données avec le modèle-vue-vue modèle à l’aide d’un exemple de
pavé numérique.
.NET MAUI Exemples

Bien que l’exemple de pavé numérique se concentre sur l’implémentation modèle-vue-


vue modèle dans les applications .NET MAUIBlazor Hybrid :

Le dictionnaire d’objets transmis aux composants racines peut inclure n’importe


quel type à tout endroit où vous devez passer un ou plusieurs paramètres au
composant racine en vue d’une utilisation par les composants Razor dans
l’application.
Les concepts présentés dans l’exemple .NET MAUIBlazor suivant sont les mêmes
pour les applications Blazor Windows Forms et les applications Blazor WPF.

Placez le modèle d’affichage suivant dans votre application .NET MAUIBlazor Hybrid.

KeypadViewModel.cs :

C#
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace MauiBlazor;

public class KeypadViewModel : INotifyPropertyChanged


{
public event PropertyChangedEventHandler PropertyChanged;

private string _inputString = "";


private string _displayText = "";
private char[] _specialChars = { '*', '#' };

public ICommand AddCharCommand { get; private set; }


public ICommand DeleteCharCommand { get; private set; }

public string InputString


{
get => _inputString;
private set
{
if (_inputString != value)
{
_inputString = value;
OnPropertyChanged();
DisplayText = FormatText(_inputString);

// Perhaps the delete button must be enabled/disabled.


((Command)DeleteCharCommand).ChangeCanExecute();
}
}
}

public string DisplayText


{
get => _displayText;
set
{
if (_displayText != value)
{
_displayText = value;
OnPropertyChanged();
}
}
}

public KeypadViewModel()
{
// Command to add the key to the input string
AddCharCommand = new Command<string>((key) => InputString += key);

// Command to delete a character from the input string when allowed


DeleteCharCommand =
new Command(
// Command strips a character from the input string
() => InputString = InputString.Substring(0,
InputString.Length - 1),

// CanExecute is processed here to return true when there's


something to delete
() => InputString.Length > 0
);
}

string FormatText(string str)


{
bool hasNonNumbers = str.IndexOfAny(_specialChars) != -1;
string formatted = str;

// Format the string based on the type of data and the length
if (hasNonNumbers || str.Length < 4 || str.Length > 10)
{
// Special characters exist, or the string is too small or large
for special formatting
// Do nothing
}

else if (str.Length < 8)


formatted = string.Format("{0}-{1}", str.Substring(0, 3),
str.Substring(3));

else
formatted = string.Format("({0}) {1}-{2}", str.Substring(0, 3),
str.Substring(3, 3), str.Substring(6));

return formatted;
}

public void OnPropertyChanged([CallerMemberName] string name = "") =>


PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

Dans l’exemple de cet article, l’espace de noms racine de l’application est MauiBlazor .
Modifiez l’espace de noms de KeypadViewModel pour qu’il corresponde à l’espace de
noms racine de l’application :

C#

namespace MauiBlazor;

7 Notes
Au moment où le modèle de vue KeypadViewModel a été créé pour l’exemple
d’application .NET MAUI et la documentation .NET MAUI, les modèles de vue ont
été placés dans un dossier nommé ViewModels , mais l’espace de noms a été défini
à la racine de l’application et n’incluait pas le nom du dossier. Si vous souhaitez
mettre à jour l’espace de noms pour inclure le dossier dans le fichier
KeypadViewModel.cs , modifiez l’exemple de code de cet article pour qu’il

corresponde. Ajoutez des instructions using (C#) et @using (Razor) aux fichiers
suivants ou qualifiez complètement les références au type de modèle de vue
comme {APP NAMESPACE}.ViewModels.KeypadViewModel , où l’espace réservé {APP
NAMESPACE} est l’espace de noms racine de l’application.

Bien que vous puissiez définir Parameters directement en XAML, l’exemple suivant
nomme le composant racine ( rootComponent ) dans le fichier XAML et définit le
dictionnaire de paramètres dans le fichier code-behind.

Dans MainPage.xaml :

XAML

<RootComponent x:Name="rootComponent"
Selector="#app"
ComponentType="{x:Type local:Main}" />

Dans le fichier code-behind ( MainPage.xaml.cs ), affectez le modèle de vue dans le


constructeur :

C#

public MainPage()
{
InitializeComponent();

rootComponent.Parameters =
new Dictionary<string, object>
{
{ "KeypadViewModel", new KeypadViewModel() }
};
}

L’exemple suivant met en cascade les hiérarchies de composants d’objet


( KeypadViewModel ) dans la partie Blazor de l’application en tant que CascadingValue.

Dans le composant Main ( Main.razor ) :


Ajoutez un paramètre correspondant au type de l’objet passé au composant
racine :

razor

@code {
[Parameter]
public KeypadViewModel KeypadViewModel { get; set; }
}

Mettez en cascade le KeypadViewModel avec le composant CascadingValue. Mettez


à jour le contenu XAML <Found> vers le balisage suivant :

XAML

<Found Context="routeData">
<CascadingValue Value="@KeypadViewModel">
<RouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
</CascadingValue>
</Found>

À ce stade, le type en cascade est disponible pour les composants Razor à travers
l’application en tant que CascadingParameter.

L’exemple de composant Keypad suivant :

Affiche la valeur actuelle de KeypadViewModel.DisplayText .


Autorise la suppression de caractères en appelant la commande
KeypadViewModel.DeleteCharCommand si la longueur de chaîne d’affichage est

supérieure à 0 (zéro), condition vérifiée par l’appel à ICommand.CanExecute.


Autorise l’ajout de caractères en appelant KeypadViewModel.AddCharCommand avec la
touche enfoncée dans l’interface utilisateur.

Pages/Keypad.razor :

razor

@page "/keypad"

<h1>Keypad</h1>

<table id="keypad">
<thead>
<tr>
<th colspan="2">@KeypadViewModel.DisplayText</th>
<th><button @onclick="DeleteChar">&#x21E6;</button></th>
</tr>
</thead>
<tbody>
<tr>
<td><button @onclick="@(e => AddChar("1"))">1</button></td>
<td><button @onclick="@(e => AddChar("2"))">2</button></td>
<td><button @onclick="@(e => AddChar("3"))">3</button></td>
</tr>
<tr>
<td><button @onclick="@(e => AddChar("4"))">4</button></td>
<td><button @onclick="@(e => AddChar("5"))">5</button></td>
<td><button @onclick="@(e => AddChar("6"))">6</button></td>
</tr>
<tr>
<td><button @onclick="@(e => AddChar("7"))">7</button></td>
<td><button @onclick="@(e => AddChar("8"))">8</button></td>
<td><button @onclick="@(e => AddChar("9"))">9</button></td>
</tr>
<tr>
<td><button @onclick="@(e => AddChar("*"))">*</button></td>
<td><button @onclick="@(e => AddChar("0"))">0</button></td>
<td><button @onclick="@(e => AddChar("#"))">#</button></td>
</tr>
</tbody>
</table>

@code {
[CascadingParameter]
protected KeypadViewModel KeypadViewModel { get; set; }

private void DeleteChar()


{
if (KeypadViewModel.DeleteCharCommand.CanExecute(null))
{
KeypadViewModel.DeleteCharCommand.Execute(null);
}
}

private void AddChar(string key)


{
KeypadViewModel.AddCharCommand.Execute(key);
}
}

Purement à des fins de démonstration, mettez en forme les boutons en plaçant les
styles CSS suivants dans le contenu <head> du fichier wwwroot/index.html :

HTML

<style>
#keypad button {
border: 1px solid black;
border-radius:6px;
height: 35px;
width:80px;
}
</style>

Créez une entrée de navigation de barre latérale dans le composant NavMenu


( Shared/NavMenu.razor ) avec le balisage suivant :

razor

<div class="nav-item px-3">


<NavLink class="nav-link" href="keypad">
<span class="oi oi-list-rich" aria-hidden="true"></span> Keypad
</NavLink>
</div>

Ressources supplémentaires
Héberger une application web Blazor dans une application .NET MAUI à l’aide de
BlazorWebView
Liaison de données et MVVM : Commande (documentation .NET MAUI)
Valeurs et paramètres en cascade ASP.NET Core Blazor

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Authentification et autorisation avec
ASP.NET Core Blazor Hybrid
Article • 09/02/2024

Cet article décrit la prise en charge d’ASP.NET Core pour la configuration et la gestion
de la sécurité et d’ASP.NET Core Identity dans les applications Blazor Hybrid.

L’authentification dans les applications Blazor Hybrid est gérée par les bibliothèques de
plateforme natives, qui offrent des garanties de sécurité renforcées que le bac à sable
du navigateur ne peut pas offrir. L’authentification des applications natives utilise un
mécanisme spécifique au système d’exploitation ou un protocole fédéré, tel qu’OpenID
Connect (OIDC) . Suivez les instructions du fournisseur d’identité que vous avez
sélectionné pour l’application, puis poursuivez l’intégration de l’identité avec Blazor en
suivant les instructions fournies dans cet article.

L’intégration de l’authentification doit atteindre les objectifs suivants pour les


composants et les services Razor :

Utilisez les abstractions figurant dans le package


Microsoft.AspNetCore.Components.Authorization , telles que AuthorizeView.
Réagissez aux modifications apportées au contexte d’authentification.
Accédez aux informations d’identification provisionnées par l’application à partir
du fournisseur d’identité, telles que les jetons d’accès pour effectuer des appels
d’API autorisés.

Une fois que l’authentification a été ajoutée à une application .NET MAUI, WPF ou
Windows Forms, et que les utilisateurs sont en mesure de se connecter et de se
déconnecter correctement, intégrez l’authentification à Blazor pour rendre l’utilisateur
authentifié disponible pour les composants et les services Razor. Procédez comme suit :

Référencez le package Microsoft.AspNetCore.Components.Authorization .

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET,
consultez les articles figurant sous Installer et gérer des packages dans Flux de
travail de la consommation des packages (documentation NuGet). Vérifiez
les versions du package sur NuGet.org .
Implémentez un AuthenticationStateProvider personnalisé, qui constitue
l’abstraction que les composants Razor utilisent pour accéder aux informations
relatives à l’utilisateur authentifié et pour recevoir des mises à jour lorsque l’état
d’authentification change.

Inscrivez le fournisseur d’état d’authentification personnalisé dans le conteneur


d’injection de dépendances.

Les applications .NET MAUI utilisent Xamarin.Essentials : Authentificateur web : La classe


WebAuthenticator permet à l’application de lancer des flux d’authentification basés sur

un navigateur qui sont à l’écoute d’un rappel vers une URL spécifique inscrite auprès de
l’application.

Pour obtenir des conseils supplémentaires, consultez les ressources suivantes :

Authentificateur web (.NET MAUI documentation


Sample.Server.WebAuthenticator exemple d’application

Créer un élément AuthenticationStateProvider


personnalisé sans mises à jour de modification
de l’utilisateur
Si l’application authentifie l’utilisateur immédiatement après le lancement de
l’application et que l’utilisateur authentifié reste le même pour l’intégralité de la durée
de vie de l’application, les notifications de modification de l’utilisateur ne sont pas
requises et l’application fournit uniquement des informations sur l’utilisateur authentifié.
Dans ce scénario, l’utilisateur se connecte à l’application quand celle-ci est ouverte, et
l’application affiche à nouveau l’écran de connexion une fois l’utilisateur déconnecté.
L’élément ExternalAuthStateProvider suivant est un exemple d’implémentation d’un
élément AuthenticationStateProvider personnalisé pour ce scénario d’authentification.

7 Notes

L’élément AuthenticationStateProvider personnalisé suivant ne déclare pas


d’espace de noms pour que l’exemple de code s’applique à n’importe quelle
application Blazor Hybrid. Toutefois, une bonne pratique consiste à fournir l’espace
de noms de votre application quand vous implémentez l’exemple dans une
application de production.

ExternalAuthStateProvider.cs :
C#

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider


{
private readonly Task<AuthenticationState> authenticationState;

public ExternalAuthStateProvider(AuthenticatedUser user) =>


authenticationState = Task.FromResult(new
AuthenticationState(user.Principal));

public override Task<AuthenticationState> GetAuthenticationStateAsync()


=>
authenticationState;
}

public class AuthenticatedUser


{
public ClaimsPrincipal Principal { get; set; } = new();
}

Les étapes suivantes décrivent comment :

Ajouter les espaces de noms nécessaires.


Ajouter les services d’autorisation et les abstractions Blazor à la collection de
services.
Générer la collection de services.
Résoudre le service AuthenticatedUser pour définir le principal de revendications
de l’utilisateur authentifié. Pour plus d’informations, consultez la documentation de
votre fournisseur d’identités.
Retourner l’hôte généré.

Dans la méthode MauiProgram.CreateMauiApp de MauiProgram.cs , ajoutez des espaces de


noms pour Microsoft.AspNetCore.Components.Authorization et System.Security.Claims :

C#

using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;

Supprimez la ligne de code suivante qui retourne un


élémentMicrosoft.Maui.Hosting.MauiApp généré :

diff
- return builder.Build();

Remplacez la ligne de code précédente par le code suivant. Ajoutez du code


OpenID/MSAL pour authentifier l’utilisateur. Pour plus d’informations, consultez la
documentation de votre fournisseur d’identités.

C#

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();

var authenticatedUser = host.Services.GetRequiredService<AuthenticatedUser>


();

/*
Provide OpenID/MSAL code to authenticate the user. See your identity
provider's
documentation for details.

The user is represented by a new ClaimsPrincipal based on a new


ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());

authenticatedUser.Principal = user;

return host;

Créer un élément AuthenticationStateProvider


personnalisé avec des mises à jour de
modification de l’utilisateur
Pour mettre à jour l’utilisateur pendant l’exécution de l’application Blazor, appelez
NotifyAuthenticationStateChanged dans l’implémentation AuthenticationStateProvider
en utilisant l’une des approches suivantes :

Signaler une mise à jour d’authentification en dehors de BlazorWebView)


Gérer l’authentification dans BlazorWebView

Signaler une mise à jour d’authentification en dehors de


BlazorWebView (Option 1)
Un AuthenticationStateProvider personnalisé peut utiliser un service global pour signaler
une mise à jour d’authentification. Nous recommandons que le service offre un
événement auquel AuthenticationStateProvider pourra s’abonner, l’événement appelant
NotifyAuthenticationStateChanged.

7 Notes

L’élément AuthenticationStateProvider personnalisé suivant ne déclare pas


d’espace de noms pour que l’exemple de code s’applique à n’importe quelle
application Blazor Hybrid. Toutefois, une bonne pratique consiste à fournir l’espace
de noms de votre application quand vous implémentez l’exemple dans une
application de production.

ExternalAuthStateProvider.cs :

C#

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class ExternalAuthStateProvider : AuthenticationStateProvider


{
private AuthenticationState currentUser;

public ExternalAuthStateProvider(ExternalAuthService service)


{
currentUser = new AuthenticationState(service.CurrentUser);

service.UserChanged += (newUser) =>


{
currentUser = new AuthenticationState(newUser);
NotifyAuthenticationStateChanged(Task.FromResult(currentUser));
};
}

public override Task<AuthenticationState> GetAuthenticationStateAsync()


=>
Task.FromResult(currentUser);
}

public class ExternalAuthService


{
public event Action<ClaimsPrincipal>? UserChanged;
private ClaimsPrincipal? currentUser;

public ClaimsPrincipal CurrentUser


{
get { return currentUser ?? new(); }
set
{
currentUser = value;

if (UserChanged is not null)


{
UserChanged(currentUser);
}
}
}
}

Dans la méthode MauiProgram.CreateMauiApp de MauiProgram.cs , ajoutez un espace de


noms pour Microsoft.AspNetCore.Components.Authorization :

C#

using Microsoft.AspNetCore.Components.Authorization;

Ajoutez les services d’autorisation et les abstractions Blazor à la collection de services :

C#

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();

Où que l’application authentifie un utilisateur, résolvez le service ExternalAuthService :

C#

var authService = host.Services.GetRequiredService<ExternalAuthService>();

Exécutez votre code OpenID/MSAL personnalisé pour authentifier l’utilisateur. Pour plus
d’informations, consultez la documentation de votre fournisseur d’identités. L’utilisateur
authentifié ( authenticatedUser dans l’exemple suivant) est un nouveau ClaimsPrincipal
basé sur un nouvel élément ClaimsIdentity.

Définissez l’utilisateur actuel sur l’utilisateur authentifié :

C#

authService.CurrentUser = authenticatedUser;
Une alternative à l’approche précédente consiste à définir le principal de l’utilisateur sur
System.Threading.Thread.CurrentPrincipal au lieu de le définir via un service, ce qui évite
d’utiliser le conteneur d’injection de dépendances :

C#

public class CurrentThreadUserAuthenticationStateProvider :


AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
=>
Task.FromResult(
new AuthenticationState(Thread.CurrentPrincipal as
ClaimsPrincipal ??
new ClaimsPrincipal(new ClaimsIdentity())));
}

Avec l’approche alternative, seuls les services d’autorisation (AddAuthorizationCore) et


CurrentThreadUserAuthenticationStateProvider

( .TryAddScoped<AuthenticationStateProvider,
CurrentThreadUserAuthenticationStateProvider>() ) sont ajoutés à la collection de

services.

Gérer l’authentification dans BlazorWebView (Option 2)


Un AuthenticationStateProvider personnalisé peut inclure des méthodes
supplémentaires pour déclencher la connexion et la déconnexion et mettre à jour
l’utilisateur.

7 Notes

L’élément AuthenticationStateProvider personnalisé suivant ne déclare pas


d’espace de noms pour que l’exemple de code s’applique à n’importe quelle
application Blazor Hybrid. Toutefois, une bonne pratique consiste à fournir l’espace
de noms de votre application quand vous implémentez l’exemple dans une
application de production.

ExternalAuthStateProvider.cs :

C#

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
public class ExternalAuthStateProvider : AuthenticationStateProvider
{
private ClaimsPrincipal currentUser = new ClaimsPrincipal(new
ClaimsIdentity());

public override Task<AuthenticationState> GetAuthenticationStateAsync()


=>
Task.FromResult(new AuthenticationState(currentUser));

public Task LogInAsync()


{
var loginTask = LogInAsyncCore();
NotifyAuthenticationStateChanged(loginTask);

return loginTask;

async Task<AuthenticationState> LogInAsyncCore()


{
var user = await LoginWithExternalProviderAsync();
currentUser = user;

return new AuthenticationState(currentUser);


}
}

private Task<ClaimsPrincipal> LoginWithExternalProviderAsync()


{
/*
Provide OpenID/MSAL code to authenticate the user. See your
identity
provider's documentation for details.

Return a new ClaimsPrincipal based on a new ClaimsIdentity.


*/
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());

return Task.FromResult(authenticatedUser);
}

public void Logout()


{
currentUser = new ClaimsPrincipal(new ClaimsIdentity());
NotifyAuthenticationStateChanged(
Task.FromResult(new AuthenticationState(currentUser)));
}
}

Dans l'exemple précédent :

L’appel à LogInAsyncCore déclenche le processus de connexion.


L’appel à NotifyAuthenticationStateChanged notifie qu’une mise à jour est en
cours, ce qui permet à l’application de fournir une interface utilisateur temporaire
pendant le processus de connexion ou de déconnexion.
Le retour de loginTask retourne la tâche afin que le composant qui a déclenché la
connexion puisse attendre et réagir une fois la tâche terminée.
La méthode LoginWithExternalProviderAsync est implémentée par le développeur
pour connecter l’utilisateur à l’aide du kit SDK du fournisseur d’identité. Pour plus
d’informations, consultez la documentation de votre fournisseur d’identité.
L’utilisateur authentifié ( authenticatedUser ) est un nouveau ClaimsPrincipal basé
sur un nouvel élément ClaimsIdentity.

Dans la méthode MauiProgram.CreateMauiApp de MauiProgram.cs , ajoutez les services


d’autorisation et l’abstraction Blazor à la collection de services :

C#

builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();

Le composant LoginComponent suivant illustre comment connecter un utilisateur. Dans


une application classique, le composant LoginComponent est affiché uniquement dans un
composant parent si l’utilisateur n’est pas connecté à l’application.

Shared/LoginComponent.razor :

razor

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Login">Log in</button>

@code
{
public async Task Login()
{
await ((ExternalAuthStateProvider)AuthenticationStateProvider)
.LogInAsync();
}
}

Le composant LogoutComponent suivant illustre comment déconnecter un utilisateur.


Dans une application classique, le composant LogoutComponent est affiché uniquement
dans un composant parent si l’utilisateur est connecté à l’application.

Shared/LogoutComponent.razor :
razor

@inject AuthenticationStateProvider AuthenticationStateProvider

<button @onclick="Logout">Log out</button>

@code
{
public async Task Logout()
{
await ((ExternalAuthStateProvider)AuthenticationStateProvider)
.Logout();
}
}

Accès à d’autres informations d’authentification


Blazor ne définit pas d’abstraction pour gérer d’autres informations d’identification,
telles que les jetons d’accès à utiliser pour les requêtes HTTP adressées aux API web.
Nous vous recommandons de suivre les instructions du fournisseur d’identité pour gérer
les informations d’identification de l’utilisateur avec les primitives fournies par le kit SDK
du fournisseur d’identité.

Il est courant que les kits SDK du fournisseur d’identité utilisent un magasin de jetons
pour les informations d’identification d’utilisateur stockées dans l’appareil. Si la primitive
du magasin de jetons du kit SDK est ajoutée au conteneur de service, consommez la
primitive du kit SDK dans l’application.

Le framework Blazor ne connaît pas les informations d’authentification d’un utilisateur et


n’interagit en aucune manière avec ces informations, de sorte que le code de
l’application est libre de suivre l’approche que vous jugez la plus pratique. Toutefois,
suivez les instructions générales de sécurité fournies dans la section suivante, Autres
considérations relatives à la sécurité de l’authentification, quand vous implémentez du
code d’authentification dans une application.

Autres considérations relatives à la sécurité de


l’authentification
Le processus d’authentification est externe à Blazor et nous recommandons aux
développeurs d’accéder aux instructions du fournisseur d’identité pour obtenir des
conseils de sécurité supplémentaires.

Lors de l’implémentation de l’authentification :


Évitez l’authentification dans le contexte du Web View. Par exemple, évitez
d’utiliser une bibliothèque JavaScript OAuth pour effectuer le flux
d’authentification. Dans une application monopage, les jetons d’authentification ne
sont pas masqués dans JavaScript et peuvent être facilement découverts par des
utilisateurs malveillants et utilisés à des fins nuisibles. Les applications natives ne
courent pas ce risque, car elles ne peuvent obtenir que des jetons en dehors du
contexte du navigateur, ce qui signifie que les scripts tiers non autorisés ne
peuvent pas voler les jetons et compromettre l’application.
Évitez d’implémenter vous-même le workflow d’authentification. Dans la plupart
des cas, les bibliothèques de plateforme gèrent en toute sécurité le workflow
d’authentification, en utilisant le navigateur du système au lieu d’utiliser un
élément Web View personnalisé pouvant être détourné.
Évitez d’utiliser le contrôle Web View de la plateforme pour effectuer
l’authentification. Au lieu de cela, utilisez le navigateur du système si possible.
Évitez de transmettre les jetons au contexte de document (JavaScript). Dans
certains cas, une bibliothèque JavaScript dans le document est nécessaire pour
effectuer un appel autorisé à un service externe. Au lieu de rendre le jeton
disponible pour JavaScript via l’interopérabilité JS :
Fournissez un jeton temporaire généré à la bibliothèque et dans le Web View.
Interceptez la demande réseau sortante dans le code.
Remplacez le jeton temporaire par le jeton réel et vérifiez que la destination de
la demande est valide.

Ressources supplémentaires
Authentification et autorisation avec ASP.NET Core Blazor
Considérations relatives à la sécurité avec ASP.NET Core Blazor Hybrid
6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Considérations relatives à la sécurité
Blazor Hybrid ASP.NET Core
Article • 05/12/2023

Cet article décrit les considérations relatives à la sécurité pour les applications Blazor
Hybrid.

Les applications Blazor Hybrid qui affichent du contenu web exécutent du code .NET à
l’intérieur d’une plateforme Web View. Le code .NET interagit avec le contenu web via
un canal d’interopérabilité entre le code .NET et le Web View.

Le contenu web rendu dans le Web View peut provenir des ressources fournies par
l’application à partir de l’un des emplacements suivants :

Le dossier wwwroot dans l’application.


Une source externe à l’application. Par exemple, une source réseau, telle
qu’Internet.

Une limite d’approbation existe entre le code .NET et le code qui s’exécute à l’intérieur
du Web View. Le code .NET est fourni par l’application et tous les packages tiers
approuvés que vous avez installés. Une fois l’application générée, les sources de
contenu du code Web View .NET ne peuvent pas changer.

Contrairement aux sources de code .NET du contenu, les sources de contenu du code
qui s’exécute à l’intérieur de Web View peuvent provenir non seulement de l’application,
mais également de sources externes. Par exemple, les ressources statiques d’un réseau
de distribution de contenu (CDN) externe peuvent être utilisées ou affichées par
l’application Web View.

Considérez le code à l’intérieur du Web View comme non fiable de la même manière
que le code s’exécutant à l’intérieur du navigateur pour une application web n’est pas
fiable. Les mêmes menaces et recommandations générales en matière de sécurité
s’appliquent aux ressources non fiables dans les applications Blazor Hybrid que pour les
autres types d’applications.

Si possible, évitez de charger du contenu à partir d’une origine tierce. Pour atténuer les
risques, vous pouvez délivrer du contenu directement à partir de l’application en
téléchargeant les ressources externes, en vérifiant qu’elles sont sécurisées pour les
utilisateurs et en les plaçant dans le dossier wwwroot de l’application pour qu’elles soient
intégrées au reste de l’application. Lorsque le contenu externe est téléchargé pour
l’inclure dans l’application, nous vous recommandons de l’analyser à la recherche de
virus et de programmes malveillants avant de le placer dans le dossier wwwroot de
l’application.

Si votre application doit référencer du contenu provenant d’une origine externe, nous
vous recommandons d’utiliser des approches de sécurité web courantes pour permettre
à l’application d’empêcher le chargement du contenu si le contenu est compromis :

Délivrez du contenu en toute sécurité avec TLS/HTTPS.


Instituez une stratégie de sécurité du contenu (CSP) .
Effectuez des vérifications d’intégrité des sous-ressources .

Même si toutes les ressources sont intégrées à l’application et ne se chargent pas à


partir d’une origine externe, restez prudent quant aux problèmes dans le code des
ressources qui s’exécutent à l’intérieur du Web View, car les ressources peuvent
présenter des vulnérabilités qui pourraient entraîner des attaques par scripting inter-site
(XSS).

En général, l’infrastructure Blazor protège contre le XSS en traitant du code HTML de


manière sécurisée. Toutefois, certains modèles de programmation permettent aux
composants Razor d’injecter du code HTML brut dans une sortie affichée, comme le
rendu du contenu à partir d’une source non fiable. Par exemple, le rendu du contenu
HTML directement à partir d’une base de données doit être évité. En outre, les
bibliothèques JavaScript utilisées par l’application peuvent manipuler le code HTML de
manière non sécurisée pour afficher par inadvertance ou délibérément une sortie non
sécurisée.

Pour ces raisons, il est préférable d’appliquer les mêmes protections contre le XSS que
celles normalement appliquées aux applications web. Empêchez le chargement de
scripts à partir de sources inconnues et n’implémentez pas de fonctionnalités JavaScript
potentiellement non sécurisées, telles que eval et d’autres primitives JavaScript non
sécurisées. Il est recommandé d’établir un fournisseur de solutions pour réduire ces
risques de sécurité.
Si le code à l’intérieur du Web View est compromis, le code accède à tout le contenu à
l’intérieur du Web View et peut interagir avec l’hôte via le canal d’interopérabilité. Pour
cette raison, tout contenu provenant du Web View (événements, interopérabilité JS) doit
être traité comme non fiable et validé de la même manière que pour d’autres contextes
sensibles, comme dans une application compromise Blazor Server qui peut entraîner des
attaques malveillantes sur le système hôte.

Ne stockez pas d’informations sensibles, telles que des informations d’identification, des
jetons de sécurité ou des données utilisateur sensibles, dans le contexte de Web View,
car ces informations seraient alors accessibles à un attaquant si le Web View est
compromis. Il existe des alternatives plus sûres, telles que la gestion des informations
sensibles directement dans la partie native de l’application.

Contenu externe affiché dans un iframe


Lors de l’utilisation d’un iframe pour afficher du contenu externe dans une page
Blazor Hybrid, nous recommandons aux utilisateurs de tirer parti des fonctionnalités de
bac à sable (sandboxing) pour s’assurer que le contenu est isolé de la page parente
contenant l’application. Dans l’exemple de composant Razor suivant, sandboxl’attribut
est présent pour la balise <iframe> pour appliquer des fonctionnalités de bac à sable à
la page admin.html :

razor

<iframe sandbox src="https://contoso.com/admin.html" />

2 Avertissement

sandboxL’attribut n’est pas pris en charge dans les premières versions du


navigateur. Pour plus d’informations, consultez Puis-je utiliser : sandbox .

Les liens vers des URL externes


Par défaut, les liens vers des URL en dehors de l’application sont ouverts dans une
application externe appropriée, et non chargés dans Web View. Nous ne conseillons pas
de remplacer le comportement par défaut.
Conservez le Web View actuel dans les
applications déployées
Par défaut, le contrôle BlazorWebView utilise le natif Web View propre à la plateforme
actuellement installé. Étant donné que le natif Web View est régulièrement mis à jour
avec la prise en charge des nouvelles API et des correctifs pour les problèmes de
sécurité, il peut être nécessaire de s’assurer qu’une application utilise une version Web
View qui répond aux exigences de l’application.

Utilisez l’une des approches suivantes pour conserver le Web View actuel dans les
applications déployées :

Sur toutes les plateformes : vérifiez la version Web View et invitez l’utilisateur à
prendre toutes les mesures nécessaires pour la mettre à jour.
Uniquement sur Windows : intégrez une version Web View fixe dans l’application,
en l’utilisant à la place du système partagé Web View.

Android
Android Web View est distribué et mis à jour via le Google Play Store . Vérifiez la
version Web View en lisant la chaîne User-Agent . Lisez la propriété de
navigator.userAgent du Web View à l’aide de l’interopérabilité JavaScript et mettez
éventuellement la valeur en cache à l’aide d’un service singleton si la chaîne de l’agent
utilisateur est requise en dehors d’un contexte de composant Razor.

Lors de l’utilisation de l’Émulateur Android :

Utilisez un appareil émulé avec Google Play Services préinstallé. Les appareils
émulés sans Google Play Services préinstallés ne sont pas pris en charge.
Installez Google Chrome à partir du Google Play Store. Si Google Chrome est déjà
installé, mettez à jour Chrome à partir du Google Play Store . Si un appareil
émulé n’a pas la dernière version de Chrome installée, il se peut qu’il n’ait pas la
dernière version d’Android Web View installée.

iOS/Mac Catalyst
iOS et Mac Catalyst utilisent tous deux WKWebView , un contrôle basé sur Safari, qui
est mis à jour par le système d’exploitation. Comme dans le cas Android, déterminez la
version Web View en lisant la chaîne User-Agent de Web View.

Windows (.NET MAUI, WPF, Windows Forms)


Sur Windows, Microsoft Edge WebView2 basé sur Chromium est requis pour exécuter
des applications web Blazor.

Par défaut, la dernière version installée de WebView2 , connue sous le nom de Evergreen
distribution, est utilisée. Si vous souhaitez expédier une version spécifique de WebView2
avec l’application, utilisez Fixed Version distribution.

Pour plus d’informations sur la vérification de la version WebView2 actuellement installée


et des modes de distribution, consultez la WebView2documentation de distribution.

Ressources supplémentaires
Authentification et autorisation avec ASP.NET Core Blazor Hybrid
Authentification et autorisation avec ASP.NET Core Blazor

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Publier des applications ASP.NET Core
Blazor Hybrid
Article • 09/02/2024

Cet article explique comment publier des applications Blazor Hybrid.

Publier pour une infrastructure spécifique


Blazor Hybrid prend en charge .NET MAUI, WPF et Windows Forms. Les étapes de
publication pour les applications utilisant Blazor Hybrid sont presque identiques aux
étapes de publication pour la plateforme cible.

WPF et Windows Forms


Vue d’ensemble de la publication d’applications .NET
.NET MAUI
Windows
Android
iOS
macOS

Blazor-considérations spécifiques
Les applications Blazor Hybrid nécessitent un Web View sur la plateforme hôte. Pour
plus d’informations, consultez Keep the Web View current in deployed Blazor Hybrid
apps.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Résoudre les problèmes liés à ASP.NET
Blazor Hybrid
Article • 09/02/2024

BlazorWebView dispose d’une journalisation intégrée qui peut vous aider à


diagnostiquer les problèmes dans votre application Blazor Hybrid.

Cet article explique les étapes à suivre pour utiliser la journalisation BlazorWebView :

Activez BlazorWebView et les composants associés pour consigner les informations


de diagnostic.
Configurez les fournisseurs de journalisation.
Afficher la sortie de l’enregistreur d’événements.

Activer la journalisation BlazorWebView


Activez la configuration de la journalisation pendant l’inscription au service. Pour activer
la journalisation maximale pour BlazorWebView et les composants associés sous
l’espace de noms Microsoft.AspNetCore.Components.WebView, ajoutez le code suivant
dans le fichier Program :

C#

services.AddLogging(logging =>
{
logging.AddFilter("Microsoft.AspNetCore.Components.WebView",
LogLevel.Trace);
});

Vous pouvez également utiliser le code suivant pour activer la journalisation maximale
pour chaque composant qui utilise Microsoft.Extensions.Logging :

C#

services.AddLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Trace);
});

Configurez les fournisseurs de journalisation


Après avoir configuré des composants pour écrire des informations de journal,
configurez l’emplacement où les enregistreurs d’événements doivent écrire des
informations de journal.

Les fournisseurs de journalisation de débogage écrivent la sortie à l’aide


Debugd’instructions.

Pour configurer le fournisseur de journalisation de débogage, ajoutez une référence au


package NuGet Microsoft.Extensions.Logging.Debug .

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Enregistrez le fournisseur à l’intérieur de l’appel à AddLogging ajouté à l’étape


précédente en appelant la méthode d’extension AddDebug :

C#

services.AddLogging(logging =>
{
logging.AddFilter("Microsoft.AspNetCore.Components.WebView",
LogLevel.Trace);
logging.AddDebug();
});

Afficher la sortie de l’enregistreur


d’événements
Lorsque l’application est exécutée à partir de Visual Studio avec débogage activé, la
sortie de débogage s’affiche dans la fenêtre Sortie de Visual Studio.

Ressources supplémentaires
Journalisation en C# et .NET
Journalisation dans .NET Core et ASP.NET Core
6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Structure de projet ASP.NET Core Blazor
Article • 09/02/2024

Cet article décrit les fichiers et les dossiers qui composent une application Blazor
générée à partir d’un modèle de projet Blazor.

Application webBlazor
BlazorModèle de projet Application web : blazor

Le modèle de projet d’application web Blazor fournit un point de départ unique pour
utiliser des composants Razor pour créer n’importe quel style d’interface utilisateur web,
rendu côté serveur et rendu côté client. Il combine les forces des modèles
d’hébergement Blazor Server et Blazor WebAssembly existants avec le rendu côté
serveur, le rendu en streaming, la navigation améliorée et la gestion des formulaires, et
la possibilité d’ajouter une interactivité à l’aide de Blazor Server ou de Blazor
WebAssembly sur une base par composant.

Si vous sélectionnez le rendu côté client (CSR) et le rendu côté serveur interactif (SSR
interactif) lors de la création de l’application, le modèle de projet utilise le mode de
rendu Automatique interactif. Le mode de rendu automatique utilise initialement le SSR
interactif pendant que l’ensemble d’applications .NET et le runtime sont téléchargés
dans le navigateur. Une fois le runtime WebAssembly .NET activé, le rendu passe à CSR.

Par défaut, le modèle d’application web Blazor active le rendu côté serveur statique et le
rendu côté serveur interactif en utilisant un seul projet. Si vous activez également le
rendu WebAssembly interactif, le projet inclut un projet de client
supplémentaire ( .Client ) pour vos composants basés sur WebAssembly. La sortie
générée du projet client est téléchargée dans le navigateur et exécutée sur le client. Les
composants utilisant les modes d’affichage WebAssembly interactif ou Automatique
interactif doivent se trouver dans le projet .Client .

Pour plus d’informations, consultez Modes de rendu ASP.NET Core Blazor.

Projet serveur :

Components Dossier :

Dossier Layout : contient les composants de disposition et les feuilles de


style suivants :
Composant MainLayout ( MainLayout.razor ) : le composant de disposition
de l’application.
MainLayout.razor.css : feuille de style pour la disposition principale de

l’application.
Composant NavMenu ( NavMenu.razor ) : implémente la navigation dans la
barre latérale. Inclut le NavLinkcomposant (NavLink), qui affiche les liens
de navigation vers d’autres Razorcomposants. Le composant NavLink
indique à l’utilisateur quel composant est actuellement affiché.
NavMenu.razor.css : Feuille de style pour le menu de navigation de

l’application.

Dossier Pages : contient les composants Razor côté serveur routables de


l’application ( .razor ). L’itinéraire de chaque page est spécifié à l’aide de la
directive @page. Le modèle comprend les éléments suivants :
Composant Counter ( Counter.razor ) : implémente la page Compteur.
Composant Error ( Error.razor ) : implémente la page d’erreur.
Composant Home ( Home.razor ) :implémente la page Home.
Composant Weather ( Weather.razor ) : implémente la page de prévision
météorologique.

Composant App ( App.razor ) : composant racine de l’application avec


balisage HTML <head> , Routes composant, et balise Blazor <script> . Le
composant racine est le premier composant que l’application charge.

Composant Routes ( Routes.razor ) : configure le routage à l’aide du


composant Router. Pour les composants interactifs côté client, le composant
Router intercepte la navigation du navigateur et affiche la page qui
correspond à l’adresse demandée.

_Imports.razor : inclut des directives Razor courantes à inclure dans les

composants d’application rendus côté serveur ( .razor ), tels que les


directives @using pour les espaces de noms.

Dossier Properties : contient configuration de l’environnement de


développement dans le fichier launchSettings.json .

7 Notes

Le profil http précède le profil https dans le fichier launchSettings.json .


Lorsqu’une application est exécutée avec l’interface CLI .NET, l’application
s’exécute sur un point de terminaison HTTP, car le premier profil trouvé est
http . L’ordre de profil facilite la transition de l’adoption du protocole

HTTPS pour les utilisateurs Linux et macOS. Si vous préférez démarrer


l’application avec l’interface CLI .NET sans avoir à passer l’option -lp https
ou --launch-profile https à la commande dotnet run , placez simplement
le profil https au-dessus du profil http dans le fichier.

Dossier wwwroot : dossier racine web du projet serveur contenant les ressources
statiques publiques de l’application.

Fichier Program.cs : point d’entrée du projet serveur qui configure l’application


web ASP.NET Core hôte et contient la logique de démarrage de l’application, y
compris les inscriptions de service, la configuration, la journalisation et le
pipeline de traitement des requêtes.
Les services pour les composants Razor sont ajoutés en appelant
AddRazorComponents. AddInteractiveServerComponents ajoute des services
pour prendre en charge le rendu des composants de serveur interactif.
AddInteractiveWebAssemblyComponents ajoute des services pour prendre
en charge le rendu des composants WebAssembly interactifs.
MapRazorComponents découvre les composants disponibles et spécifie le
composant racine de l’application (le premier composant chargé), qui est par
défaut le composant App ( App.razor ). AddInteractiveServerRenderMode
configure le rendu côté serveur interactif (SSR interactif) pour l’application.
AddInteractiveWebAssemblyRenderMode configure le mode de rendu
WebAssembly interactif pour l’application.

Fichiers de paramètres d’application ( appsettings.Development.json ,


appsettings.json ) : fournissez des paramètres de configuration pour le projet

serveur.

Projet client ( .Client ) :

Dossier Pages : contient les composants de Razor côté client routables de


l’application ( .razor ). L’itinéraire de chaque page est spécifié à l’aide de la
directive @page. Le modèle inclut Counter composant ( Counter.razor ) qui
implémente la page Counter.

La structure des dossiers de composants du projet .Client diffère de la


structure des dossiers du projet principal de l’application web Blazor, car le
projet principal est un projet ASP.NET Core standard. Le projet principal doit
prendre en compte d’autres ressources pour les projets ASP.NET Core qui ne
sont pas liés à Blazor.
Le projet .Client est purement un projet Blazor et n’a pas besoin de s’intégrer
autant aux fonctionnalités et spécifications non-Blazor d’ASP.NET Core. Il utilise
donc une structure de dossiers de composants moins complexe. Toutefois, vous
pouvez utiliser la structure de dossiers de composants de votre choix dans le
projet .Client . Vous êtes libre de mettre en miroir la disposition des dossiers
de composants du projet principal dans le projet .Client si vous le souhaitez.
Notez qu’il vous faudra peut-être ajuster les espaces de noms pour des
ressources telles que les fichiers de disposition si vous déplacez des composants
dans des dossiers différents de ceux utilisés par le modèle de projet.

Le dossier racine web du projet côté client contenant les ressources statiques
publiques de l’application, y compris les fichiers de paramètres d’application
( appsettings.Development.json , appsettings.json ) qui fournissent paramètres
de configuration pour le projet côté client.

Fichier Program.cs : point d’entrée du projet côté client qui configure le hôte
WebAssembly et contient la logique de démarrage du projet, y compris les
inscriptions de service, la configuration, la journalisation et le pipeline de
traitement des demandes.

_Imports.razor : inclut des directives de Razor courantes à inclure dans les

composants d’application rendus WebAssembly ( .razor ), tels que les directives


@using pour les espaces de noms.

Des fichiers et dossiers supplémentaires peuvent apparaître dans une application


produite à partir d’un modèle de projet d’application web Blazor lorsque des options
supplémentaires sont configurées. Par exemple, la génération d’une application avec
ASP.NET Core Identity inclut des ressources supplémentaires pour les fonctionnalités
d’authentification et d’autorisation.

Blazor WebAssembly
Modèles de projet Blazor WebAssembly : blazorwasm

Les modèles Blazor WebAssembly créent les fichiers et la structure de répertoires


initiaux pour une application Blazor WebAssembly autonome :

Si le modèle blazorwasm est utilisé, l’application est remplie avec les éléments
suivants :
Code de démonstration d’un composant Weather qui charge des données à
partir d’une ressource statique ( weather.json ) et interaction de l’utilisateur avec
un composant Counter .
Kit de ressources frontales Bootstrap .
Le modèle blazorwasm peut également être généré sans exemples de pages ni
style.

Structure du projet :

Dossier Layout : contient les composants de disposition et les feuilles de style


suivants :
Composant MainLayout ( MainLayout.razor ) : le composant de disposition de
l’application.
MainLayout.razor.css : feuille de style pour la disposition principale de

l’application.
Composant NavMenu ( NavMenu.razor ) : implémente la navigation dans la barre
latérale. Inclut le NavLinkcomposant (NavLink), qui affiche les liens de
navigation vers d’autres Razorcomposants. Le composant NavLink indique
automatiquement un état sélectionné lorsque son composant est chargé, ce qui
permet à l’utilisateur de comprendre quel composant est actuellement affiché.
NavMenu.razor.css : Feuille de style pour le menu de navigation de l’application.

Dossier Pages : contient les composants Blazorroutables de l’applicationRazor


( .razor ). L’itinéraire de chaque page est spécifié à l’aide de la directive @page. Le
modèle inclut les composants suivants :
Composant Counter ( Counter.razor ) : implémente la page Compteur.
Composant Index ( Index.razor ) :implémente la page Home.
Composant Weather ( Weather.razor ) : implémente la page Météo.

_Imports.razor : inclut des directives courantes Razor à inclure dans les

composants de l’application ( .razor ), telles que les directives @using pour les
espaces de noms.

App.razor : le composant racine de l’application qui configure le routage côté

client à l’aide du composant Router. Le composant Router intercepte la navigation


du navigateur et affiche la page qui correspond à l’adresse demandée.

Dossier Properties : contient configuration de l’environnement de développement


dans le fichier launchSettings.json .

7 Notes
Le profil http précède le profil https dans le fichier launchSettings.json .
Lorsqu’une application est exécutée avec l’interface CLI .NET, l’application
s’exécute sur un point de terminaison HTTP, car le premier profil trouvé est
http . L’ordre de profil facilite la transition de l’adoption du protocole HTTPS

pour les utilisateurs Linux et macOS. Si vous préférez démarrer l’application


avec l’interface CLI .NET sans avoir à passer l’option -lp https ou --launch-
profile https à la commande dotnet run , placez simplement le profil https

au-dessus du profil http dans le fichier.

Dossier wwwroot : le dossier Racine web de l’application contenant les ressources


statiques publiques de l’application, y compris appsettings.json et les fichiers de
paramètres d’application d’environnement pour les paramètres de configuration et
les exemples de données météorologiques ( sample-data/weather.json ). La page
web index.html est la page racine de l’application implémentée en tant que page
HTML :
Lorsqu’une page de l’application est initialement requise, cette page est affichée
et renvoyée dans la réponse.
La page spécifie l’emplacement où le composant App racine est affiché. Le
composant est affiché à l’emplacement de l’élément DOM div avec un id de
app ( <div id="app">Loading...</div> ).

Program.cs : le point d’entrée de l’application qui configure l’hôte WebAssembly :

Le composant App est le composant racine de l’application. Le composant App


est spécifié en tant qu’élément DOM div avec un id de app ( <div
id="app">Loading...</div> dans wwwroot/index.html ) de la collection de

composants racine ( builder.RootComponents.Add<App>("#app") ).


Les Services sont ajoutés et configurés (par exemple,
builder.Services.AddSingleton<IMyDependency, MyDependency>() ).

Des fichiers et dossiers supplémentaires peuvent apparaître dans une application


produite à partir d’un modèle de projet Blazor WebAssembly lorsque des options
supplémentaires sont configurées. Par exemple, la génération d’une application avec
ASP.NET Core Identity inclut des ressources supplémentaires pour les fonctionnalités
d’authentification et d’autorisation.

Emplacement du script Blazor


Le script Blazor est délivré depuis une ressource incorporée dans le framework partagé
ASP.NET Core.
Dans une application web Blazor, le script Blazor se trouve dans le fichier
Components/App.razor :

HTML

<script src="_framework/blazor.web.js"></script>

Dans une application Blazor Server, le script Blazor se trouve dans le fichier
Pages/_Host.cshtml :

HTML

<script src="_framework/blazor.server.js"></script>

Dans une application Blazor WebAssembly, le contenu du script Blazor se trouve dans le
fichier wwwroot/index.html :

HTML

<script src="_framework/blazor.webassembly.js"></script>

Emplacement du contenu <head> et <body>


Dans une application web Blazor, le contenu <head> et <body> se trouve dans le fichier
Components/App.razor .

Dans une application Blazor WebAssembly, le contenu <head> et <body> se trouve dans
le fichier wwwroot/index.html .

Ressources supplémentaires
Outils pour ASP.NET Core Blazor
Modèles d’hébergement ASP.NET Core Blazor
Vue d’ensemble des API minimales
Blazor exemples de référentiel GitHub (dotnet/blazor-samples)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Notions de base d’ASP.NET Core Blazor
Article • 09/02/2024

Les articles Notions de base fournissent des conseils sur les concepts fondamentaux de
Blazor. Certains concepts sont liés à la compréhension de base des composants Razor,
qui sont décrits plus loin dans la section suivante de cet article et abordés en détail dans
les articles Composants.

Concepts de rendu du client et du serveur


Tout au long de la documentation Blazor, l’activité qui se déroule sur le système de
l’utilisateur est dite sur le client oucôté client. L’activité qui se produit sur un serveur est
dite sur le serveur ou côté serveur.

Le terme rendu signifie produire le balisage HTML affiché par les navigateurs.

Le rendu côté client signifie que le balisage HTML final est généré par le runtime
Blazor WebAssembly sur le client. Aucune interface utilisateur générée par le client
de l’application n’est envoyée à partir d’un serveur au client pour ce type de rendu.
L’interactivité utilisateur avec la page est supposée. Il n’existe aucun concept de
rendu côté client statique. CSR est supposé être interactif, de sorte que le « rendu
interactif côté client » et « CSR interactif » ne sont pas utilisés par le secteur ou
dans la documentation Blazor.

Le rendu côté serveur signifie que le balisage HTML final est généré par le runtime
ASP.NET Core sur le serveur. Le code HTML est envoyé au client via un réseau pour
l’affichage par le navigateur du client. Aucune interface utilisateur générée par le
serveur de l’application n’est créée par le client pour ce type de rendu. Les SSR
peuvent être de deux types :
SSR statique : le serveur produit du code HTML statique qui ne fournit pas
d’interactivité utilisateur ou de maintenance de l’état du composant Razor.
SSR interactif : les événements Blazor autorisent l’interactivité des utilisateurs et
l’état des composants Razor est géré par l’infrastructure Blazor.

Le prérendu est le processus de rendu initial du contenu d’une page sur le serveur
sans activation des gestionnaires d’événements pour les contrôles rendus. Le
serveur génère l’interface utilisateur HTML de la page dès que possible en réponse
à la demande initiale, ce qui rend l’application plus réactive pour les utilisateurs. Le
prérendu peut aussi améliorer l’optimisation du référencement d’un site auprès
d’un moteur de recherche (SEO) en rendant le contenu de la réponse HTTP
initiale qui est utilisée par les moteurs de recherche pour calculer le rang de la
page. La préversion est toujours suivie du rendu final, soit sur le serveur, soit sur le
client.

Concepts de rendu statiques et interactifs


Les composants Razor sont affichés de manière statique ou interactive.

Le rendu statique ou statique est un scénario côté serveur, ce qui signifie que le
composant est rendu sans la capacité d’interaction entre l’utilisateur et le code .NET/C#.
Les événements DOM JavaScript et HTML ne sont pas affectés, mais aucun événement
utilisateur sur le client ne peut être traité avec .NET s’exécutant sur le serveur.

Le rendu interactif ou interactif signifie que le composant a la capacité de traiter les


événements .NET via du code C#. Les événements .NET sont traités sur le serveur par le
runtime ASP.NET Core ou dans le navigateur sur le client par le runtime WebAssembly
Blazor.

Vous trouverez plus d’informations sur ces concepts et sur la façon de contrôler le rendu
statique et interactif dans l’article Modes de rendu ASP.NET Core Blazor plus loin dans la
documentation Blazor.

Composants Razor
Les applications Blazor sont basées sur les composants Razor, souvent appelés simples
composants. Un composant est un élément d’interface utilisateur, comme une page, une
boîte de dialogue ou un formulaire de saisie de données. Les composants sont des
classes .NET C# intégrées dans des assemblys .NET.

Razor fait référence à la façon dont les composants sont généralement écrits sous la
forme d’une page de balisage Razor pour la logique et la composition de l’interface
utilisateur côté client. Razor est une syntaxe qui combine des balises HTML à du code
C# destiné à améliorer la productivité des développeurs. Les fichiers Razor utilisent
l’extension de fichier .razor .

Bien que certains développeurs Blazor et ressources en ligne utilisent le terme


« composants Blazor », la documentation évite ce terme et utilise universellement
« composants Razor » ou « composants ».

La documentation Blazor adopte plusieurs conventions pour afficher les composants et


en discuter :
Le code de projet, les noms et chemins de fichiers, les noms de modèles de projet
et d’autres termes spécialisés sont en anglais (États-Unis) et sont généralement
délimités par du code.
Les composants sont généralement référencés par leur nom de classe C# (casse
Pascal) précédé du mot « composant ». Par exemple, un composant de
chargement de fichiers standard est référencé en tant que « composant
FileUpload ».

En règle générale, le nom de classe C# d’un composant est le même que son nom
de fichier.
En règle générale, un composant routable définit son URL relative comme le nom
de classe du composant dans la casse kebab. Par exemple, un composant
FileUpload inclut une configuration de routage pour atteindre le composant

rendu à l’URL relative /file-upload . Le routage et la navigation sont couverts dans


Routage et navigation ASP.NET Core Blazor.
Quand plusieurs versions d’un composant sont utilisées, elles sont numérotées
séquentiellement. Par exemple, le composant FileUpload3 est accessible à /file-
upload-3 .

Les directives Razor dans la partie supérieure d’une définition de composant


( .razor file ) sont placées dans l’ordre suivant : @page , @rendermode (.NET 8 ou
version ultérieure), instructions @using , autres directives par ordre alphabétique.
Vous trouverez des informations supplémentaires sur l’ordre des directives Razor
dans la section Syntaxe Razor des composants ASP.NET Core Razor.
Les modificateurs d’accès sont utilisés dans les exemples d’articles. Par exemple, les
champs sont private par défaut, mais sont explicitement présents dans le code du
composant. Par exemple, private est mentionné pour la déclaration d’un champ
nommé maxAllowedFiles en tant que private int maxAllowedFiles = 3; .
Les valeurs de paramètres de composant commencent par un symbole @ réservé à
Razor, mais ce n’est pas obligatoire. Les littéraux (par exemple, valeurs
booléennes), mots clés (par exemple, this ) et valeurs null utilisés comme valeurs
de paramètres de composant n’ont pas le préfixe @ , mais il s’agit là aussi d’une
simple convention de documentation. Dans votre code, vous pouvez faire précéder
les littéraux du préfixe @ si vous le souhaitez.
En règle générale, les exemples respectent les conventions de codage ASP.NET
Core/C# et les directives d’ingénierie. Pour plus d’informations, consultez les
ressources suivantes :
Directives d’ingénierie (dépôt GitHub dotnet/aspnetcore)
Conventions de codage C# (Guide C#)

Voici un exemple de composant de compteur et une partie d’une application créée à


partir d’un modèle de projet Blazor. La couverture détaillée des composants se trouve
dans les articles Composants plus loin dans la documentation. L’exemple suivant illustre
les concepts de composant rencontrés dans les articles Notions de base avant
d’atteindre les articles Composants, plus loin dans la documentation.

Counter.razor :

Le composant suppose que le mode d’affichage interactif est hérité d’un composant
parent ou appliqué globalement à l’application.

razor

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Le composant Counter précédent :

Définit sa route avec la directive @page dans la première ligne.


Définit son titre de page et son titre.
Affiche le nombre actuel avec @currentCount . currentCount est une variable
entière définie dans le code C# du bloc @code .
Affiche un bouton pour déclencher la méthode IncrementCount , qui figure
également dans le bloc @code et augmente la valeur de la variable currentCount .

Modes de rendu
Les articles du nœud Principes de base font référence au concept de modes de rendu. Ce
sujet est abordé en détail dans l’article Modes de rendu ASP.NET Core Blazor dans le
nœud Composants, qui apparaît après le nœud d’articles Principes de base.
Pour les premières références de ce nœud d’articles pour afficher les concepts du mode,
notez simplement ce qui suit à ce stade :

Chaque composant d’une application web Blazor adopte un mode de rendu pour
déterminer le modèle d’hébergement qu’il utilise, où il est rendu, et s’il est rendu
statiquement sur le serveur, rendu avec pour l’interactivité utilisateur sur le serveur ou
rendu pour l’interactivité utilisateur sur le client (généralement avec prérendu sur le
serveur).

Les applications Blazor Server et Blazor WebAssembly pour les versions ASP.NET Core
antérieures à .NET 8 restent fixées sur les concepts du modèle d’hébergement, et non sur
les modes de rendu. Les modes de rendu sont appliqués conceptuellement aux
applications web Apps Blazor dans .NET 8 ou version ultérieure.

Le tableau suivant présente les modes de rendu disponibles pour le rendu des
composants Razor dans l’application web Blazor. Les modes de rendu sont appliqués
aux composants avec la directive @rendermode sur l'instance du composant ou sur la
définition du composant. Il est également possible de définir un mode de rendu pour
l’ensemble de l’application.

ノ Agrandir le tableau

Nom Description Emplacement Interactive


du rendu

Serveur statique Rendu statique côté serveur (SSR statique) Serveur ❌

Serveur Rendu interactif côté serveur (SSR interactif) à Serveur ✔️


interactif l’aide de Blazor Server

WebAssembly Rendu côté client (CSR) à l’aide de Blazor Client ✔️


interactif WebAssembly†

Voiture SSR interactif à l’aide de Blazor Server dans Serveur, puis ✔️


interactive un premier temps, puis de CSR lors des visites client
ultérieures après le téléchargement de l’offre
groupée Blazor

† Le rendu côté client (CSR) est supposé être interactif. Le « rendu interactif côté client »
et « CSR interactif » ne sont pas utilisés par le secteur ou dans la documentation Blazor.

Les informations précédentes sur les modes de rendu sont tout ce que vous devez
savoir pour comprendre les articles du nœud Principes de base. Si vous débutez avec
Blazor et lisez les articles Blazor dans l’ordre dans la table des matières, vous pouvez
retarder l’utilisation d’informations détaillées sur les modes de rendu jusqu’à atteindre
l’article des modes de rendu ASP.NET Core Blazor dans le nœud Composants.

DOM (Document Object Model)


En référence au Document Object Model, utilisez l’abréviation DOM.

Pour plus d'informations, reportez-vous aux ressources suivantes :

Présentation du DOM (documentation MDN)


Spécification du Document Object Model de niveau 1 (W3C)

Sous-ensemble d’API .NET pour les applications


Blazor WebAssembly
Il n’existe pas de liste exhaustive des API .NET spécifiques qui sont prises en charge sur
le navigateur pour Blazor WebAssembly. Vous pouvez cependant rechercher
manuellement une liste d’API .NET annotées avec [UnsupportedOSPlatform("browser")]
pour découvrir les API .NET qui ne sont pas prises en charge dans WebAssembly.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Pour plus d’informations, consultez les ressources suivantes :

Bibliothèques de classes : Analyseur de compatibilité du navigateur côté client


Annotation des API comme étant non prises en charge sur des plateformes
spécifiques (dotnet/designsdépôt GitHub )

Exemples d’applications
Des exemples d’applications de documentation sont disponibles pour inspection et
téléchargement :
Blazor exemples de dépôt GitHub (dotnet/blazor-samples)

Exemples d’applications dans le référentiel :

Application webBlazor
Blazor WebAssembly
Application web Blazor avec EF Core (ASP.NET Core Blazor avec Entity Framework
Core (EF Core))
Application web Blazor avec SignalR (Utiliser ASP.NET Core SignalR avec Blazor)
Application web Blazor avec OIDC et Aspire
Journalisation prenant en charge les étendues Blazor WebAssembly (journalisation
ASP.NET Core Blazor)
Blazor WebAssembly avec ASP.NET Core Identity (Secure ASP.NET Core Blazor
WebAssembly avec ASP.NET Core Identity)

Pour plus d’informations, consultez les Blazor exemples du référentiel GitHub fichier
README.md .

L’application de test de base du référentiel ASP.NET Core est également un ensemble


d’exemples utiles pour différents scénarios Blazor :

BasicTestApp dans la source de référence ASP.NET Core (dotnet/aspnetcore)

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Multiples d’octets
Les tailles d’octets .NET utilisent des préfixes de métrique pour les multiples non
décimaux d’octets en fonction de puissances de 1024.

ノ Agrandir le tableau
Nom (abréviation) Size Exemple

Kilo-octet (Ko) 1 024 octets 1 Ko = 1 024 octets

Mégaoctet (Mo) 1 0242 octets 1 Mo = 1 048 576 octets

Gigaoctet (Go) 1 0243 octets 1 Go = 1 073 741 824 octets

Demandes de support
Seuls les problèmes liés à la documentation sont appropriés pour le dépôt
dotnet/AspNetCore.Docs . Pour la prise en charge des produits, n’ouvrez pas de

problème de documentation. Demandez de l’aide par un ou plusieurs des canaux de


support suivants :

Dépassement de capacité de la pile (étiqueté : blazor)


Équipe générale ASP.NET Core Slack
Gitter Blazor

Pour un bogue potentiel dans l’infrastructure ou les commentaires sur les produits,
ouvrez un problème pour l’unité de produit ASP.NET Core dans Problèmes
dotnet/aspnetcore . Les rapports de bogues nécessitent généralement les éléments
suivants :

Une explication claire du problème : suivez les instructions du modèle de


problème GitHub fourni par l’unité de produit lors de l’ouverture du problème.
Un projet de reproduction minimal : placez un projet sur GitHub que les
ingénieurs de l’unité de produit pourront télécharger et exécuter. Placez un lien
croisé vers le projet dans le commentaire d’ouverture du problème.

Pour un problème potentiel avec un article Blazor, ouvrez un problème de


documentation. Pour ouvrir un problème de documentation, utilisez le bouton et le
formulaire de commentaires Cette page en bas de l’article et laissez les métadonnées en
place lors de la création du commentaire d’ouverture. Les métadonnées fournissent des
données de suivi et effectuent automatiquement un ping de l’auteur de l’article. Si le
sujet a été discuté avec l’unité de produit, placez un lien croisé vers le problème
d’ingénierie dans le commentaire d’ouverture du problème de documentation.

Pour des problèmes ou des commentaires sur Visual Studio, utilisez le Signaler un
problème ou suggérer des mouvements de fonctionnalité à partir de Visual Studio, qui
ouvrent des problèmes internes pour Visual Studio. Pour plus d'informations, consultez
Commentaires Visual Studio .
Pour les problèmes liés à Visual Studio Code, demandez de l’aide sur les forums de
support de la communauté. Pour obtenir des rapports de bogues et des commentaires
sur les produits, ouvrez un problème dans le dépôt GitHub microsoft/vscode .

Les problèmes GitHub pour la documentation Blazor sont automatiquement marqués


pour le triage sur le projet Blazor.Docs (dépôt GitHub dotnet/AspNetCore.Docs) .
Patientez un peu avant d’obtenir une réponse, en particulier pendant les week-ends et
les jours fériés. En règle générale, les auteurs de documentation répondent dans les 24 h
les jours de semaine.

Liens de la communauté vers les ressources


Blazor
Pour voir l’ensemble des liens vers les ressources Blazor gérées par la communauté,
visitez Awesome Blazor .

7 Notes

Microsoft ne possède pas, ne tient pas à jour et ne prend pas en charge Awesome
Blazor ni la plupart des produits et services de la communauté décrits et accessibles
ici.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Routage et navigation dans ASP.NET Core Blazor
Article • 09/02/2024

Cet article explique comment gérer le routage de requêtes des applications Blazor et comment utiliser le
composant NavLink pour créer des liens de navigation.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour distinguer les
emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de mode d’affichage interactif
avec une directive @rendermode dans le fichier de définition du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué au composant. Ce mode
peut être spécifié dans le fichier de définition du composant ou hérité d’un composant parent. Pour plus
d’informations, consultez Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants fonctionnent tels qu’ils sont présentés
et ne nécessitent pas de mode d’affichage, car ils s’exécutent toujours de manière interactive sur
WebAssembly dans une application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le code du composant
envoyé au client peut être décompilé et inspecté. N’insérez pas de code privé, de secrets d’application ou d’autres
informations personnelles dans les composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers, consultez la structure de projet
ASP.NET Core Blazor, qui décrit également l’emplacement du script de démarrage Blazor et l’emplacement des
contenus <head> et <body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples d’applications
BlazorSample_{PROJECT TYPE} à partir du référentiel d’exemples GitHub Blazor qui correspond à la version de
.NET que vous ciblez. Pour le moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du premier trimestre 2024.

) Important

Les exemples de code tout au long de cet article illustrent l’appel de méthodes sur Navigation , qui est un
NavigationManager injecté dans les classes et les composants.

Routage statique contre routage interactif


Cette section s’applique aux applications web Blazor.

Si le préréglage n’est pas désactivé, le Blazor routeur ( Router composant, <Router> in Routes.razor ) effectue un
routage statique vers des composants pendant le rendu statique côté serveur (SSR statique). Ce type de routage
est appelé routage statique.
Lorsqu’un mode de rendu interactif est attribué au Routes composant, le Blazor routeur devient interactif après le
SSR statique avec routage statique sur le serveur. Ce type de routage est appelé routage interactif.

Les routeurs statiques utilisent le routage des points de terminaison et le chemin de requête HTTP pour
déterminer le composant à afficher. Lorsque le routeur devient interactif, il utilise l’URL du document (l’URL dans la
barre d’adresses du navigateur) pour déterminer le composant à afficher. Cela signifie que le routeur interactif
peut changer dynamiquement le composant rendu si l’URL du document change dynamiquement pour une autre
URL interne valide, et ce sans effectuer de requête HTTP pour récupérer le nouveau contenu de la page.

Le routage interactif empêche également le prérendu, car le nouveau contenu de la page n’est pas demandé
auprès du serveur avec une demande de page normale. Pour plus d’informations, consultez Prévisualiser les
composants ASP.NET Core Razor.

Modèles de route
Le composant Router active le routage vers les composants Razor et se trouve dans le composant Routes
( Components/Routes.razor ) de l’application.

Quand un composant Razor ( .razor ) avec une directive @page est compilé, la classe de composant générée
reçoit un RouteAttribute spécifiant le modèle de route du composant.

Au démarrage de l’application, l’assembly spécifié en tant que AppAssembly du routeur est analysé afin de
permettre la collecte des informations de route pour les composants de l’application ayant un RouteAttribute.

Au moment de l’exécution, le composant RouteView :

Reçoit le RouteData de Router avec tous les paramètres de route.


Affiche le composant spécifié avec sa disposition ainsi que toutes les autres dispositions imbriquées.

Spécifiez éventuellement un paramètre DefaultLayout avec une classe de disposition pour les composants qui ne
spécifient pas de disposition avec la directive @layout. Les modèles de projet Blazor du framework spécifient le
composant MainLayout ( MainLayout.razor ) en tant que disposition par défaut de l’application. Pour plus
d’informations sur les dispositions, consultez Dispositions ASP.NET Core Blazor.

Les composants prennent en charge plusieurs modèles de route à l’aide de plusieurs directives @page. L’exemple
de composant suivant se charge quand /blazor-route et /different-blazor-route sont demandés.

BlazorRoute.razor :

razor

@page "/blazor-route"
@page "/different-blazor-route"

<PageTitle>Routing</PageTitle>

<h1>Routing Example</h1>

<p>
This page is reached at either <code>/blazor-route</code> or
<code>/different-blazor-route</code>.
</p>

) Important
Pour que les URL soient résolues correctement, l’application doit inclure une balise <base> (emplacement du
contenu <head>) avec le chemin de base de l’application spécifié dans l’attribut href . Pour plus
d’informations, consultez Héberger et déployer ASP.NET Core Blazor.

Au lieu de spécifier le modèle de route sous forme de littéral de chaîne avec la directive @page , vous pouvez
spécifier des modèles de route basés sur des constantes avec la directive @attribute.

Dans l’exemple suivant, la directive @page d’un composant est remplacée par la directive @attribute et le modèle
de route basé sur des constantes dans Constants.CounterRoute , qui a la valeur « /counter » ailleurs dans
l’application :

diff

- @page "/counter"
+ @attribute [Route(Constants.CounterRoute)]

Définir le focus sur un élément dans la navigation


Le composant FocusOnNavigate définit le focus de l’IU sur un élément en fonction d’un sélecteur CSS, après avoir
navigué d’une page à une autre.

razor

<FocusOnNavigate RouteData="routeData" Selector="h1" />

Quand le composant Router accède à une nouvelle page, le composant FocusOnNavigate définit le focus sur l’en-
tête de premier niveau de la page ( <h1> ). Il s’agit d’une stratégie courante permettant de garantir l’annonce de la
navigation d’une page à une autre en cas d’utilisation d’ un lecteur d’écran.

Fournir du contenu personnalisé quand le contenu est


introuvable
Cette section s’applique uniquement aux applications Blazor WebAssembly. Les applications web Blazor n’utilisent
pas le paramètre NotFound ( <NotFound>...</NotFound> ), mais le paramètre est pris en charge pour la compatibilité
descendante afin d’éviter un changement cassant dans l’infrastructure. Les applications web Blazor traitent
généralement les demandes d’URL incorrectes en affichant l’interface utilisateur intégrée 404 du navigateur ou en
retournant une page 404 personnalisée à partir du serveur ASP.NET Core via l’intergiciel ASP.NET Core (par
exemple UseStatusCodePagesWithRedirects / Documentation de l’API).

Le composant Router permet à l’application de spécifier du contenu personnalisé si le contenu relatif à la route
demandée est introuvable.

Définissez du contenu personnalisé pour le paramètre NotFound du composant Router :

razor

<Router ...>
...
<NotFound>
...
</NotFound>
</Router>
Les éléments arbitraires sont pris en charge en tant que contenu du paramètre NotFound, comme d’autres
composants interactifs. Pour appliquer une disposition par défaut au contenu NotFound, consultez Dispositions
ASP.NET Core Blazor.

Effectuer un routage vers les composants à partir de plusieurs


assemblys
Cette section s’applique aux applications web Blazor.

Utilisez le Router paramètre du composant et le générateur AdditionalAssemblies de conventions de point de


terminaison AddAdditionalAssemblies pour découvrir les composants routables dans des assemblys
supplémentaires. Les sous-sections suivantes expliquent quand et comment utiliser chaque API.

Routage statique
Pour découvrir des composants routables à partir d’assemblages supplémentaires pour le rendu statique côté
serveur (SSR statique), même si le routeur devient ensuite interactif pour le rendu interactif, les assemblages
doivent être divulgués à l’infrastructure Blazor. Appelez la AddAdditionalAssemblies méthode avec les assemblys
supplémentaires chaînés MapRazorComponents dans le fichier du projet de Program serveur.

L’exemple suivant inclut les composants routables dans l’assembly BlazorSample.Client du projet à l’aide du
fichier du _Imports.razor projet :

C#

app.MapRazorComponents<App>()
.AddAdditionalAssemblies(typeof(BlazorSample.Client._Imports).Assembly);

7 Notes

Les instructions précédentes s’appliquent également dans les scénarios de bibliothèque de classes de
composants. Des conseils importants supplémentaires pour les bibliothèques de classes et le SSR statique se
trouvent dans ASP.NET Bibliothèques de classes principales Razor (RCL) avec rendu statique côté serveur
(SSR statique).

Routage interactif
Un mode de rendu interactif peut être attribué au composant Routes ( Routes.razor ) pour que le routeur Blazor
devienne interactif après le SSR statique et le routage statique sur le serveur. Par exemple, <Routes
@rendermode="InteractiveServer" /> attribue un rendu interactif côté serveur (SSR interactif) au Routes

composant. Le Router composant hérite du rendu interactif côté serveur (SSR interactif) du Routes composant. Le
routeur devient interactif après le routage statique sur le serveur.

La navigation interne pour le routage interactif n’implique pas de demander le nouveau contenu de la page auprès
du serveur. Par conséquent, un prérendu n’est pas effectué pour les demandes de pages internes. Pour plus
d’informations, consultez Prévisualiser les composants ASP.NET Core Razor.

Si le Routes composant est défini dans le projet serveur, le AdditionalAssemblies paramètre du Router composant
doit inclure l’assembly .Client du projet. Cela permet au routeur de fonctionner correctement lorsqu’il est rendu
de manière interactive.
Dans l’exemple suivant, le Routes composant se trouve dans le projet serveur et le _Imports.razor fichier du
BlazorSample.Client projet indique l’assembly pour rechercher des composants routables :

razor

<Router
AppAssembly="..."
AdditionalAssemblies="new[] { typeof(BlazorSample.Client._Imports).Assembly }">
...
</Router>

Les assemblys supplémentaires sont analysés en plus de l’assembly spécifié dans le cadre de AppAssembly.

7 Notes

Les instructions précédentes s’appliquent également dans les scénarios de bibliothèque de classes de
composants.

Les composants routables existent également uniquement dans le projet avec le .Client rendu global Interactive
WebAssembly ou Auto appliqué, et le Routes composant est défini dans le .Client projet, et non dans le projet
serveur. Dans ce cas, il n’existe pas d’assemblys externes avec des composants routables. Il n’est donc pas
nécessaire de spécifier une valeur pour AdditionalAssemblies.

Paramètres de routage
Le routeur utilise des paramètres de route pour remplir les paramètres de composant correspondants avec le
même nom. Les noms de paramètres de route ne respectent pas la casse. Dans l’exemple suivant, le paramètre
text affecte la valeur du segment de route à la propriété Text du composant. Lorsqu’une demande est faite pour

/route-parameter-1/amazing , le contenu est rendu sous forme de Blazor is amazing! .

RouteParameter1.razor :

razor

@page "/route-parameter-1/{text}"

<PageTitle>Route Parameter 1</PageTitle>

<h1>Route Parameter Example 1</h1>

<p>Blazor is @Text!</p>

@code {
[Parameter]
public string? Text { get; set; }
}

Les paramètres facultatifs sont pris en charge. Dans l’exemple suivant, le paramètre facultatif text affecte la valeur
du segment de routage à la propriété Text du composant. Si le segment n’est pas présent, la valeur de Text est
fantastic .

RouteParameter2.razor :

razor
@page "/route-parameter-2/{text?}"

<PageTitle>Route Parameter 2</PageTitle>

<h1>Route Parameter Example 2</h1>

<p>Blazor is @Text!</p>

@code {
[Parameter]
public string? Text { get; set; }

protected override void OnInitialized()


{
Text = Text ?? "fantastic";
}
}

Utilisez OnParametersSet à la place de OnInitialized{Async} pour permettre à l’application d’accéder au même


composant avec une autre valeur de paramètre facultatif. D’après l’exemple précédent, utilisez OnParametersSet
quand l’utilisateur doit pouvoir passer de /route-parameter-2 à /route-parameter-2/amazing , ou de /route-
parameter-2/amazing à /route-parameter-2 :

C#

protected override void OnParametersSet()


{
Text = Text ?? "fantastic";
}

Contraintes d'itinéraire
Une contrainte de route applique la correspondance de type d’un segment de route par rapport à un composant.

Dans l’exemple suivant, la route vers le composant User correspond uniquement si :

Un segment de route Id est présent dans l’URL de requête.


Le segment Id est un type entier ( int ).

User.razor :

razor

@page "/user/{Id:int}"

<PageTitle>User</PageTitle>

<h1>User Example</h1>

<p>User Id: @Id</p>

@code {
[Parameter]
public int Id { get; set; }
}

Les contraintes de route affichées dans le tableau suivant sont disponibles. Si vous souhaitez connaître les
contraintes de route qui correspondent à la culture invariante, consultez l’avertissement situé sous le tableau pour
plus d’informations.
ノ Agrandir le tableau

Contrainte Exemple Exemples de correspondances Invariant


culture
correspondance

bool {active:bool} true , FALSE Non

datetime {dob:datetime} 2016-12-31 , 2016-12-31 7:32pm Oui

decimal {price:decimal} 49.99 , -1,000.01 Oui

double {weight:double} 1.234 , -1,001.01e8 Oui

float {weight:float} 1.234 , -1,001.01e8 Oui

guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 , {CD2C1638-1638-72D5-1638- Non


DEADBEEF1638}

int {id:int} 123456789 , -123456789 Oui

long {ticks:long} 123456789 , -123456789 Oui

2 Avertissement

Les contraintes de routage qui vérifient que l’URL peut être convertie en type CLR (comme int ou DateTime)
utilisent toujours la culture invariant. ces contraintes partent du principe que l’URL n’est pas localisable.

Les contraintes de route fonctionnent également avec les paramètres facultatifs. Dans l’exemple suivant, Id est
obligatoire, mais Option est un paramètre de route booléen facultatif.

User.razor :

razor

@page "/user/{id:int}/{option:bool?}"

<p>
Id: @Id
</p>

<p>
Option: @Option
</p>

@code {
[Parameter]
public int Id { get; set; }

[Parameter]
public bool Option { get; set; }
}

Paramètres de route fourre-tout


Les paramètres de route fourre-tout, qui capturent les chemins au-delà de plusieurs limites de dossiers, sont pris
en charge dans les composants.

Les paramètres de route fourre-tout sont :


Nommés pour correspondre au nom du segment de route. Le nommage ne respecte pas la casse.
Type string . Le framework ne fournit pas de cast automatique.
À la fin de l’URL.

CatchAll.razor :

razor

@page "/catch-all/{*pageRoute}"

<PageTitle>Catch All</PageTitle>

<h1>Catch All Parameters Example</h1>

<p>Add some URI segments to the route and request the page again.</p>

<p>
PageRoute: @PageRoute
</p>

@code {
[Parameter]
public string? PageRoute { get; set; }
}

Pour l’URL /catch-all/this/is/a/test avec le modèle de route /catch-all/{*pageRoute} , la valeur de PageRoute


est this/is/a/test .

Les barres obliques et les segments du chemin capturé sont décodés. Pour le modèle de route /catch-
all/{*pageRoute} , l’URL /catch-all/this/is/a%2Ftest%2A donne this/is/a/test* .

Outils d’assistance pour les URI et l’état de navigation


Utilisez NavigationManager pour gérer les URI et la navigation dans du code C#. NavigationManager fournit
l’événement et les méthodes indiqués dans le tableau suivant.

ノ Agrandir le tableau

Membre Description

Uri Obtient l’URI absolu actuel.

BaseUri Obtient l’URI de base (avec une barre oblique de fin) qui peut être ajouté au début des
chemins d’URI relatifs pour produire un URI absolu. En règle générale, BaseUri correspond
à l’attribut href de l’élément <base> du document (emplacement du contenu de <head>).

NavigateTo Permet d’accéder à l’URI spécifié. Si forceLoad a la valeur false :

Et la navigation améliorée est disponible à l’URL actuelle, la navigation Blazor


améliorée est activée.
Sinon, Blazor effectue un rechargement de page complète pour l’URL demandée.

Si forceLoad a la valeur true :

Le routage côté client est contourné.


Le navigateur est forcé de charger la nouvelle page à partir du serveur, que l’URI soit
normalement géré ou non par le routeur interactif côté client.

Pour plus d’informations, consultez la section Navigation améliorée et gestion des


formulaires.
Membre Description

Si replace a la valeur true , l’URI actuel dans l’historique du navigateur est remplacé, et
aucun nouvel URI n’est envoyé (push) vers la pile de l’historique.

LocationChanged Événement qui se déclenche en cas de changement de l’emplacement de navigation. Pour


plus d’informations, consultez la section Changements d’emplacement.

ToAbsoluteUri Convertit un URI relatif en URI absolu.

ToBaseRelativePath En fonction de l’URI de base de l’application, convertit un URI absolu en URI par rapport au
préfixe de l’URI de base. Pour obtenir un exemple, consultez la section Produire un URI par
rapport au préfixe de l’URI de base.

RegisterLocationChangingHandler Inscrit un gestionnaire pour traiter les événements de navigation entrants. L’appel de
NavigateTo entraîne toujours l’appel du gestionnaire.

GetUriWithQueryParameter Retourne un URI construit en mettant à jour NavigationManager.Uri via l’ajout, la mise à
jour ou la suppression d’un seul paramètre. Pour plus d’informations, consultez la section
Chaînes de requête.

Modifications d’emplacement
Pour l’événement LocationChanged, LocationChangedEventArgs fournit les informations suivantes sur les
événements de navigation :

Location : URL du nouvel emplacement.


IsNavigationIntercepted : Si la valeur est true , cela signifie que Blazor a intercepté la navigation à partir du
navigateur. Si la valeur est false , cela signifie que NavigationManager.NavigateTo a déclenché la navigation.

Le composant suivant :

Accède au composant Counter ( Counter.razor ) de l’application quand le bouton est sélectionné à l’aide de
NavigateTo.
Gère l’événement de changement d’emplacement en s’abonnant à NavigationManager.LocationChanged.

La méthode HandleLocationChanged est décrochée quand Dispose est appelé par le framework. Le
décrochage de la méthode permet le nettoyage de la mémoire (garbage collection) du composant.

L’implémentation du journaliseur entraîne la journalisation des informations suivantes quand le bouton est
sélectionné :

BlazorSample.Pages.Navigate: Information: URL of new location: https://localhost:{PORT}/counter

Navigate.razor :

razor

@page "/navigate"
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation

<PageTitle>Navigate</PageTitle>

<h1>Navigate Example</h1>

<button class="btn btn-primary" @onclick="NavigateToCounterComponent">


Navigate to the Counter component
</button>
@code {
private void NavigateToCounterComponent()
{
Navigation.NavigateTo("counter");
}

protected override void OnInitialized()


{
Navigation.LocationChanged += HandleLocationChanged;
}

private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)


{
Logger.LogInformation("URL of new location: {Location}", e.Location);
}

public void Dispose()


{
Navigation.LocationChanged -= HandleLocationChanged;
}
}

Pour plus d’informations sur la suppression de composants, consultez Cycle de vie des composants ASP.NET Core
Razor.

Navigation améliorée et gestion des formulaires


Cette section s’applique aux applications web Blazor.

Les applications web Blazor sont capables de deux types de routage pour la navigation de page et les requêtes de
gestion des formulaires :

Navigation normale (navigation entre documents) : un rechargement de page complète est déclenché pour
l’URL de la requête.
Navigation améliorée (navigation dans le même document)† : Blazor intercepte la requête et effectue une
requête fetch à la place. Blazor corrige ensuite le contenu de la réponse dans le DOM de la page. La
navigation Blazor améliorée et la gestion des formulaires évitent d’avoir besoin d’un rechargement de page
complète et conservent davantage l’état de la page. Les pages se chargent donc plus rapidement,
généralement sans perdre la position de défilement de l’utilisateur sur la page.

† La navigation améliorée est disponible lorsque :

Le script de l’application web Blazor ( blazor.web.js ) est utilisé, et non le script Blazor Server
( blazor.server.js ) ou le script Blazor WebAssembly ( blazor.webassembly.js ).
La fonctionnalité n’est pas explicitement désactivée.
L’URL de destination se trouve dans l’espace d’URI de base interne (chemin d’accès de base de l’application).

Si le routage côté serveur et la navigation améliorée sont activés, les gestionnaires de modification d’emplacement
sont appelés seulement pour la navigation par programmation lancée depuis un runtime interactif. Dans les
versions ultérieures, d’autres types de navigation, comme des clics sur des liens, pourront également appeler les
gestionnaires de modification d’emplacement.

Quand une navigation améliorée se produit, ce sont généralement des gestionnaires d’événements
LocationChanged inscrits auprès du serveur interactif et des runtimes WebAssembly qui sont appelés. Il existe des
cas où les gestionnaires de modification d’emplacement peuvent ne pas intercepter une navigation améliorée. Par
exemple, l’utilisateur peut basculer vers une autre page avant qu’un runtime interactif ne devienne disponible. Par
conséquent, il est important que la logique de l’application ne s’appuie pas sur l’appel d’un gestionnaire de
modification d’emplacement, car il n’y a pas de garantie qu’un gestionnaire soit en cours d’exécution.

Lors de l’appel de NavigateTo :

Si forceLoad est false , qui est la valeur par défaut :


Et la navigation améliorée est disponible à l’URL actuelle, la navigation Blazor améliorée est activée.
Sinon, Blazor effectue un rechargement de page complète pour l’URL demandée.
Si forceLoad est true : Blazor effectue un rechargement de page complète pour l’URL demandée, que la
navigation améliorée soit disponible ou non.

Vous pouvez actualiser la page active en appelant NavigationManager.Refresh(bool forceLoad = false) , qui
effectue toujours une navigation améliorée, si disponible. Si la navigation améliorée n’est pas disponible, Blazor
effectue un rechargement de page complète.

C#

Navigation.Refresh();

Passez true au paramètre forceLoad pour vous assurer qu’un rechargement de page complète est toujours
effectué, même si la navigation améliorée est disponible :

C#

Navigation.Refresh(true);

La navigation améliorée est activée par défaut, mais elle peut être contrôlée hiérarchiquement et par lien à l’aide
de l’attribut HTML data-enhance-nav .

Les exemples suivants désactivent la navigation améliorée :

HTML

<a href="redirect" data-enhance-nav="false">


GET without enhanced navigation
</a>

razor

<ul data-enhance-nav="false">
<li>
<a href="redirect">GET without enhanced navigation</a>
</li>
<li>
<a href="redirect-2">GET without enhanced navigation</a>
</li>
</ul>

Si la destination est un point de terminaison non-Blazor, la navigation améliorée ne s’applique pas et le JavaScript
côté client effectue de nouvelles tentatives en tant que chargement de page complète. Cela garantit l’absence de
confusion dans l’infrastructure concernant les pages externes qui ne doivent pas être patchées dans une page
existante.

Pour activer la gestion améliorée des formulaires, ajoutez le paramètre Enhance aux formulaires EditForm ou
l’attribut data-enhance aux formulaires HTML ( <form> ) :
razor

<EditForm Enhance ...>


...
</EditForm>

HTML

<form ... data-enhance>


...
</form>

La gestion améliorée des formulaires n’est pas hiérarchique et ne passe pas aux formulaires enfants :

❌ vous ne pouvez pas définir la navigation améliorée sur l’élément ancêtre d’un formulaire pour activer la
navigation améliorée pour le formulaire.

HTML

<div data-enhance>
<form ...>
<!-- NOT enhanced -->
</form>
</div>

Les publications de formulaire améliorées fonctionnent uniquement avec les points de terminaison Blazor. La
publication d’un formulaire amélioré sur un point de terminaison non-Blazor entraîne une erreur.

Pour désactiver la navigation améliorée :

Pour un EditForm, supprimez le paramètre Enhance de l’élément de formulaire (ou définissez-le sur false :
Enhance="false" ).

Pour un code HTML <form> , supprimez l’attribut data-enhance de l’élément de formulaire (ou définissez-le
sur false : data-enhance="false" ).

La navigation améliorée et la gestion des formulaires de Blazor peuvent annuler les modifications dynamiques
apportées au DOM si le contenu mis à jour ne fait pas partie du rendu du serveur. Pour conserver le contenu d’un
élément, utilisez l’attribut data-permanent .

Dans l’exemple suivant, le contenu de l’élément <div> est mis à jour dynamiquement par un script lorsque la page
se charge :

HTML

<div data-permanent>
...
</div>

Une fois que Blazor a démarré sur le client, vous pouvez utiliser l’évènement enhancedload pour écouter les mises
à jour de page améliorées. Cela permet de ré-appliquer les modifications apportées au DOM qui ont peut-être été
annulées par une mise à jour de page améliorée.

JavaScript

Blazor.addEventListener('enhancedload', () => console.log('Enhanced update!'));


Pour désactiver globalement la navigation améliorée et la gestion des formulaires, consultez Démarrage
ASP.NET Core Blazor.

La navigation améliorée avec le rendu statique côté serveur (SSR statique) nécessite une attention particulière lors
du chargement de JavaScript. Pour plus d’informations, consultez JavaScript Blazor ASP.NET Core avec rendu côté
serveur statique (SSR statique).

Produire un URI par rapport au préfixe de l’URI de base


En fonction de l’URI de base de l’application, ToBaseRelativePath convertit un URI absolu en URI par rapport au
préfixe de l’URI de base.

Prenons l'exemple suivant :

C#

try
{
baseRelativePath = Navigation.ToBaseRelativePath(inputURI);
}
catch (ArgumentException ex)
{
...
}

Si l’URI de base de l’application est https://localhost:8000 , les résultats suivants sont obtenus :

Passer https://localhost:8000/segment dans inputURI entraîne un baseRelativePath de segment .


Passer https://localhost:8000/segment1/segment2 dans inputURI entraîne un baseRelativePath de
segment1/segment2 .

Si l’URI de base de l’application ne correspond pas à l’URI de base de inputURI , une ArgumentException est levée.

Passer https://localhost:8001/segment dans inputURI entraîne l’exception suivante :

System.ArgumentException: 'The URI 'https://localhost:8001/segment' is not contained by the base URI


'https://localhost:8000/'.'

État de l’historique de navigation


Le NavigationManager utilise l’API d’historique du navigateur pour conserver l’état de l’historique de navigation
associé à chaque changement d’emplacement effectué par l’application. La conservation de l’état de l’historique
est particulièrement utile dans les scénarios de redirection externe, par exemple durant l’authentification des
utilisateurs auprès de fournisseurs d’identité externes. Pour plus d’informations, consultez la section Options de
navigation.

Options de navigation
Passez NavigationOptions à NavigateTo pour contrôler les comportements suivants :

ForceLoad : Contourne le routage côté client, et force le navigateur à charger la nouvelle page à partir du
serveur, que l’URI soit géré ou non par le routeur côté client. La valeur par défaut est false .
ReplaceHistoryEntry : Remplace l’entrée actuelle dans la pile de l’historique. Si la valeur est false , ajoute la
nouvelle entrée à la pile de l’historique. La valeur par défaut est false .
HistoryEntryState : Obtient ou définit l’état à ajouter à l’entrée d’historique.

C#

Navigation.NavigateTo("/path", new NavigationOptions


{
HistoryEntryState = "Navigation state"
});

Pour plus d’informations sur l’obtention de l’état associé à l’entrée d’historique cible durant la gestion des
changements d’emplacement, consultez la section Gérer/empêcher les changements d’emplacement.

Chaînes de requête
Utilisez l’attribut [SupplyParameterFromQuery] pour spécifier qu’un paramètre de composant provient de la chaîne
de requête.

Les paramètres de composant fournis à partir de la chaîne de requête prennent en charge les types suivants :

bool , DateTime , decimal , double , float , Guid , int , long , string .

Variantes Nullable des types précédents.


Tableaux des types précédents, qu’il s’agisse ou non de types Nullable.

La mise en forme appropriée, indépendante de la culture, est appliquée pour le type donné
(CultureInfo.InvariantCulture).

Spécifiez la propriété Name de l’attribut [SupplyParameterFromQuery] pour utiliser un nom de paramètre de


requête différent du nom de paramètre de composant. Dans l’exemple suivant, le nom C# du paramètre de
composant est {COMPONENT PARAMETER NAME} . Un autre nom de paramètre de requête est spécifié pour l’espace
réservé {QUERY PARAMETER NAME} :

C#

[SupplyParameterFromQuery(Name = "{QUERY PARAMETER NAME}")]


public string? {COMPONENT PARAMETER NAME} { get; set; }

Dans l’exemple suivant avec l’URL /search?filter=scifi%20stars&page=3&star=LeVar%20Burton&star=Gary%20Oldman :

La propriété Filter est résolue en scifi stars .


La propriété Page est résolue en 3 .
Le tableau Stars est rempli à partir des paramètres de requête nommés star ( Name = "star" ), et se résout
en LeVar Burton et Gary Oldman .

7 Notes

Les paramètres de chaîne de requête dans le composant de page routable suivant fonctionnent également
dans un composant non routable sans directive @page (par exemple, Search.razor pour un composant
Search partagé utilisé dans d’autres composants).

Search.razor :

razor
@page "/search"

<h1>Search Example</h1>

<p>Filter: @Filter</p>

<p>Page: @Page</p>

@if (Stars is not null)


{
<p>Stars:</p>

<ul>
@foreach (var name in Stars)
{
<li>@name</li>
}
</ul>
}

@code {
[SupplyParameterFromQuery]
public string? Filter { get; set; }

[SupplyParameterFromQuery]
public int? Page { get; set; }

[SupplyParameterFromQuery(Name = "star")]
public string[]? Stars { get; set; }
}

Utilisez NavigationManager.GetUriWithQueryParameter pour ajouter, changer ou supprimer un ou plusieurs


paramètres de requête de l’URL actuelle :

razor

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameter("{NAME}", {VALUE})

Pour l’exemple précédent :

L’espace réservé {NAME} spécifie le nom du paramètre de requête. L’espace réservé {VALUE} spécifie la valeur
en tant que type pris en charge. Les types pris en charge sont listés plus loin dans cette section.
Une chaîne équivalente à l’URL actuelle est retournée avec un seul paramètre :
Si le nom du paramètre de requête n’existe pas dans l’URL actuelle, il est ajouté à la chaîne retournée.
Si le paramètre de requête existe dans l’URL actuelle, sa valeur est mise à jour en fonction de la valeur
fournie dans la chaîne retournée.
Si le type de la valeur fournie est Nullable et si la valeur est null , le paramètre de requête est supprimé de
la chaîne retournée.
La mise en forme appropriée, indépendante de la culture, est appliquée pour le type donné
(CultureInfo.InvariantCulture).
Le nom et la valeur du paramètre de requête sont codés dans l’URL.
Toutes les valeurs portant le nom du paramètre de requête correspondant sont remplacées, s’il existe
plusieurs instances du type.

Appelez NavigationManager.GetUriWithQueryParameters pour créer un URI construit à partir de Uri avec plusieurs
paramètres ajoutés, mis à jour ou supprimés. Pour chaque valeur, le framework utilise value?.GetType() afin de
déterminer le type de runtime de chaque paramètre de requête, et sélectionne la mise en forme appropriée,
indépendamment de la culture. Le framework lève une erreur pour les types non pris en charge.

razor

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameters({PARAMETERS})

L’espace réservé {PARAMETERS} est un IReadOnlyDictionary<string, object> .

Passez une chaîne d’URI à GetUriWithQueryParameters pour générer un nouvel URI à partir d’un URI fourni avec
plusieurs paramètres ajoutés, mis à jour ou supprimés. Pour chaque valeur, le framework utilise value?.GetType()
afin de déterminer le type de runtime de chaque paramètre de requête, et sélectionne la mise en forme
appropriée, indépendamment de la culture. Le framework lève une erreur pour les types non pris en charge. Les
types pris en charge sont listés plus loin dans cette section.

razor

@inject NavigationManager Navigation

...

Navigation.GetUriWithQueryParameters("{URI}", {PARAMETERS})

L’espace réservé {URI} est l’URI avec ou sans chaîne de requête.


L’espace réservé {PARAMETERS} est un IReadOnlyDictionary<string, object> .

Les types pris en charge sont identiques aux types pris en charge pour les contraintes de route :

bool

DateTime
decimal

double
float

Guid

int
long

string

Les types pris en charge comprennent les suivants :

Variantes Nullable des types précédents.


Tableaux des types précédents, qu’il s’agisse ou non de types Nullable.

Remplacer une valeur de paramètre de requête quand le paramètre existe


C#

Navigation.GetUriWithQueryParameter("full name", "Morena Baccarin")

ノ Agrandir le tableau
URL actuelle URL générée

scheme://host/?full%20name=David%20Krumholtz&age=42 scheme://host/?full%20name=Morena%20Baccarin&age=42

scheme://host/?fUlL%20nAmE=David%20Krumholtz&AgE=42 scheme://host/?full%20name=Morena%20Baccarin&AgE=42

scheme://host/? scheme://host/?
full%20name=Jewel%20Staite&age=42&full%20name=Summer%20Glau full%20name=Morena%20Baccarin&age=42&full%20name=Morena%20Baccarin

scheme://host/?full%20name=&age=42 scheme://host/?full%20name=Morena%20Baccarin&age=42

scheme://host/?full%20name= scheme://host/?full%20name=Morena%20Baccarin

Ajouter un paramètre de requête et une valeur quand le paramètre n’existe


pas
C#

Navigation.GetUriWithQueryParameter("name", "Morena Baccarin")

ノ Agrandir le tableau

URL actuelle URL générée

scheme://host/?age=42 scheme://host/?age=42&name=Morena%20Baccarin

scheme://host/ scheme://host/?name=Morena%20Baccarin

scheme://host/? scheme://host/?name=Morena%20Baccarin

Supprimer un paramètre de requête quand la valeur du paramètre est


null

C#

Navigation.GetUriWithQueryParameter("full name", (string)null)

ノ Agrandir le tableau

URL actuelle URL générée

scheme://host/?full%20name=David%20Krumholtz&age=42 scheme://host/?age=42

scheme://host/?full%20name=Sally%20Smith&age=42&full%20name=Summer%20Glau scheme://host/?age=42

scheme://host/?full%20name=Sally%20Smith&age=42&FuLl%20NaMe=Summer%20Glau scheme://host/?age=42

scheme://host/?full%20name=&age=42 scheme://host/?age=42

scheme://host/?full%20name= scheme://host/

Ajouter, mettre à jour et supprimer des paramètres de requête


Dans l’exemple suivant :

name est supprimé, le cas échéant.


age est ajouté avec la valeur 25 ( int ), s’il n’est pas présent. S’il est présent, age est mis à jour avec la valeur
25 .

eye color est ajouté ou mis à jour avec la valeur green .

C#

Navigation.GetUriWithQueryParameters(
new Dictionary<string, object?>
{
["name"] = null,
["age"] = (int?)25,
["eye color"] = "green"
})

ノ Agrandir le tableau

URL actuelle URL générée

scheme://host/?name=David%20Krumholtz&age=42 scheme://host/?age=25&eye%20color=green

scheme://host/?NaMe=David%20Krumholtz&AgE=42 scheme://host/?age=25&eye%20color=green

scheme://host/?name=David%20Krumholtz&age=42&keepme=true scheme://host/?age=25&keepme=true&eye%20color=green

scheme://host/?age=42&eye%20color=87 scheme://host/?age=25&eye%20color=green

scheme://host/? scheme://host/?age=25&eye%20color=green

scheme://host/ scheme://host/?age=25&eye%20color=green

Prise en charge des valeurs énumérables


Dans l’exemple suivant :

full name est ajouté ou mis à jour avec Morena Baccarin , une valeur unique.

Les paramètres de ping sont ajoutés ou remplacés par 35 , 16 , 87 et 240 .

C#

Navigation.GetUriWithQueryParameters(
new Dictionary<string, object?>
{
["full name"] = "Morena Baccarin",
["ping"] = new int?[] { 35, 16, null, 87, 240 }
})

ノ Agrandir le tableau

URL actuelle URL générée

scheme://host/? scheme://host/?
full%20name=David%20Krumholtz&ping=8&ping=300 full%20name=Morena%20Baccarin&ping=35&ping=16&ping=87&ping=240

scheme://host/? scheme://host/?
ping=8&full%20name=David%20Krumholtz&ping=300 ping=35&full%20name=Morena%20Baccarin&ping=16&ping=87&ping=240

scheme://host/? scheme://host/?
ping=8&ping=300&ping=50&ping=68&ping=42 ping=35&ping=16&ping=87&ping=240&full%20name=Morena%20Baccarin
Naviguer avec une chaîne de requête ajoutée ou modifiée
Pour naviguer avec une chaîne de requête ajoutée ou modifiée, passez une URL générée à NavigateTo.

L’exemple suivant appelle :

GetUriWithQueryParameter pour ajouter ou remplacer le paramètre de requête name à l’aide de la valeur


Morena Baccarin .

Appelle NavigateTo pour déclencher la navigation vers la nouvelle URL.

C#

Navigation.NavigateTo(
Navigation.GetUriWithQueryParameter("name", "Morena Baccarin"));

Routage haché vers des éléments nommés


Accédez à un élément nommé à l’aide des approches suivantes avec une référence de hachage ( # ) à l’élément. Les
itinéraires vers des éléments au sein du composant et vers des éléments dans des composants externes utilisent
des chemins relatifs à la racine. Une barre oblique de début ( / ) est facultative.

Les exemples de chacune des approches suivantes illustrent la navigation vers un élément avec un id de
targetElement dans le composant Counter :

Élément d’ancrage ( <a> ) avec un href :

razor

<a href="/counter#targetElement">

Composant NavLink avec un href :

razor

<NavLink href="/counter#targetElement">

NavigationManager.NavigateTo qui passe l’URL relative :

C#

Navigation.NavigateTo("/counter#targetElement");

L’exemple suivant illustre le routage haché vers des en-têtes H2 nommés au sein d’un composant et vers des
composants externes.

Dans les composants Home ( Home.razor ) et Counter ( Counter.razor ), placez le balisage suivant en bas du balisage
de composant existant pour servir de cibles de navigation. Le <div> crée un espace vertical artificiel pour illustrer
le comportement de défilement du navigateur :

razor

<div class="border border-info rounded bg-info" style="height:500px"></div>


<h2 id="targetElement">Target H2 heading</h2>
<p>Content!</p>

Ajoutez le composant HashedRouting suivant à l’application.

HashedRouting.razor :

razor

@page "/hashed-routing"
@inject NavigationManager Navigation

<PageTitle>Hashed routing</PageTitle>

<h1>Hashed routing to named elements</h1>

<ul>
<li>
<a href="/hashed-routing#targetElement">
Anchor in this component
</a>
</li>
<li>
<a href="/#targetElement">
Anchor to the <code>Home</code> component
</a>
</li>
<li>
<a href="/counter#targetElement">
Anchor to the <code>Counter</code> component
</a>
</li>
<li>
<NavLink href="/hashed-routing#targetElement">
Use a `NavLink` component in this component
</NavLink>
</li>
<li>
<button @onclick="NavigateToElement">
Navigate with <code>NavigationManager</code> to the
<code>Counter</code> component
</button>
</li>
</ul>

<div class="border border-info rounded bg-info" style="height:500px"></div>

<h2 id="targetElement">Target H2 heading</h2>


<p>Content!</p>

@code {
private void NavigateToElement()
{
Navigation.NavigateTo("/counter#targetElement");
}
}

Interaction utilisateur avec du contenu <Navigating>


S’il existe un délai important pendant la navigation, par exemple pendant un chargement différé d’assemblys dans
une application Blazor WebAssembly ou pour une connexion réseau lente à une application Blazor côté serveur, le
composant Router peut indiquer à l’utilisateur qu’une transition de page se produit.
En haut du composant qui spécifie le composant Router, ajoutez une directive @using pour l’espace de noms
Microsoft.AspNetCore.Components.Routing :

razor

@using Microsoft.AspNetCore.Components.Routing

Fournissez du contenu au paramètre Navigating à afficher lors des événements de transition de page.

Dans le contenu de l’élément routeur ( <Router>...</Router> ) :

razor

<Navigating>
<p>Loading the requested page&hellip;</p>
</Navigating>

Pour obtenir un exemple qui utilise la propriété Navigating, consultez Charger des assemblys en mode différé dans
ASP.NET Core Blazor WebAssembly.

Gérer les événements de navigation asynchrones avec


OnNavigateAsync
Le composant Router prend en charge une fonctionnalité OnNavigateAsync. Le gestionnaire OnNavigateAsync est
appelé quand l’utilisateur :

Visite une route pour la première fois en y accédant directement dans son navigateur.
Accède à une nouvelle route à l’aide d’un lien ou d’un appel de NavigationManager.NavigateTo.

razor

<Router AppAssembly="typeof(App).Assembly"
OnNavigateAsync="OnNavigateAsync">
...
</Router>

@code {
private async Task OnNavigateAsync(NavigationContext args)
{
...
}
}

Pour obtenir un exemple qui utilise OnNavigateAsync, consultez Charger des assemblys en mode différé dans
ASP.NET Core Blazor WebAssembly.

En cas de prérendu sur le serveur, OnNavigateAsync est exécuté deux fois :

Une première fois quand le composant de point de terminaison demandé est affiché initialement de manière
statique.
Une deuxième fois quand le navigateur affiche le composant de point de terminaison.

Pour empêcher le code de développeur dans OnNavigateAsync de s’exécuter à deux reprises, le composant
Routes peut stocker le NavigationContext afin de l’utiliser dans OnAfterRender{Async}, où firstRender peut être

vérifié. Pour plus d’informations, consultez Prérendu avec l’interopérabilité JavaScript dans l’article Cycle de vie de
Blazor.
Gérer les annulations dans OnNavigateAsync
L’objet NavigationContext passé au rappel de OnNavigateAsync contient un CancellationToken qui est défini
quand un nouvel événement de navigation se produit. Le rappel de OnNavigateAsync doit lever une exception au
moment où ce jeton d’annulation est défini pour éviter de poursuivre l’exécution du rappel de OnNavigateAsync
en cas de navigation obsolète.

Si un utilisateur accède à un point de terminaison, puis accède immédiatement après à un nouveau point de
terminaison, l’application ne doit pas continuer à exécuter le rappel de OnNavigateAsync pour le premier point de
terminaison.

Dans l’exemple suivant :

Le jeton d’annulation est passé dans l’appel à PostAsJsonAsync , qui peut annuler la méthode POST si
l’utilisateur quitte le point de terminaison /about .
Le jeton d’annulation est défini durant une opération de prérécupération du produit si l’utilisateur quitte le
point de terminaison /store .

razor

@inject HttpClient Http


@inject ProductCatalog Products

<Router AppAssembly="typeof(App).Assembly"
OnNavigateAsync="OnNavigateAsync">
...
</Router>

@code {
private async Task OnNavigateAsync(NavigationContext context)
{
if (context.Path == "/about")
{
var stats = new Stats { Page = "/about" };
await Http.PostAsJsonAsync("api/visited", stats,
context.CancellationToken);
}
else if (context.Path == "/store")
{
var productIds = new[] { 345, 789, 135, 689 };

foreach (var productId in productIds)


{
context.CancellationToken.ThrowIfCancellationRequested();
Products.Prefetch(productId);
}
}
}
}

7 Notes

Le fait de ne pas lever d’exception si le jeton d’annulation dans NavigationContext est annulé peut entraîner
un comportement involontaire, par exemple le rendu d’un composant à partir d’une navigation précédente.

Gérer/empêcher les changements d’emplacement


RegisterLocationChangingHandler inscrit un gestionnaire pour traiter les événements de navigation entrants. Le
contexte du gestionnaire fourni par LocationChangingContext comprend les propriétés suivantes :

TargetLocation : obtient l’emplacement cible.


HistoryEntryState : obtient l’état associé à l’entrée d’historique cible.
IsNavigationIntercepted : détermine si la navigation a été interceptée à partir d’un lien.
CancellationToken : obtient un CancellationToken pour déterminer si la navigation a été annulée, par exemple
pour savoir si l’utilisateur a déclenché une autre navigation.
PreventNavigation : appelé pour empêcher la navigation de continuer.

Un composant peut inscrire plusieurs gestionnaires de changement d’emplacement dans ses méthodes
OnAfterRender ou OnAfterRenderAsync. La navigation appelle tous les gestionnaires de changement
d’emplacement inscrits dans l’ensemble de l’application (pour plusieurs composants), et la navigation interne les
exécute tous en parallèle. Des gestionnaires NavigateTo supplémentaires sont appelés :

Au moment de la sélection des liens internes, qui sont des liens pointant vers des URL sous le chemin de
base de l’application.
Durant la navigation à l’aide des boutons Précédent et Suivant d’un navigateur.

Les gestionnaires sont exécutés uniquement pour la navigation interne au sein de l’application. Si l’utilisateur
sélectionne un lien qui lui permet de naviguer vers un autre site, ou s’il change manuellement la barre d’adresse
pour accéder à un autre site, les gestionnaires de changement d’emplacement ne sont pas exécutés.

Implémentez IDisposable, et supprimez les gestionnaires inscrits pour les désinscrire. Pour plus d’informations,
consultez le cycle de vie des composants Razor ASP.NET Core.

) Important

N’essayez pas d’exécuter des tâches de nettoyage DOM via l’interopérabilité JavaScript (JS) quand vous gérez
les changements d’emplacement. Utilisez le modèle MutationObserver dans JS sur le client. Pour plus
d’informations, consultez Interopérabilité JavaScript et ASP.NET Core Blazor (interopérabilité JS).

Dans l’exemple suivant, un gestionnaire de changement d’emplacement est inscrit pour les événements de
navigation.

NavHandler.razor :

razor

@page "/nav-handler"
@implements IDisposable
@inject NavigationManager Navigation

<p>
<button @onclick="@(() => Navigation.NavigateTo("/"))">
Home (Allowed)
</button>
<button @onclick="@(() => Navigation.NavigateTo("/counter"))">
Counter (Prevented)
</button>
</p>

@code {
private IDisposable? registration;

protected override void OnAfterRender(bool firstRender)


{
if (firstRender)
{
registration =
Navigation.RegisterLocationChangingHandler(OnLocationChanging);
}
}

private ValueTask OnLocationChanging(LocationChangingContext context)


{
if (context.TargetLocation == "/counter")
{
context.PreventNavigation();
}

return ValueTask.CompletedTask;
}

public void Dispose() => registration?.Dispose();


}

Dans la mesure où la navigation interne peut être annulée de manière asynchrone, plusieurs appels à des
gestionnaires inscrits peuvent se chevaucher. Par exemple, plusieurs appels de gestionnaires peuvent se produire
quand l’utilisateur sélectionne rapidement le bouton Précédent sur une page, ou qu’il sélectionne plusieurs liens
avant l’exécution d’une navigation. Voici un récapitulatif de la logique de navigation asynchrone :

Si des gestionnaires de changement d’emplacement sont inscrits, l’ensemble de la navigation est initialement
rétabli, puis relu, si la navigation n’est pas annulée.
Si des demandes de navigation se chevauchent, la dernière demande annule toujours les demandes
antérieures, ce qui signifie plusieurs choses :
L’application peut traiter plusieurs sélections des boutons Précédent et Suivant sous forme d’une seule
sélection.
Si l’utilisateur sélectionne plusieurs liens avant la fin de la navigation, le dernier lien sélectionné détermine
la navigation.

Pour plus d’informations sur le passage de NavigationOptions à NavigateTo pour contrôler les entrées et l’état de
la pile de l’historique de navigation, consultez la section Options de navigation.

Pour obtenir un exemple de code supplémentaire, consultez les informations relatives au


NavigationManagerComponent dans BasicTestApp (source de référence relative à dotnet/aspnetcore) .

7 Notes

Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du
référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus
d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Le composant NavigationLock intercepte les événements de navigation tant qu’ils sont affichés, ce qui permet de
« verrouiller » efficacement toute navigation donnée jusqu’à ce qu’il soit décidé de la poursuivre ou de l’annuler.
Utilisez NavigationLock quand l’interception de la navigation peut être délimitée en fonction de la durée de vie
d’un composant.

Paramètres de NavigationLock :

ConfirmExternalNavigation définit une boîte de dialogue de navigateur pour inviter l’utilisateur à confirmer
ou à annuler la navigation externe. La valeur par défaut est false . L’affichage de la boîte de dialogue de
confirmation nécessite une interaction initiale de l’utilisateur avec la page avant de déclencher une
navigation externe avec l’URL dans la barre d’adresse du navigateur. Pour plus d’informations sur le besoin
d’interaction, consultez Fenêtre : événement beforeunload (Documentation MDN) .
OnBeforeInternalNavigation définit un rappel pour les événements de navigation internes.

Dans le composant NavLock suivant :

Toute tentative de suivi du lien vers le site web de Microsoft doit être confirmée par l’utilisateur pour que la
navigation vers https://www.microsoft.com aboutisse.
PreventNavigation est appelé pour empêcher la navigation si l’utilisateur refuse de confirmer celle-ci via un
appel d’interopérabilité JavaScript (JS), qui génère la boîte de dialogue JSconfirm .

NavLock.razor :

razor

@page "/nav-lock"
@inject IJSRuntime JSRuntime
@inject NavigationManager Navigation

<NavigationLock ConfirmExternalNavigation="true"
OnBeforeInternalNavigation="OnBeforeInternalNavigation" />

<p>
<button @onclick="Navigate">Navigate</button>
</p>

<p>
<a href="https://www.microsoft.com">Microsoft homepage</a>
</p>

@code {
private void Navigate()
{
Navigation.NavigateTo("/");
}

private async Task OnBeforeInternalNavigation(LocationChangingContext context)


{
var isConfirmed = await JSRuntime.InvokeAsync<bool>("confirm",
"Are you sure you want to navigate to the root page?");

if (!isConfirmed)
{
context.PreventNavigation();
}
}
}

Pour obtenir un exemple de code supplémentaire, consultez les informations relatives au composant
ConfigurableNavigationLock dans BasicTestApp (source de référence relative à dotnet/aspnetcore) .

NavLink (composant)
Utilisez un composant NavLink à la place des éléments de lien hypertexte HTML ( <a> ) quand vous créez des liens
de navigation. Un composant NavLink se comporte comme un élément <a> , à ceci près qu’il ajoute/supprime une
classe CSS active selon que son href correspond ou non à l’URL actuelle. La classe active permet à un utilisateur
de comprendre quelle est la page active parmi les liens de navigation affichés. Si vous le souhaitez, affectez un
nom de classe CSS à NavLink.ActiveClass pour appliquer une classe CSS personnalisée au lien affiché quand la
route actuelle correspond à href .
Vous pouvez affecter deux options NavLinkMatch à l’attribut Match de l’élément <NavLink> :

NavLinkMatch.All : NavLink est actif quand il correspond à l’intégralité de l’URL actuelle.


NavLinkMatch.Prefix (par défaut) : NavLink est actif quand il correspond à un préfixe de l’URL actuelle.

Dans l’exemple précédent, HomeNavLink href="" correspond à l’URL d’accueil et reçoit uniquement la classe CSS
active au niveau du chemin de base par défaut de l’application ( / ). Le deuxième NavLink reçoit la classe active

quand l’utilisateur visite une URL ayant un préfixe component (par exemple /component et /component/another-
segment ).

Des attributs de composant NavLink supplémentaires sont passés à la balise d’ancrage affichée. Dans l’exemple
suivant, le composant NavLink inclut l’attribut target :

razor

<NavLink href="example-page" target="_blank">Example page</NavLink>

Le balisage HTML suivant est affiché :

HTML

<a href="example-page" target="_blank">Example page</a>

2 Avertissement

En raison de la façon dont Blazor affiche le contenu enfant, le rendu des composants NavLink dans une
boucle for nécessite une variable d’index local si la variable de boucle d’incrémentation est utilisée dans le
contenu du composant (enfant) NavLink :

razor

@for (int c = 0; c < 10; c++)


{
var current = c;
<li ...>
<NavLink ... href="product-number/@c">
<span ...></span> Product #@current
</NavLink>
</li>
}

L’utilisation d’une variable d’index dans ce scénario est obligatoire pour tout composant enfant qui utilise une
variable de boucle dans son contenu enfant, et pas seulement le composant NavLink .

Vous pouvez également utiliser une boucle foreach avec Enumerable.Range :

razor

@foreach (var c in Enumerable.Range(0,10))


{
<li ...>
<NavLink ... href="product-number/@c">
<span ...></span> Product #@c
</NavLink>
</li>
}
Intégration du routage de points de terminaison ASP.NET
Core
Cette section s’applique aux applications Web Blazor fonctionnant sur un circuit.

Une application Web Blazor est intégrée au routage de points de terminaison ASP.NET Core. Une application
ASP.NET Core est configurée pour accepter les connexions entrantes des composants interactifs avec
MapRazorComponents dans le fichier Program . Le composant racine par défaut (premier composant chargé) est le
composant App ( App.razor ) :

C#

app.MapRazorComponents<App>();

6 Collaborer avec nous sur


GitHub Commentaires sur ASP.NET Core
La source de ce contenu se trouve ASP.NET Core est un projet open source. Sélectionnez un
sur GitHub, où vous pouvez lien pour fournir des commentaires :
également créer et examiner les
problèmes et les demandes de  Ouvrir un problème de documentation
tirage. Pour plus d’informations,
consultez notre guide du  Indiquer des commentaires sur le produit
contributeur.
Configuration Blazor de ASP.NET Core
Article • 26/12/2023

Cet article explique comment configurer des applications Blazor, notamment les
paramètres de l’application, l’authentification et la configuration de la journalisation.

Ces conseils s’appliquent aux éléments suivants :

Rendu WebAssembly interactif dans une application Web Blazor.


Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lors de l’utilisation du mode de rendu WebAssembly interactif ou Automatique


interactif, gardez à l’esprit que tout le code des composants est compilé et envoyé au
client. Les utilisateurs peuvent ainsi le décompiler et l’inspecter. N’insérez pas de code
privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, les exemples de la documentation ne figurent pas tous dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications. Nous aurons terminé ces transferts
dans le courant du premier trimestre 2024.

Pour la configuration des applications ASP.NET Core côté serveur, consultez


Configuration dans ASP.NET Core.

Sur le client, la configuration est chargée par défaut à partir des fichiers de configuration
d’application suivants :

wwwroot/appsettings.json .

wwwroot/appsettings.{ENVIRONMENT}.json , où l’espace réservé {ENVIRONMENT} est

l’environnement d’exécution de l’application.

7 Notes

La configuration de la journalisation placée dans un fichier de paramètres


d’application dans wwwroot n’est pas chargée par défaut. Pour plus d’informations,
consultez la section Configuration de la journalisation plus loin dans cet article.

Dans certains scénarios, comme avec les services Azure, il est important d’utiliser un
segment de nom de fichier d’environnement qui correspond exactement au nom
de l’environnement. Par exemple, utilisez le nom de fichier
appsettings.Staging.json avec un « S » majuscule pour l’environnement Staging .

Pour connaître les conventions recommandées, consultez les remarques


d’ouverture des environnements Blazor ASP.NET Core.

D’autres fournisseurs de configuration inscrits par l’application peuvent également


fournir une configuration, mais tous les fournisseurs ou fonctionnalités de fournisseur ne
conviennent pas :

Fournisseur de configuration Azure Key Vault : le fournisseur n’est pas pris en


charge pour les scénarios d’identité managée et d’ID d’application (ID client) avec
clé secrète client. L’ID d’application avec une clé secrète client n’est pas
recommandé pour une application ASP.NET Core, en particulier pour les
applications côté client, car la clé secrète client ne peut pas être sécurisée côté
client pour accéder au service Azure Key Vault.
Fournisseur de configuration d’applications Azure : le fournisseur n’est pas
approprié pour les applications côté client, car elles ne s’exécutent pas sur un
serveur dans Azure.

Pour plus d’informations sur les fournisseurs de configuration, consultez Configuration


dans ASP.NET Core.
2 Avertissement

Les fichiers de configuration et de paramètres sont visibles par les utilisateurs sur le
client, et les utilisateurs peuvent falsifier les données. Ne stockez pas de secrets
d’application, d’informations d’identification ou d’autres données sensibles dans
la configuration ou les fichiers de l’application.

Configuration des paramètres de l’application


La configuration dans les fichiers de paramètres d’application est chargée par défaut.
Dans l’exemple suivant, une valeur de configuration de l’interface utilisateur est stockée
dans un fichier de paramètres d’application et chargée automatiquement par
l’infrastructure Blazor. La valeur est lue par un composant.

wwwroot/appsettings.json :

JSON

{
"h1FontSize": "50px"
}

Injectez une instance IConfiguration dans un composant pour accéder aux données de
configuration.

ConfigExample.razor :

razor

@page "/config-example"
@inject IConfiguration Configuration

<PageTitle>Configuration</PageTitle>

<h1 style="font-size:@Configuration["h1FontSize"]">
Configuration example (50px)
</h1>

Les restrictions de sécurité du client empêchent l’accès direct aux fichiers via le code
utilisateur, y compris les fichiers de paramètres pour la configuration de l’application.
Pour lire les fichiers de configuration en plus de appsettings.json / appsettings.
{ENVIRONMENT}.json du dossier wwwroot dans la configuration, utilisez un HttpClient.
2 Avertissement

Les fichiers de configuration et de paramètres sont visibles par les utilisateurs sur le
client, et les utilisateurs peuvent falsifier les données. Ne stockez pas de secrets
d’application, d’informations d’identification ou d’autres données sensibles dans
la configuration ou les fichiers de l’application.

L’exemple suivant lit un fichier de configuration ( cars.json ) dans la configuration de


l’application.

wwwroot/cars.json :

JSON

{
"size": "tiny"
}

Ajoutez l’espace de noms pour Microsoft.Extensions.Configuration au fichier Program :

C#

using Microsoft.Extensions.Configuration;

Modifiez l’inscription de service HttpClient existante pour utiliser le client afin de lire le
fichier :

C#

var http = new HttpClient()


{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
};

builder.Services.AddScoped(sp => http);

using var response = await http.GetAsync("cars.json");


using var stream = await response.Content.ReadAsStreamAsync();

builder.Configuration.AddJsonStream(stream);

Source de configuration de la mémoire


L’exemple suivant utilise un MemoryConfigurationSource dans le fichier Program pour
fournir une configuration supplémentaire.

Ajoutez l’espace de noms pour Microsoft.Extensions.Configuration.Memory dans le


fichier Program :

C#

using Microsoft.Extensions.Configuration.Memory;

Dans le fichier Program :

C#

var vehicleData = new Dictionary<string, string?>()


{
{ "color", "blue" },
{ "type", "car" },
{ "wheels:count", "3" },
{ "wheels:brand", "Blazin" },
{ "wheels:brand:type", "rally" },
{ "wheels:year", "2008" },
};

var memoryConfig = new MemoryConfigurationSource { InitialData = vehicleData


};

builder.Configuration.Add(memoryConfig);

Injectez une instance IConfiguration dans un composant pour accéder aux données de
configuration.

MemoryConfig.razor :

razor

@page "/memory-config"
@inject IConfiguration Configuration

<PageTitle>Memory Configuration</PageTitle>

<h1>Memory Configuration Example</h1>

<h2>General specifications</h2>

<ul>
<li>Color: @Configuration["color"]</li>
<li>Type: @Configuration["type"]</li>
</ul>
<h2>Wheels</h2>

<ul>
<li>Count: @Configuration["wheels:count"]</li>
<li>Brand: @Configuration["wheels:brand"]</li>
<li>Type: @Configuration["wheels:brand:type"]</li>
<li>Year: @Configuration["wheels:year"]</li>
</ul>

Obtenez une section de la configuration en code C# avec IConfiguration.GetSection.


L’exemple suivant obtient la section wheels de la configuration de l’exemple précédent :

razor

@code {
protected override void OnInitialized()
{
var wheelsSection = Configuration.GetSection("wheels");

...
}
}

Configuration de l'authentification
Fournissez une configuration d’authentification dans un fichier de paramètres
d’application.

wwwroot/appsettings.json :

JSON

{
"Local": {
"Authority": "{AUTHORITY}",
"ClientId": "{CLIENT ID}"
}
}

Chargez la configuration d’un fournisseur Identity avec ConfigurationBinder.Bind dans le


fichier Program . L’exemple suivant charge la configuration d’un fournisseur OIDC :

C#

builder.Services.AddOidcAuthentication(options =>
builder.Configuration.Bind("Local", options.ProviderOptions));

Configuration de la journalisation
Cette section s’applique aux applications qui configurent la journalisation via un fichier de
paramètres d’application dans le dossier wwwroot .

Ajoutez le package Microsoft.Extensions.Logging.Configuration à l’application.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Dans le fichier de paramètres de l’application, indiquez la configuration de


journalisation. La configuration de journalisation est chargée dans le fichier Program .

wwwroot/appsettings.json :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

Dans le fichier Program :

C#

builder.Logging.AddConfiguration(
builder.Configuration.GetSection("Logging"));

Configuration de build de l’hôte


Lisez la configuration de build de l’hôte à partir de
WebAssemblyHostBuilder.Configuration dans le fichier Program :

C#

var hostname = builder.Configuration["HostName"];

Configuration mise en cache


Les fichiers de configuration sont mis en cache pour une utilisation hors connexion.
Avec les Applications web progressives (PWA), vous pouvez uniquement mettre à jour
les fichiers de configuration lors de la création d’un nouveau déploiement. La
modification des fichiers de configuration entre les déploiements n’a aucun effet, car :

Les utilisateurs ont mis en cache des versions des fichiers qu’ils continuent
d’utiliser.
Les fichiers service-worker.js et service-worker-assets.js de la PWA doivent
être regénérés lors de la compilation, ce qui signale à l’application, lors de la
prochaine visite en ligne de l’utilisateur, que l’application a été redéployée.

Pour plus d’informations sur la façon dont les mises à jour en arrière-plan sont gérées
par les PWA, consultez Application web progressive (PWA) Blazor ASP.NET Core.

Configuration des options


La configuration des options nécessite l’ajout d’une référence de package pour le
package NuGet Microsoft.Extensions.Options.ConfigurationExtensions .

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Exemple :

C#

builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));

Les Fonctionnalités des options ASP.NET Core ne sont pas toutes prises en charge dans
les composants Razor. Par exemple, la configuration IOptionsSnapshot<TOptions> et
IOptionsMonitor<TOptions> est prise en charge, mais le recalcul des valeurs d’option
pour ces interfaces n’est pas prise en charge en dehors du rechargement de
l’application, soit en requérant l’application dans un nouvel onglet du navigateur, soit en
sélectionnant le bouton de rechargement du navigateur. Le simple fait d’appeler
StateHasChanged ne met pas à jour les valeurs de l’instantané ou des options surveillées
lorsque la configuration sous-jacente change.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Injection de dépendances ASP.NET Core
Blazor
Article • 11/02/2024

Par Rainer Stropek et Mike Rousos

Cet article explique comment les applications Blazor peuvent injecter des services dans
des composants.

L’injection de dépendances (DI) est une technique permettant d’accéder aux services
configurés dans un emplacement central :

Les services inscrits dans le framework peuvent être injectés directement dans les
composants Razor.
Les applications Blazor définissent et inscrivent des services personnalisés et les
rendent disponibles dans l’ensemble de l’application via la DI.

7 Notes

Nous vous recommandons de lire Injection de dépendances dans ASP.NET Core


avant de lire cette rubrique.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.
Dans une application Blazor WebAssembly autonome, les composants
fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du dépôt GitHub d’exemples
Blazor qui correspond à la version de .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Services par défaut


Les services indiqués dans le tableau suivant sont couramment utilisés dans les
applications Blazor.

ノ Agrandir le tableau

Service Durée de vie Description

HttpClient Délimité Fournit des méthodes pour envoyer des


requêtes HTTP et recevoir des réponses
HTTP d’une ressource identifiée par un URI.

Côté client, une instance de HttpClient est


inscrite par l’application dans le fichier
Program et utilise le navigateur pour gérer le
trafic HTTP en arrière-plan.

Côté client, aucun HttpClient n’est configuré


comme service par défaut. Dans du code
côté serveur, indiquez un HttpClient.
Service Durée de vie Description

Pour plus d’informations, consultez Appeler


une API web à partir d’une application
Blazor ASP.NET Core.

Un HttpClient est inscrit en tant que service


délimité, et non en tant que singleton. Pour
plus d’informations, consultez la section
Durée de vie du service.

IJSRuntime Côté client : singleton Représente une instance d’un runtime


JavaScript où les appels JavaScript sont
Côté serveur : délimité distribués. Pour plus d’informations,
consultez Appeler des fonctions JavaScript à
Le framework Blazor inscrit
partir de méthodes .NET dans ASP.NET Core
IJSRuntime dans le
Blazor.
conteneur de service de
l’application. Lorsque vous souhaitez injecter le service
dans un service singleton sur le serveur,
adoptez l’une des approches suivantes :

Modifiez l’inscription du service sur


« délimité » pour qu’elle corresponde
à l’inscription de IJSRuntime, ce qui
est approprié si le service traite avec
l’état spécifique de l’utilisateur.
Passez le IJSRuntime dans
l’implémentation du service singleton
comme argument de ses appels de
méthode au lieu de l’injecter dans le
singleton.

NavigationManager Côté client : singleton Contient des aides pour l’utilisation des URI
et de l’état de navigation. Pour plus
Côté serveur : délimité d’informations, consultez URI et assistants
d’état de navigation.
Le framework Blazor inscrit
NavigationManager dans le
conteneur de service de
l’application.

Les services supplémentaires inscrits par le framework Blazor sont décrits dans la
documentation où ils sont utilisés pour décrire des fonctionnalités Blazor, comme la
configuration et la journalisation.

Un fournisseur de services personnalisé ne fournit pas automatiquement les services par


défaut répertoriés dans le tableau. Si vous utilisez un fournisseur de services
personnalisé et que vous avez besoin de l’un des services indiqués dans le tableau,
ajoutez les services requis au nouveau fournisseur de services.

Ajouter des services côté client


Configurez les services pour la collection de services de l’application dans le fichier
Program . Dans l’exemple suivant, l’implémentation ExampleDependency est inscrite pour

IExampleDependency :

C#

var builder = WebAssemblyHostBuilder.CreateDefault(args);


...
builder.Services.AddSingleton<IExampleDependency, ExampleDependency>();
...

await builder.Build().RunAsync();

Une fois l’hôte généré, les services sont disponibles à partir de l’étendue de DI racine
avant que tous les composants soient rendus. Cela peut être utile pour exécuter la
logique d’initialisation avant le rendu du contenu :

C#

var builder = WebAssemblyHostBuilder.CreateDefault(args);


...
builder.Services.AddSingleton<WeatherService>();
...

var host = builder.Build();

var weatherService = host.Services.GetRequiredService<WeatherService>();


await weatherService.InitializeWeatherAsync();

await host.RunAsync();

L’hôte fournit une instance de configuration centrale pour l’application. À partir de


l’exemple précédent, l’URL du service météo est passée d’une source de configuration
par défaut (par exemple, appsettings.json ) à InitializeWeatherAsync :

C#

var builder = WebAssemblyHostBuilder.CreateDefault(args);


...
builder.Services.AddSingleton<WeatherService>();
...
var host = builder.Build();

var weatherService = host.Services.GetRequiredService<WeatherService>();


await weatherService.InitializeWeatherAsync(
host.Configuration["WeatherServiceUrl"]);

await host.RunAsync();

Ajouter des services côté serveur


Après avoir créé une application, examinez une partie du fichier Program :

C#

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();

La variable builder représente un WebApplicationBuilder avec une IServiceCollection,


qui est une liste d’objets de descripteur de service. Les services sont ajoutés en
fournissant des descripteurs de service à la collection de services. L’exemple suivant
illustre le concept avec l’interface IDataAccess et son implémentation concrète
DataAccess :

C#

builder.Services.AddSingleton<IDataAccess, DataAccess>();

Inscrire des services communs


Si un ou plusieurs services communs sont nécessaires côté client et côté serveur, vous
pouvez placer les inscriptions des services communs dans une méthode côté client et
appeler la méthode pour inscrire les services dans les deux projets.

Tout d’abord, factorisez les inscriptions de service communes dans une méthode
distincte. Par exemple, créez une méthode ConfigureCommonServices côté client :

C#

public static void ConfigureCommonServices(IServiceCollection services)


{
services.Add...;
}

Pour le fichier Program côté client, appelez ConfigureCommonServices pour inscrire les
services communs :

C#

var builder = WebAssemblyHostBuilder.CreateDefault(args);

...

ConfigureCommonServices(builder.Services);

Dans le fichier Program côté serveur, appelez ConfigureCommonServices pour inscrire les
services communs :

C#

var builder = WebApplication.CreateBuilder(args);

...

Client.Program.ConfigureCommonServices(builder.Services);

Pour obtenir un exemple de cette approche, consultez Scénarios de sécurité


supplémentaires ASP.NET Core Blazor WebAssembly.

Services côté client qui échouent lors du


prérendu
Cette section s’applique seulement aux composants WebAssembly dans les applications
web Blazor.

Les applications web Blazor effectuent normalement le prérendu des composants


WebAssembly côté client. Si une application est exécutée avec un service requis inscrit
seulement dans le projet .Client , l’exécution de l’application génère une erreur de
runtime similaire à l’erreur suivante quand un composant tente d’utiliser le service
requis lors du prérendu :

InvalidOperationException : Impossible de fournir une valeur pour {PROPRIÉTÉ} sur


le type « {ASSEMBLY}}.Client.Pages.{NOM DU COMPOSANT} ». Il n’existe pas de
service inscrit de type « {SERVICE} ».
Pour résoudre ce problème, utilisez l’une des approches suivantes :

Inscrivez le service dans le projet principal pour le rendre disponible lors de la


prérendu des composants.
Si le prérendu n’est pas requis pour le composant, désactivez le prérendu en
suivant les instructions fournies dans Modes de prérendu d’ASP.NETBlazor. Si vous
adoptez cette approche, vous n’avez pas besoin d’inscrire le service dans le projet
principal.

Pour plus d’informations, consultez Les services côté client ne peuvent pas être résolus
lors du prérendu.

Durée de vie d’un service


Les services peuvent être configurés avec les durées de vie indiquées dans le tableau
suivant.

ノ Agrandir le tableau

Durée de Description
vie

Scoped Pour l’heure, il n’existe pas de concept d’étendues d’injection de dépendances côté
client. Les services inscrits par Scoped se comportent comme des services Singleton .

Le développement côté serveur prend en charge la durée de vie Scoped entre les
requêtes HTTP, mais pas entre les messages de connexion/circuit SignalR parmi les
composants qui sont chargés sur le client. La partie Razor Pages ou MVC de
l’application traite normalement les services délimités et recrée les services pour
chaque requête HTTP lors de la navigation entre pages ou vues, ou d’une page ou vue
à un composant. Les services délimités ne sont pas reconstruits lors de la navigation
entre les composants sur le client, où la communication vers le serveur a lieu sur la
connexion SignalR du circuit de l’utilisateur, et non via des requêtes HTTP. Dans les
scénarios de composant suivants sur le client, les services délimités sont reconstruits,
car un nouveau circuit est créé pour l’utilisateur :

L’utilisateur ferme la fenêtre du navigateur. L’utilisateur ouvre une nouvelle


fenêtre et retourne à l’application.
L’utilisateur ferme un onglet de l’application dans une fenêtre de navigateur.
L’utilisateur ouvre un nouvel onglet et retourne à l’application.
L’utilisateur sélectionne le bouton Recharger/Actualiser du navigateur.

Pour plus d’informations sur la préservation de l’état utilisateur dans les applications
côté serveur, consultez Gestion de l’état ASP.NET Core Blazor.

Singleton La DI crée une seule instance du service. Tous les composants nécessitant un service
Singleton reçoivent la même instance du service.
Durée de Description
vie

Transient Chaque fois qu’un composant obtient une instance d’un service Transient à partir du
conteneur de service, il reçoit une nouvelle instance du service.

Le système de DI est basé sur le système d’authentification unique dans ASP.NET Core.
Pour plus d’informations, consultez Injection de dépendances dans ASP.NET Core.

Demander un service dans un composant


Une fois les services ajoutés à la collection de services, injectez les services dans les
composants à l’aide de la directive @injectRazor, qui a deux paramètres :

Type : le type du service à injecter.


Propriété : le nom de la propriété recevant le service d’application injecté. La
propriété ne nécessite pas de création manuelle. Le compilateur crée la propriété.

Pour plus d’informations, consultez Injection de dépendances dans des vues dans
ASP.NET Core.

Utilisez plusieurs instructions @inject pour injecter différents services.

L'exemple suivant montre comment utiliser @inject. Le service implémentant


Services.IDataAccess est injecté dans le DataRepository de propriétés du composant.

Notez comment le code utilise uniquement l’abstraction IDataAccess :

razor

@page "/the-sunmakers"
@inject IDataAccess DataRepository

<PageTitle>The Sunmakers</PageTitle>

<h1>Doctor Who®: The Sunmakers Actors (Villains)</h1>

@if (actors != null)


{
<ul>
@foreach (var actor in actors)
{
<li>@actor.FirstName @actor.LastName</li>
}
</ul>
}

<a href="https://www.doctorwho.tv">Doctor Who</a> is a


registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
<a href="https://www.doctorwho.tv/stories/the-sunmakers">The Sunmakers</a>

@code {
private IReadOnlyList<Actor>? actors;

protected override async Task OnInitializedAsync()


{
actors = await DataRepository.GetAllActorsAsync();
}

public class Actor


{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}

public interface IDataAccess


{
public Task<IReadOnlyList<Actor>> GetAllActorsAsync();
}

public class DataAccess : IDataAccess


{
public Task<IReadOnlyList<Actor>> GetAllActorsAsync() =>
Task.FromResult(GetActors());
}

/*
* Register the service in Program.cs:
* using static BlazorSample.Components.Pages.TheSunmakers;
* builder.Services.AddScoped<IDataAccess, DataAccess>();
*/

public static IReadOnlyList<Actor> GetActors()


{
return new Actor[]
{
new() { FirstName = "Henry", LastName = "Woolf" },
new() { FirstName = "Jonina", LastName = "Scott" },
new() { FirstName = "Richard", LastName = "Leech" }
};
}
}

En interne, la propriété générée ( DataRepository ) utilise l’attribut [Inject]. En règle


générale, cet attribut n’est pas utilisé directement. Si une classe de base est requise pour
les composants et que des propriétés injectées sont également requises pour la classe
de base, ajoutez manuellement l’attribut [Inject] :

C#
using Microsoft.AspNetCore.Components;

public class ComponentBase : IComponent


{
[Inject]
protected IDataAccess DataRepository { get; set; } = default!;

...
}

7 Notes

Comme les services injectés sont censés être disponibles, le littéral par défaut avec
l’opérateur null-forgiving ( default! ) est affecté dans .NET 6 ou version ultérieure.
Pour plus d’informations, consultez Analyse statique des types de référence
nullables (NRT) et de l’état null du compilateur .NET.

Dans les composants dérivés de la classe de base, la directive @inject n’est pas
obligatoire. L’élément InjectAttribute de la classe de base est suffisant, et tout ce dont le
composant a besoin est la directive @inherits :

razor

@page "/demo"
@inherits ComponentBase

<h1>Demo Component</h1>

Utiliser la DI dans les services


Les services complexes peuvent nécessiter des services supplémentaires. Dans l’exemple
suivant, DataAccess nécessite le service HttpClient par défaut. @inject (ou l’attribut
[Inject]) n’est pas disponible pour une utilisation dans les services. L’injection de
constructeur doit être utilisée à la place. Les services requis sont ajoutés en ajoutant des
paramètres au constructeur du service. Lorsque la DI crée le service, elle reconnaît les
services nécessaires dans le constructeur et les fournit en conséquence. Dans l’exemple
suivant, le constructeur reçoit HttpClient via la DI. HttpClient est un service par défaut.

C#

using System.Net.Http;

public class DataAccess : IDataAccess


{
public DataAccess(HttpClient http)
{
...
}
}

Prérequis pour l’injection de constructeur :

Il doit exister un constructeur dont les arguments peuvent tous être remplis par la
DI. Les paramètres supplémentaires non couverts par la DI sont autorisés s’ils
spécifient des valeurs par défaut.
Le constructeur applicable doit être public .
Un constructeur applicable doit exister. En cas d’ambiguïté, la DI lève une
exception.

Injecter des services à clé dans les composants


Blazor prend en charge l’injection de services à clé à l’aide de l’attribut [Inject] . Les
clés permettent de définir l’étendue de l'inscription et de la consommation des services
lors de l'utilisation de l'injection de dépendances. Utilisez la propriété InjectAttribute.Key
pour spécifier la clé du service à injecter :

C#

[Inject(Key = "my-service")]
public IMyService MyService { get; set; }

Classes de composants de base de l’utilitaire


pour gérer une étendue de DI
Dans les applications ASP.NET Core non-Blazor, les services délimités et temporaires
sont généralement limités à la requête actuelle. Une fois la requête terminée, les
services délimités et temporaires sont supprimés par le système de DI.

Dans les applications Blazor côté serveur interactives, l’étendue de l’injection de


dépendances couvre la durée du circuit (la connexion SignalR entre le client et le
serveur), ce qui peut allonger la durée de vie des services délimités et temporaires
supprimables bien au-delà de la durée de vie d’un seul composant. Par conséquent,
n’injectez pas directement un service délimité dans un composant si vous voulez que la
durée de vie du service corresponde à la durée de vie du composant. Les services
temporaires injectés dans un composant qui n’implémentent pas IDisposable sont
supprimés quand le composant est supprimé. Cependant, les services temporaires
injectés qui implémentent IDisposable sont gérés par le conteneur d’injection de
dépendances pendant la durée de vie du circuit, ce qui empêche la garbage collection
du service quand le composant est supprimé et aboutit à une fuite de mémoire. Une
approche alternative pour les services délimités en fonction du type
OwningComponentBase est décrite plus loin dans cette section, et les services
temporaires supprimables ne doivent pas être utilisés du tout. Pour plus d’informations,
consultez Concevoir pour résoudre les éléments temporaires supprimables sur Blazor
Server (dotnet/aspnetcore #26676) .

Même dans les applications Blazor côté client qui ne fonctionnent pas sur un circuit, les
services inscrits avec une durée de vie délimitée sont traités comme des singletons ; leur
durée de vie est donc plus longue que celle des services délimités dans les applications
ASP.NET Core classiques. Les services temporaires supprimables côté client vivent
également plus longtemps que les composants dans lesquels ils sont injectés, car le
conteneur d’injection de dépendances, qui contient des références aux services
supprimables, existe pendant toute la durée de vie de l’application, empêchant la
garbage collection sur les services. Bien que les services temporaires supprimables avec
une durée de vie longue soient plus préoccupants sur le serveur, vous devez aussi les
éviter en tant qu’inscriptions de service client. L’utilisation du type
OwningComponentBase est également recommandée pour les services délimités côté
client pour contrôler la durée de vie du service, et les services temporaires supprimables
ne doivent pas être utilisés du tout.

Une approche qui limite la durée de vie d’un service est l’utilisation du type
OwningComponentBase. OwningComponentBase est un type abstrait dérivé de
ComponentBase qui crée une étendue de DI correspondant à la durée de vie du
composant. En utilisant cette étendue, un composant peut injecter des services ayant
une durée de vie délimitée et faire en sorte que leur durée de vie corresponde à celle du
composant. Lorsque le composant est détruit, les services du fournisseur de services
délimités du composant sont également supprimés. Ceci peut être utile pour les services
réutilisés au sein d’un composant mais pas partagés entre des composants.

Deux versions de type OwningComponentBase sont disponibles et décrites dans les


deux sections suivantes :

OwningComponentBase
OwningComponentBase<TService>

OwningComponentBase
OwningComponentBase est un enfant abstrait et jetable du type ComponentBase avec
une propriété ScopedServices protégée de type IServiceProvider. Le fournisseur peut
être utilisé pour résoudre les services qui sont limités à la durée de vie du composant.

Les services de DI injectés dans le composant à l’aide de @inject ou de l’attribut [Inject]


ne sont pas créés dans l’étendue du composant. Pour utiliser l’étendue du composant,
les services doivent être résolus à l’aide de ScopedServices avec GetRequiredService ou
GetService. Tous les services résolus à l’aide du fournisseur ScopedServices ont leurs
dépendances fournies dans l’étendue du composant.

L’exemple suivant illustre la différence entre l’injection directe d’un service délimité et la
résolution d’un service en utilisant ScopedServices sur le serveur. L’interface et
l’implémentation suivantes pour une classe de voyage dans le temps incluent une
propriété DT pour contenir une valeur DateTime. L’implémentation appelle
DateTime.Now pour définir DT lorsque la classe TimeTravel est instanciée.

ITimeTravel.cs :

C#

public interface ITimeTravel


{
public DateTime DT { get; set; }
}

TimeTravel.cs :

C#

public class TimeTravel : ITimeTravel


{
public DateTime DT { get; set; } = DateTime.Now;
}

Le service est inscrit comme étant délimité dans le fichier Program côté serveur. Côté
serveur, les services délimités ont une durée de vie égale à la durée du circuit.

Dans le fichier Program :

C#

builder.Services.AddScoped<ITimeTravel, TimeTravel>();

Dans le composant TimeTravel suivant :


Le service de voyage dans le temps est directement injecté avec @inject en tant
que TimeTravel1 .
Le service est également résolu séparément avec ScopedServices et
GetRequiredService en tant que TimeTravel2 .

TimeTravel.razor :

razor

@page "/time-travel"
@inject ITimeTravel TimeTravel1
@inherits OwningComponentBase

<h1><code>OwningComponentBase</code> Example</h1>

<ul>
<li>TimeTravel1.DT: @TimeTravel1?.DT</li>
<li>TimeTravel2.DT: @TimeTravel2?.DT</li>
</ul>

@code {
private ITimeTravel TimeTravel2 { get; set; } = default!;

protected override void OnInitialized()


{
TimeTravel2 = ScopedServices.GetRequiredService<ITimeTravel>();
}
}

Navigant initialement vers le composant TimeTravel , le service de voyage dans le temps


est instancié deux fois lorsque le composant se charge, et TimeTravel1 et TimeTravel2
ont la même valeur initiale :

TimeTravel1.DT: 8/31/2022 2:54:45 PM


TimeTravel2.DT: 8/31/2022 2:54:45 PM

Lorsque vous naviguez loin du composant TimeTravel pour un autre composant et


revenez au composant TimeTravel :

TimeTravel1 reçoit la même instance de service que celle créée lors du premier

chargement du composant ; la valeur de DT reste donc la même.


TimeTravel2 obtient une nouvelle instance de service ITimeTravel dans
TimeTravel2 avec une nouvelle valeur DT.

TimeTravel1.DT: 8/31/2022 2:54:45 PM


TimeTravel2.DT: 8/31/2022 2:54:48 PM
TimeTravel1 est lié au circuit de l’utilisateur, qui reste intact et n’est pas supprimé tant

que le circuit sous-jacent n’est pas déconstruit. Par exemple, le service est supprimé si le
circuit est déconnecté pendant la période de rétention du circuit déconnecté.

Malgré l’inscription de service délimité dans le fichier Program et la longévité du circuit


de l’utilisateur, TimeTravel2 reçoit une nouvelle instance de service ITimeTravel chaque
fois que le composant est initialisé.

OwningComponentBase<TService>

OwningComponentBase<TService> dérive de OwningComponentBase et ajoute une


propriété Service qui retourne une instance de T à partir du fournisseur de DI délimitée.
Ce type est un moyen pratique d’accéder aux services délimités sans utiliser d’instance
de IServiceProvider quand l’application a besoin d’un service principal à partir du
conteneur de DI avec l’étendue du composant. La propriété ScopedServices est
disponible, afin que l’application puisse obtenir des services d’autres types, si nécessaire.

razor

@page "/users"
@attribute [Authorize]
@inherits OwningComponentBase<AppDbContext>

<h1>Users (@Service.Users.Count())</h1>

<ul>
@foreach (var user in Service.Users)
{
<li>@user.UserName</li>
}
</ul>

Détecter les éléments jetables temporaires côté client


Vous pouvez ajouter du code personnalisé à une application Blazor côté client pour
détecter les services temporaires jetables dans une application qui doit utiliser
OwningComponentBase. Cette approche est utile si vous craignez que le code ajouté
par la suite à l’application consomme un ou plusieurs services temporaires jetables,
notamment des services ajoutés par des bibliothèques. Le code de démonstration est
disponible dans le dépôt GitHub d’exemples Blazor .

Inspectez ce qui suit dans les versions .NET 6 et ultérieures de l’exemple


BlazorSample_WebAssembly :
DetectIncorrectUsagesOfTransientDisposables.cs
Services/TransientDisposableService.cs

Dans : Program.cs
L’espace de noms Services de l’application est fourni en haut du fichier ( using
BlazorSample.Services; ).

DetectIncorrectUsageOfTransients est appelé immédiatement après

l’attribution de builder à partir de WebAssemblyHostBuilder.CreateDefault.


TransientDisposableService est inscrit

( builder.Services.AddTransient<TransientDisposableService>(); ).
EnableTransientDisposableDetection est appelé sur l’hôte généré dans le

pipeline de traitement de l’application


( host.EnableTransientDisposableDetection(); ).
L’application inscrit le service TransientDisposableService sans lever d’exception.
Toutefois, la tentative de résolution du service dans TransientService.razor lève
InvalidOperationException quand l’infrastructure tente de construire une instance
de TransientDisposableService .

Détecter les éléments jetables temporaires côté serveur


Vous pouvez ajouter du code personnalisé à une application Blazor côté serveur pour
détecter les services temporaires jetables côté serveur dans une application qui doit
utiliser OwningComponentBase. Cette approche est utile si vous craignez que le code
ajouté par la suite à l’application consomme un ou plusieurs services temporaires
jetables, notamment des services ajoutés par des bibliothèques. Le code de
démonstration est disponible dans le dépôt GitHub d’exemples Blazor .

Inspectez ce qui suit dans les versions .NET 8 et ultérieures de l’exemple


BlazorSample_BlazorWebApp :

DetectIncorrectUsagesOfTransientDisposables.cs
Services/TransitiveTransientDisposableDependency.cs :

Dans : Program.cs
L’espace de noms Services de l’application est fourni en haut du fichier ( using
BlazorSample.Services; ).

DetectIncorrectUsageOfTransients est appelé sur le générateur d’hôte

( builder.DetectIncorrectUsageOfTransients(); ).
Le service TransientDependency est inscrit
( builder.Services.AddTransient<TransientDependency>(); ).
TransitiveTransientDisposableDependency est inscrit pour
ITransitiveTransientDisposableDependency

( builder.Services.AddTransient<ITransitiveTransientDisposableDependency,
TransitiveTransientDisposableDependency>(); ).

L’application inscrit le service TransientDependency sans lever d’exception.


Toutefois, la tentative de résolution du service dans TransientService.razor lève
InvalidOperationException quand l’infrastructure tente de construire une instance
de TransientDependency .

Inscriptions de services temporaires pour les


gestionnaires IHttpClientFactory / HttpClient
Les inscriptions de service temporaires pour les gestionnaires
IHttpClientFactory/HttpClient sont recommandées. Si l’application contient des
gestionnaires IHttpClientFactory/HttpClient et utilise
IRemoteAuthenticationBuilder<TRemoteAuthenticationState,TAccount> pour ajouter la
prise en charge de l’authentification, les éléments temporaires jetables suivants pour
l’authentification côté client sont également découverts. Ce comportement est attendu
et peut être ignoré :

BaseAddressAuthorizationMessageHandler
AuthorizationMessageHandler

D’autres instances de IHttpClientFactory/HttpClient sont également découvertes. Ces


instances peuvent également être ignorées.

Les exemples d’applications Blazor du dépôt GitHub d’exemples Blazor montrent le


code qui permet de détecter les éléments temporaires supprimables. Toutefois, le code
est désactivé, car les exemples d’applications incluent des gestionnaires
IHttpClientFactory/HttpClient.

Pour activer le code de démonstration et voir son fonctionnement :

Supprimez les marques de commentaire des lignes correspondant aux éléments


supprimables temporaires dans Program.cs .

Supprimez la vérification conditionnelle dans NavLink.razor qui empêche le


composant TransientService de s’afficher dans la barre latérale de navigation de
l’application :

diff
- else if (name != "TransientService")
+ else

Exécutez l’exemple d’application et accédez au composant TransientService dans


/transient-service .

Utilisation d’un DbContext Entity Framework


Core (EF Core) à partir de la DI
Pour plus d’informations, consultez ASP.NET Core Blazor avec Entity Framework Core (EF
Core).

Accéder aux services Blazor côté serveur à


partir d’une étendue d’injection de
dépendances différente
Les gestionnaires d’activités de circuit proposent une approche pour accéder aux
services Blazor délimités à partir d’autres étendues d’injection de dépendances non
Blazor, telles que les étendues créées à l’aide de IHttpClientFactory.

Avant la sortie d’ASP.NET Core 8.0, l’accès aux services limités à un circuit à partir
d’autres étendues d’injection de dépendances nécessitait l’utilisation d’un type de
composant de base personnalisé. Avec les gestionnaires d’activités de circuit, il n’est pas
nécessaire d’utiliser un type de composant de base personnalisé, comme le montre
l’exemple suivant :

C#

public class CircuitServicesAccessor


{
static readonly AsyncLocal<IServiceProvider> blazorServices = new();

public IServiceProvider? Services


{
get => blazorServices.Value;
set => blazorServices.Value = value;
}
}

public class ServicesAccessorCircuitHandler : CircuitHandler


{
readonly IServiceProvider services;
readonly CircuitServicesAccessor circuitServicesAccessor;

public ServicesAccessorCircuitHandler(IServiceProvider services,


CircuitServicesAccessor servicesAccessor)
{
this.services = services;
this.circuitServicesAccessor = servicesAccessor;
}

public override Func<CircuitInboundActivityContext, Task>


CreateInboundActivityHandler(
Func<CircuitInboundActivityContext, Task> next)
{
return async context =>
{
circuitServicesAccessor.Services = services;
await next(context);
circuitServicesAccessor.Services = null;
};
}
}

public static class CircuitServicesServiceCollectionExtensions


{
public static IServiceCollection AddCircuitServicesAccessor(
this IServiceCollection services)
{
services.AddScoped<CircuitServicesAccessor>();
services.AddScoped<CircuitHandler, ServicesAccessorCircuitHandler>
();

return services;
}
}

Accédez aux services limités à un circuit en injectant le CircuitServicesAccessor là où il


est nécessaire.

Pour voir un exemple qui montre comment accéder à AuthenticationStateProvider à


partir d’un DelegatingHandler configuré à l’aide de IHttpClientFactory, consultez Autres
scénarios de sécurité ASP.NET Core Blazor côté serveur.

Ressources supplémentaires
Injection de dépendances dans ASP.NET Core
Conseils sur IDisposable pour les instances temporaires et partagées
Injection de dépendances dans des vues dans ASP.NET Core
6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Démarrage ASP.NET Core Blazor
Article • 22/12/2023

Cet article explique la configuration de démarrage de l’application Blazor.

Pour des conseils généraux sur la configuration des applications ASP.NET Core pour le
développement côté serveur, consultez Configuration dans ASP.NET Core.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lors de l’utilisation des modes d’affichage WebAssembly interactif ou Auto interactif, le


code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, les exemples de la documentation ne figurent pas tous dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Processus de démarrage et configuration


Le processus de démarrage Blazor est automatique et asynchrone par le script Blazor
( blazor.*.js ), où l’espace réservé * est :

web pour une application web Blazor

server pour une application Blazor Server

webassembly pour une application Blazor WebAssembly

Pour connaître l’emplacement du script, consultez ASP.NET Core Blazor structure du


projet.

Pour démarrer Blazor manuellement :

Application web Blazor :

Ajoutez un attribut et une valeur autostart="false" à la balise Blazor <script> .


Placez un script qui appelle Blazor.start() après la balise Blazor <script> et à
l’intérieur de la balise fermante </body> .
Placez les options de rendu statique côté serveur (SSR statique) dans la propriété
ssr .

Placez les options côté serveur du circuit Blazor-SignalR dans la propriété circuit .
Placez les options WebAssembly côté client dans la propriété webAssembly .

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
...
Blazor.start({
ssr: {
...
},
circuit: {
...
},
webAssembly: {
...
}
});
...
</script>

Blazor Server:

Ajoutez un attribut et une valeur autostart="false" à la balise Blazor <script> .


Placez un script qui appelle Blazor.start() après la balise Blazor <script> et à
l’intérieur de la balise fermante </body> .

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
...
Blazor.start();
...
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script, consultez
ASP.NET Core Blazor structure du projet.

Initialiseurs JavaScript
Les initialiseurs JavaScript (JS) exécutent la logique avant et après le chargement d’une
application Blazor. Les initialiseurs JS sont utiles dans les scénarios suivants :

Personnalisation de la façon dont une application Blazor se charge.


Initialisation des bibliothèques avant le démarrage de Blazor.
Configuration des paramètres Blazor.

Les initialiseurs JS sont détectés dans le cadre du processus de génération et importés


automatiquement. L’utilisation des initialiseurs JS supprime souvent la nécessité de
déclencher manuellement des fonctions de script à partir de l’application lors de
l’utilisation de bibliothèques de classes (RCL) Razor.

Pour définir un initialiseur JS, ajoutez un module JS au projet nommé


{NAME}.lib.module.js , où l’espace réservé {NAME} est le nom de l’assembly, le nom de la

bibliothèque ou l’identificateur de package. Placez le fichier dans la racine web du


projet, qui est généralement le dossier wwwroot .

Pour Blazor Web Apps :


beforeWebStart(options) : appelé avant le démarrage de l’application web Blazor.

Par exemple, beforeWebStart il est utilisé pour personnaliser le processus de


chargement, le niveau de journalisation et d’autres options. Reçoit les Blazor
options Web ( options ).
afterWebStarted(blazor) : appelé après toutes les promesses beforeWebStart

résolues. Par exemple, afterWebStarted vous pouvez l’utiliser pour inscrire Blazor
des écouteurs d’événements et des types d’événements personnalisés. L’instance
Blazor est passée afterWebStarted en tant qu’argument ( blazor ).
beforeServerStart(options, extensions) : appelé avant le démarrage du premier

runtime du serveur. Reçoit les options de démarrage du circuit ( options ) et toutes


les extensions ( extensions ) ajoutéesSignalRlors de la publication.
afterServerStarted(blazor) : appelé après le démarrage du premier runtime

interactive server.
beforeWebAssemblyStart(options, extensions) : appelé avant le démarrage du

runtime Interactive WebAssembly. Reçoit les Blazor options ( options ) et toutes les
extensions ( extensions ) ajoutées lors de la publication. Par exemple, les options
peuvent spécifier l’utilisation d’un chargeur de ressources de démarrage
personnalisé.
afterWebAssemblyStarted(blazor) : appelé après le démarrage du runtime

Interactive WebAssembly.

7 Notes

Les initialiseurs JS hérités ( beforeStart , afterStarted ) ne sont pas appelés par


défaut dans uneBlazorapplication web. Vous pouvez activer l’exécution des
initialiseurs hérités avec l’option enableClassicInitializers . Toutefois, l’exécution
d’initialiseur héritée est imprévisible.

HTML

<script>
Blazor.start({ enableClassicInitializers: true });
</script>

Pour Blazor Server, Blazor WebAssemblyet Blazor Hybrid les applications :

beforeStart(options, extensions) : appelée avant le démarrage de Blazor. Par

exemple, beforeStart est utilisée pour personnaliser le processus de chargement,


le niveau de journalisation et d’autres options spécifiques au modèle
d’hébergement.
Côté client, beforeStart reçoit les Blazor options ( options ) et toutes les
extensions ( extensions ) ajoutées lors de la publication. Par exemple, les options
peuvent spécifier l’utilisation d’un chargeur de ressources de démarrage
personnalisé.
Côté serveur, beforeStart reçoit les SignalR options de démarrage du circuit
( options ).
Dans un BlazorWebView, aucune option n’est transmise.
afterStarted(blazor) : appelée une fois que Blazor est prêt à recevoir des appels à

partir de JS. Par exemple, afterStarted permet d’initialiser des bibliothèques en


effectuant des appels d’interopérabilité JS et en inscrivant des éléments
personnalisés. L’instance Blazor est passée en afterStarted tant qu’argument
( blazor ).

Pour le nom de fichier :

Si les initialiseurs JS sont consommés en tant que ressource statique dans le projet,
utilisez le format {ASSEMBLY NAME}.lib.module.js , où l’espace réservé {ASSEMBLY
NAME} est le nom de l’assembly de l’application. Par exemple, nommez le fichier
BlazorSample.lib.module.js pour un projet avec le nom d’assembly BlazorSample .

Placez le fichier dans le dossier wwwroot de l’application.


Si les initialiseurs JS sont consommés à partir d’une RCL, utilisez le format {LIBRARY
NAME/PACKAGE ID}.lib.module.js , où l’espace réservé {LIBRARY NAME/PACKAGE ID}

est le nom de la bibliothèque ou l’identificateur de package du projet. Par exemple,


nommez le fichier RazorClassLibrary1.lib.module.js pour une bibliothèque RCL
avec l’identificateur de package RazorClassLibrary1 . Placez le fichier dans le
dossier wwwroot de la bibliothèque.

Pour Blazor Web Apps :

L’exemple suivant illustre des initialiseurs JS qui chargent des scripts personnalisés avant
et après le démarrage de l’application webBlazor en les ajoutant au <head> dans
beforeWebStart et afterWebStarted :

JavaScript

export function beforeWebStart() {


var customScript = document.createElement('script');
customScript.setAttribute('src', 'beforeStartScripts.js');
document.head.appendChild(customScript);
}

export function afterWebStarted() {


var customScript = document.createElement('script');
customScript.setAttribute('src', 'afterStartedScripts.js');
document.head.appendChild(customScript);
}

L’exemple beforeWebStart précédent garantit uniquement que le script personnalisé se


charge avant le démarrage de Blazor. Il ne garantit pas que les promesses attendues
dans le script terminent leur exécution avant que Blazor commence.

Pour Blazor Server, Blazor WebAssemblyet Blazor Hybrid les applications :

L’exemple suivant illustre des initialiseurs JS qui chargent des scripts personnalisés avant
et après le début de Blazor en les ajoutant au <head> dans beforeStart et
afterStarted :

JavaScript

export function beforeStart(options, extensions) {


var customScript = document.createElement('script');
customScript.setAttribute('src', 'beforeStartScripts.js');
document.head.appendChild(customScript);
}

export function afterStarted(blazor) {


var customScript = document.createElement('script');
customScript.setAttribute('src', 'afterStartedScripts.js');
document.head.appendChild(customScript);
}

L’exemple beforeStart précédent garantit uniquement que le script personnalisé se


charge avant le démarrage de Blazor. Il ne garantit pas que les promesses attendues
dans le script terminent leur exécution avant que Blazor commence.

7 Notes

Les applications MVC et Razor Pages ne chargent pas automatiquement les


initialiseurs JS. Toutefois, le code du développeur peut inclure un script pour
extraire le manifeste de l’application et déclencher la charge des initialiseurs JS.

Pour obtenir des exemples des initialiseurs JS, consultez les ressources suivantes :

JavaScript ASP.NET CoreBlazor avec rendu côté serveur statique (SSR statique)
Utiliser des composants Razor dans des applications JavaScript et des frameworks
SPA (exemple de quoteContainer2 )
Gestion des événements ASP.NET Core Blazor (exemple d’événement de collage de
presse-papiers personnalisé)
Application de test de base dans le dépôt GitHub ASP.NET Core
(BasicTestApp.lib.module.js)

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Vérifier que les bibliothèques sont chargées dans un


ordre spécifique
Ajoutez des scripts personnalisés au <head> dans beforeStart et afterStarted dans
l’ordre dans lequel ils doivent être chargés.

L’exemple suivant charge script1.js avant script2.js et script3.js avant


script4.js :

JavaScript

export function beforeStart(options, extensions) {


var customScript1 = document.createElement('script');
customScript1.setAttribute('src', 'script1.js');
document.head.appendChild(customScript1);

var customScript2 = document.createElement('script');


customScript2.setAttribute('src', 'script2.js');
document.head.appendChild(customScript2);
}

export function afterStarted(blazor) {


var customScript1 = document.createElement('script');
customScript1.setAttribute('src', 'script3.js');
document.head.appendChild(customScript1);

var customScript2 = document.createElement('script');


customScript2.setAttribute('src', 'script4.js');
document.head.appendChild(customScript2);
}

Importer des modules supplémentaires


Utilisez des instructions import de niveau supérieur dans le fichier d’initialiseurs JS
pour importer des modules supplémentaires.

additionalModule.js :

JavaScript

export function logMessage() {


console.log('logMessage is logging');
}

Dans le fichier d’initialiseurs JS ( .lib.module.js ) :

JavaScript

import { logMessage } from "/additionalModule.js";

export function beforeStart(options, extensions) {


...

logMessage();
}

Importer une carte


Les mappages d’importation sont pris en charge par ASP.NET Core et Blazor.

Initialiser Blazor lorsque le document est prêt


L’exemple suivant démarre Blazor lorsque le document est prêt :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
document.addEventListener("DOMContentLoaded", function() {
Blazor.start();
});
</script>
Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script, consultez
ASP.NET Core Blazor structure du projet.

Chaîne vers le Promise qui résulte d’un


démarrage manuel
Pour effectuer des tâches supplémentaires, comme l’initialisation de l’interopérabilité JS,
utilisez then pour chaîner le Promise résultant d’un démarrage d’application Blazor
manuel :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start().then(function () {
...
});
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script, consultez
ASP.NET Core Blazor structure du projet.

7 Notes

Pour qu’une bibliothèque exécute automatiquement des tâches supplémentaires


après le démarrage de Blazor, utilisez un initialiseur JavaScript. L’utilisation d’un
initialiseur JS ne nécessite pas que le consommateur de la bibliothèque enchaîne
des appels JS au démarrage manuel de Blazor.

Charger des ressources de démarrage côté


client
Lorsqu’une application se charge dans le navigateur, l’application télécharge les
ressources de démarrage à partir du serveur :

Code JavaScript pour démarrer l’application


Runtime et assemblys .NET
Données spécifiques aux paramètres régionaux
Personnalisez la façon dont ces ressources de démarrage sont chargées à l’aide de l’API
loadBootResource . La fonction loadBootResource remplace le mécanisme de chargement

des ressources de démarrage intégré. Utilisez loadBootResource pour les scénarios


suivants :

Charger des ressources statiques, comme des données de fuseau horaire ou


dotnet.wasm , à partir d’un CDN.

Charger des assemblys compressés à l’aide d’une requête HTTP et les


décompresser sur le client pour les hôtes qui ne prennent pas en charge
l’extraction du contenu compressé à partir du serveur.
Donner un alias aux ressources en redirigeant chaque requête fetch vers un
nouveau nom.

7 Notes

Les sources externes doivent retourner les en-têtes CORS (partage de ressources
cross-origin) requis pour que les navigateurs autorisent le chargement des
ressources entre les origines. Les CDN fournissent généralement les en-têtes requis
par défaut.

Les paramètres loadBootResource apparaissent dans le tableau suivant.

ノ Agrandir le tableau

Paramètre Description

type Type de la ressource. Les types possibles sont notamment assembly , pdb , dotnetjs ,
dotnetwasm et timezonedata . Vous devez spécifier des types seulement pour les
comportements personnalisés. Les types non spécifiés comme loadBootResource
sont chargés par le framework en fonction de leurs comportements de chargement
par défaut. La ressource de démarrage dotnetjs ( dotnet.*.js ) doit retourner null
pour le comportement de chargement par défaut ou un URI pour la source de la
ressource de démarrage dotnetjs .

name Nom de la ressource.

defaultUri URI relatif ou absolu de la ressource.

integrity Chaîne d’intégrité représentant le contenu attendu dans la réponse.

La fonction loadBootResource peut retourner une chaîne d’URI pour remplacer le


processus de chargement. Dans l’exemple suivant, les fichiers suivants de
bin/Release/{TARGET FRAMEWORK}/wwwroot/_framework sont servis à partir d’un CDN sur
https://cdn.example.com/blazorwebassembly/{VERSION}/ :

dotnet.*.js
dotnet.wasm

Données de fuseau horaire

L’espace réservé {TARGET FRAMEWORK} correspond au moniker de framework cible (par


exemple net7.0 ). L’espace réservé {VERSION} correspond à la version de framework
partagé (par exemple 7.0.0 ).

Application web Blazor :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
webAssembly: {
loadBootResource: function (type, name, defaultUri, integrity) {
console.log(`Loading: '${type}', '${name}', '${defaultUri}',
'${integrity}'`);
switch (type) {
case 'dotnetjs':
case 'dotnetwasm':
case 'timezonedata':
return
`https://cdn.example.com/blazorwebassembly/{VERSION}/${name}`;
}
}
}
});
</script>

Blazor WebAssembly autonome :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
loadBootResource: function (type, name, defaultUri, integrity) {
console.log(`Loading: '${type}', '${name}', '${defaultUri}',
'${integrity}'`);
switch (type) {
case 'dotnetjs':
case 'dotnetwasm':
case 'timezonedata':
return
`https://cdn.example.com/blazorwebassembly/{VERSION}/${name}`;
}
}
});
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script, consultez
ASP.NET Core Blazor structure du projet.

Pour personnaliser plus d’éléments que les URL des ressources de démarrage, la
fonction loadBootResource peut appeler fetch directement et retourner le résultat.
L’exemple suivant ajoute un en-tête HTTP personnalisé aux requêtes sortantes. Pour
conserver le comportement de vérification de l’intégrité par défaut, passez le paramètre
integrity .

Application web Blazor :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
webAssembly: {
loadBootResource: function (type, name, defaultUri, integrity) {
if (type == 'dotnetjs') {
return null;
} else {
return fetch(defaultUri, {
cache: 'no-cache',
integrity: integrity,
headers: { 'Custom-Header': 'Custom Value' }
});
}
}
}
});
</script>

Blazor WebAssembly autonome :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
loadBootResource: function (type, name, defaultUri, integrity) {
if (type == 'dotnetjs') {
return null;
} else {
return fetch(defaultUri, {
cache: 'no-cache',
integrity: integrity,
headers: { 'Custom-Header': 'Custom Value' }
});
}
}
});
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script, consultez
ASP.NET Core Blazor structure du projet.

Lorsque la fonction loadBootResource retourne null , Blazor utilise le comportement de


chargement par défaut pour la ressource. Par exemple, le code précédent retourne null
pour la ressource de démarrage dotnetjs ( dotnet.*.js ), car la ressource de démarrage
dotnetjs doit retourner null pour le comportement de chargement par défaut ou un

URI pour la source de la ressource de démarrage dotnetjs .

La fonction loadBootResource peut également retourner une promesse Response .


Pour obtenir un exemple, consultez Héberger et déployer ASP.NET Core Blazor
WebAssembly.

Contrôler les en-têtes dans le code C#


Contrôlez les en-têtes au démarrage dans le code C# à l’aide des approches suivantes.

Dans les exemples suivants, une stratégie de sécurité du contenu (CSP) est appliquée
à l’application via un en-tête CSP. L’espace réservé {POLICY STRING} est la chaîne de
stratégie CSP.

Scénarios côté serveur et pré-rendus côté client


Utilisez l’intergiciel ASP.NET Core pour contrôler la collection d’en-têtes.

Dans le fichier Program :

C#

app.Use(async (context, next) =>


{
context.Response.Headers.Add("Content-Security-Policy", "{POLICY
STRING}");
await next();
});

L’exemple précédent utilise l’intergiciel inline, mais vous pouvez également créer une
classe d’intergiciels personnalisée et appeler l’intergiciel avec une méthode d’extension
dans le fichier Program . Pour plus d’informations, consultez Écrire un intergiciel ASP.NET
Core personnalisé.

Développement côté client sans pré-rendu


Passez StaticFileOptions à MapFallbackToFile qui spécifie les en-têtes de réponse à
l’étape de OnPrepareResponse.

Dans le fichier Program côté serveur :

C#

var staticFileOptions = new StaticFileOptions


{
OnPrepareResponse = context =>
{
context.Context.Response.Headers.Add("Content-Security-Policy",
"{POLICY STRING}");
}
};

...

app.MapFallbackToFile("index.html", staticFileOptions);

Pour plus d’informations sur les CSP, consultez Appliquer une stratégie de sécurité de
contenu pour ASP.NET Core Blazor.

Indicateurs de progression de chargement côté


client
Cette section s’applique uniquement aux applications Blazor WebAssembly.

Le modèle de projet contient des graphiques vectoriels évolutifs (SVG) et des indicateurs
de texte qui signalent la progression du chargement de l’application.

Les indicateurs de progression sont implémentés avec HTML et CSS à l’aide de deux
propriétés CSS personnalisées (variables) fournies par Blazor :
--blazor-load-percentage : pourcentage des fichiers d’application chargé.
--blazor-load-percentage-text : pourcentage des fichiers d’application chargé,

arrondi au nombre entier le plus proche.

À l’aide des variables CSS précédentes, vous pouvez créer des indicateurs de
progression personnalisés qui correspondent au style de votre application.

Dans l’exemple suivant :

resourcesLoaded est un nombre instantané des ressources chargées au démarrage

de l’application.
totalResources est le nombre total de ressources à charger.

JavaScript

const percentage = resourcesLoaded / totalResources * 100;


document.documentElement.style.setProperty(
'--blazor-load-percentage', `${percentage}%`);
document.documentElement.style.setProperty(
'--blazor-load-percentage-text', `"${Math.floor(percentage)}%"`);

L’indicateur de progression arrondi par défaut est implémenté en HTML dans le fichier
wwwroot/index.html :

HTML

<div id="app">
<svg class="loading-progress">
<circle r="40%" cx="50%" cy="50%" />
<circle r="40%" cx="50%" cy="50%" />
</svg>
<div class="loading-progress-text"></div>
</div>

Pour passer en revue le balisage et le style du modèle de projet pour les indicateurs de
progression par défaut, consultez la source de référence ASP.NET Core :

wwwroot/index.html
app.css

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Au lieu d’utiliser l’indicateur de progression arrondi par défaut, l’exemple suivant montre
comment implémenter un indicateur de progression linéaire.

Ajoutez les styles suivants à wwwroot/css/app.css :

css

.linear-progress {
background: silver;
width: 50vw;
margin: 20% auto;
height: 1rem;
border-radius: 10rem;
overflow: hidden;
position: relative;
}

.linear-progress:after {
content: '';
position: absolute;
inset: 0;
background: blue;
scale: var(--blazor-load-percentage, 0%) 100%;
transform-origin: left top;
transition: scale ease-out 0.5s;
}

Une variable CSS ( var(...) ) est utilisée pour passer la valeur de --blazor-load-
percentage à la propriété scale d’un pseudo-élément bleu qui indique la progression

du chargement des fichiers de l’application. À mesure que l’application se charge, --


blazor-load-percentage est mis à jour automatiquement, ce qui modifie

dynamiquement la représentation visuelle de l’indicateur de progression.

Dans wwwroot/index.html , supprimez l’indicateur d’arrondi SVG par défaut dans <div
id="app">...</div> et remplacez-le par le balisage suivant :

HTML

<div class="linear-progress"></div>
Configurer le runtime .NET WebAssembly
Pour configurer le runtime .NET WebAssembly, utilisez la fonction configureRuntime
avec le générateur d’hôtes du runtime dotnet .

L’exemple suivant définit deux variables d'environnement, CONFIGURE_RUNTIME , sur true :

Application web Blazor :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
webAssembly: {
configureRuntime: dotnet => {
dotnet.withEnvironmentVariable("CONFIGURE_RUNTIME", "true");
}
}
});
</script>

Blazor WebAssembly autonome :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
configureRuntime: dotnet => {
dotnet.withEnvironmentVariable("CONFIGURE_RUNTIME", "true");
}
});
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script, consultez
ASP.NET Core Blazor structure du projet.

L’instance du runtime .NET est accessible à partir de Blazor.runtime .

Désactiver la navigation améliorée et la gestion


des formulaires
Cette section s’applique aux applications web Blazor.
Pour désactiver la navigation améliorée et la gestion des formulaires, définissez
disableDomPreservation sur true pour Blazor.start .

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
ssr: { disableDomPreservation: true }
});
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script, consultez
ASP.NET Core Blazor structure du projet.

Ressources supplémentaires
Environnements : définir l’environnement de l’application
SignalR (inclut des sections sur la configuration de démarrage de SignalR)
Globalisation et localisation : définir statiquement la culture avec Blazor.start() (côté
client uniquement)
Héberger et déployer : Blazor WebAssembly : Compression

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Environnements Blazor ASP.NET Core
Article • 22/12/2023

Cet article explique comment configurer et lire l’environnement dans une application
Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lors de l’utilisation des modes d’affichage WebAssembly interactif ou Auto interactif, le


code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, les exemples de la documentation ne figurent pas tous dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Pour obtenir des conseils généraux sur le paramétrage de l’environnement d’une


application web Blazor, consultez Utiliser plusieurs environnements dans ASP.NET Core.

Lors de l’exécution d’une application localement, l’environnement par défaut est


Development . Lorsque l’application est publiée, l’environnement est défini par défaut sur
Production .

Nous vous recommandons les conventions suivantes :

Utilisez toujours le nom d’environnement « Development » pour le développement


local. Cela est dû au fait que l’infrastructure ASP.NET Core attend exactement ce
nom lors de la configuration de l’application et de l’outil pour les exécutions de
développement local d’une application.

Pour les environnements de test, de mise en lots et de production, publiez et


déployez toujours l’application. Vous pouvez utiliser n’importe quel schéma
d’affectation de noms d’environnement que vous souhaitez pour les applications
publiées, mais utilisez toujours les noms de fichiers de paramétrage d’application
avec la casse du segment d’environnement qui correspond exactement au nom de
l’environnement. Pour la mise en lots, utilisez « Staging » (majuscule « S ») comme
nom d’environnement et nommez le fichier de paramètres de l’application à
mettre en correspondance ( appsettings.Staging.json ). Pour la production, utilisez
« Production » (majuscule « P ») comme nom d’environnement et nommez le
fichier de paramètres de l’application à mettre en correspondance
( appsettings.Production.json ).

L’environnement est défini à l’aide de l’une des approches suivantes :

Configuration de démarrage de Blazor


En-tête blazor-environment
Azure App Service

Sur le client d’une application web Blazor, l’environnement est déterminé à partir du
serveur via un intergiciel qui communique l’environnement au navigateur via un en-tête
nommé blazor-environment . L’en-tête définit l’environnement lorsque
WebAssemblyHost est créé dans le fichier Program côté client
(WebAssemblyHostBuilder.CreateDefault).
Pour l’exécution locale d’une application cliente autonome, le serveur de
développement ajoute l’en-tête blazor-environment .

Pour l’exécution locale des applications en cours de développement, l’application est par
défaut dans l’environnement Development . La publication de l’application définit
l’environnement par défaut sur Production .

Pour plus d’informations sur comment configurer l’environnement côté serveur,


consultez Utiliser plusieurs environnements dans ASP.NET Core.

Définissez l’environnement côté client via la


configuration de démarrage
L’exemple suivant démarre Blazor dans l’environnement Staging si le nom d’hôte inclut
localhost . Sinon, l’environnement est défini sur sa valeur par défaut.

Application web Blazor :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
if (window.location.hostname.includes("localhost")) {
Blazor.start({
webAssembly: {
environment: "Staging"
}
});
} else {
Blazor.start();
}
</script>

7 Notes

Pour les Web Apps Blazor qui définissent la propriété webAssembly > environment
dans la configuration Blazor.start , il est judicieux de faire correspondre
l’environnement côté serveur à l’environnement défini sur la propriété environment .
Dans le cas contraire, le pré-rendu sur le serveur fonctionnera dans un
environnement différent de celui du rendu sur le client, ce qui entraîne des effets
arbitraires. Pour obtenir des conseils généraux sur le paramétrage de
l’environnement d’une application web Blazor, consultez Utiliser plusieurs
environnements dans ASP.NET Core.

Blazor WebAssembly autonome :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
if (window.location.hostname.includes("localhost")) {
Blazor.start({
environment: "Staging"
});
} else {
Blazor.start();
}
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script, consultez
ASP.NET Core Blazor structure du projet.

L’utilisation de la propriété environment remplace l’environnement défini par l’en-tête


blazor-environment.

L’approche précédente définit l’environnement du client sans modifier la valeur de l’en-


tête blazor-environment , ni modifier la journalisation de la console du projet serveur de
l’environnement de démarrage pour une Web App Blazor qui a adopté le rendu global
Interactive WebAssembly.

Pour journaliser l’environnement dans la console d’un projet autonome Blazor


WebAssembly ou d’un projet .Client de Web App Blazor, placez le code C# suivant
dans le fichier Program après la création de WebAssemblyHost avec
WebAssemblyHostBuilder.CreateDefault et avant la ligne qui génère et exécute le projet
( await builder.Build().RunAsync(); ) :

C#

Console.WriteLine(
$"Client Hosting Environment: {builder.HostEnvironment.Environment}");

Pour plus d’informations sur le démarrage de Blazor, consultez Démarrage d’ASP.NET


Core Blazor.
Définissez l’environnement côté client via l’en-
tête
Pour spécifier l’environnement pour d’autres environnements d’hébergement, ajoutez
l’en-tête blazor-environment .

Dans l’exemple suivant pour IIS, l’en-tête personnalisé ( blazor-environment ) est ajouté
au fichier web.config publié. Le fichier web.config se trouve dans le dossier
bin/Release/{TARGET FRAMEWORK}/publish , où l’espace réservé {TARGET FRAMEWORK} est le

framework cible :

XML

<?xml version="1.0" encoding="UTF-8"?>


<configuration>
<system.webServer>

...

<httpProtocol>
<customHeaders>
<add name="blazor-environment" value="Staging" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>

7 Notes

Pour utiliser un fichier web.config personnalisé pour IIS qui n’est pas remplacé
lorsque l’application est publiée dans le dossier publish , consultez Héberger et
déployer ASP.NET Core Blazor WebAssembly.

Bien que l’infrastructure Blazor émette le nom de l’en-tête en lettres minuscules


( blazor-environment ), vous pouvez utiliser la casse de votre choix. Par exemple, un
nom d’en-tête qui met en majuscule chaque mot ( Blazor-Environment ) est pris en
charge.

Définir l’environnement pour Azure App


Service
Pour une application cliente autonome, vous pouvez définir l’environnement
manuellement via la configuration de démarrage ou l’en-tête blazor-environment.

Pour une application côté serveur, définissez l’environnement via un paramètre


d’application ASPNETCORE_ENVIRONMENT dans Azure :

1. Vérifiez que la casse des segments d’environnement dans les noms de fichier des
paramètres d’application correspond exactement à leur casse de nom
d’environnement. Par exemple, le nom du fichier de paramètres d’application
correspondant pour l’environnement Staging est appsettings.Staging.json . Si le
nom de fichier est appsettings.staging.json (« s » minuscule), le fichier n’est pas
localisé et les paramètres du fichier ne sont pas utilisés dans l’environnement
Staging .

2. Pour le déploiement de Visual Studio, vérifiez que l’application est déployée sur
l’emplacement de déploiement approprié. Pour une application nommée
BlazorAzureAppSample , l’application est déployée sur l’emplacement de

déploiement Staging .

3. Dans le Portail Azure de l’emplacement de déploiement de l’environnement,


définissez l’environnement avec le paramètre d’application
ASPNETCORE_ENVIRONMENT . Pour une application nommée BlazorAzureAppSample ,

l’emplacement App Service intermédiaire est nommé


BlazorAzureAppSample/Staging . Pour la configuration de l’emplacement Staging ,

créez un paramètre d’application pour ASPNETCORE_ENVIRONMENT avec une valeur de


Staging . Le paramètre d’emplacement de déploiement est activé pour le

paramètre.

Lorsqu’elle est demandée dans un navigateur, l’application


BlazorAzureAppSample/Staging se charge dans l’environnement Staging à l’adresse
https://blazorazureappsample-staging.azurewebsites.net .

Lorsque l’application est chargée dans le navigateur, la collection d’en-têtes de réponse


pour blazor.boot.json indique que la valeur de l’en-tête blazor-environment est
Staging .

Les paramètres de l’application à partir du fichier appsettings.{ENVIRONMENT}.json sont


chargés par l’application, où l’espace réservé {ENVIRONMENT} est l’environnement de
l’application. Dans l’exemple précédent, les paramètres du fichier
appsettings.Staging.json sont chargés.
Lisez l’environnement dans une application
Blazor WebAssembly
Obtenez l’environnement de l’application dans un composant en injectant
IWebAssemblyHostEnvironment et en lisant la propriété Environment.

ReadEnvironment.razor :

razor

@page "/read-environment"
@using Microsoft.AspNetCore.Components.WebAssembly.Hosting
@inject IWebAssemblyHostEnvironment Env

<h1>Environment example</h1>

<p>Environment: @HostEnvironment.Environment</p>

Lisez le côté client de l’environnement dans une


Web App Blazor
En supposant que le pré-rendu n’est pas désactivé pour un composant ou l’application,
un composant du projet .Client est pré-rendu sur le serveur. Étant donné que le
serveur n’a pas de service IWebAssemblyHostEnvironment inscrit, il n’est pas possible
d’injecter le service et d’utiliser les méthodes et propriétés d’extension de
l’environnement hôte de l’implémentation du service pendant le pré-rendu du serveur.
L’injection du service dans un composant Interactif WebAssembly ou Interactive Auto
entraîne l’erreur d’exécution suivante :

There is no registered service of type


'Microsoft.AspNetCore.Components.WebAssembly.Hosting.IWebAssemblyHostEnvir
onment'.

Pour résoudre ce problème, créez une implémentation de service personnalisée pour


IWebAssemblyHostEnvironment sur le serveur. Ajoutez la classe suivante au projet
serveur.

ServerHostEnvironment.cs :

C#
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Components;

public class ServerHostEnvironment(IWebHostEnvironment env,


NavigationManager nav) :
IWebAssemblyHostEnvironment
{
public string Environment => env.EnvironmentName;
public string BaseAddress => nav.BaseUri;
}

Dans le fichier Program du projet serveur, inscrivez le service :

C#

builder.Services.TryAddScoped<IWebAssemblyHostEnvironment,
ServerHostEnvironment>();

À ce stade, le service IWebAssemblyHostEnvironment peut être injecté dans un


composant WebAssembly interactif ou Auto interactif et utilisé comme indiqué dans la
section Lire l’environnement dans une application Blazor WebAssembly.

L’exemple précédent peut démontrer qu’il est possible d’avoir un environnement


serveur différent de celui de l’environnement client, ce qui n’est pas recommandé et
peut entraîner des résultats arbitraires. Lorsque vous définissez l’environnement dans
une Web App Blazor, il est préférable de faire correspondre les environnements serveur
et projet .Client . Considérez le scénario suivant dans une application de test :

Implémentez la propriété d’environnement côté client ( webassembly ) avec


l’environnement Staging via Blazor.start . Pour obtenir un exemple, consultez la
section Définir l’environnement côté client via la configuration de démarrage.
Ne modifiez pas le fichier Properties/launchSettings.json côté serveur. Laissez la
section environmentVariables avec la variable d’environnement
ASPNETCORE_ENVIRONMENT définie sur Development .

Vous pouvez voir la valeur de la modification de la propriété


IWebAssemblyHostEnvironment.Environment dans l’interface utilisateur.

Lorsque le pré-rendu se produit sur le serveur, le composant est rendu dans


l’environnement Development :

Environment: Development
Lorsque le composant est re-rendu une ou deux secondes plus tard, une fois que le
pack Blazor est téléchargé et que le runtime Blazor WebAssembly est activé, les valeurs
changent pour refléter le fait que le client opère dans l’environnement Staging sur le
client :

Environment: Staging

L’exemple précédent montre pourquoi nous recommandons de définir l’environnement


serveur de manière à ce qu’il corresponde à l’environnement client pour les
déploiements de développement, de test et de production.

Pour plus d’informations, consultez la section Échec de la résolution des services côté
client pendant le pré-rendu de l’article Modes de rendu, qui apparaît plus loin dans la
documentation Blazor.

Lisez l’environnement côté client pendant le


démarrage
Au démarrage, WebAssemblyHostBuilder expose IWebAssemblyHostEnvironment via la
propriété HostEnvironment, qui permet une logique spécifique à l’environnement dans
le code du générateur d’hôtes.

Dans le fichier Program :

C#

if (builder.HostEnvironment.Environment == "Custom")
{
...
};

Les méthodes d’extension pratiques suivantes fournies via


WebAssemblyHostEnvironmentExtensions permettent de vérifier l’environnement actuel
pour Development , Production , Staging et les noms d’environnement personnalisés :

IsDevelopment
IsProduction
IsStaging
IsEnvironment

Dans le fichier Program :


C#

if (builder.HostEnvironment.IsStaging())
{
...
};

if (builder.HostEnvironment.IsEnvironment("Custom"))
{
...
};

La propriété IWebAssemblyHostEnvironment.BaseAddress peut être utilisée au


démarrage lorsque le service NavigationManager n’est pas disponible.

Ressources supplémentaires
Démarrage ASP.NET Core Blazor
Utiliser plusieurs environnements dans ASP.NET Core
Blazor exemples de dépôt GitHub (dotnet/blazor-samples)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Journalisation ASP.NET Core Blazor
Article • 19/12/2023

Cet article explique la journalisation d’application Blazor, notamment la configuration et


la façon d’écrire des messages de journal à partir de composants Razor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode de rendu interactif avec une directive @rendermode dans le fichier de définition du
composant ( .razor ) :

Dans une application web Blazor, le composant doit avoir un mode de rendu
interactif appliqué, soit dans le fichier de définition du composant, soit hérité d’un
composant parent. Pour plus d’informations, consultez Modes de rendu ASP.NET
Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode de rendu, car
les composants s’exécutent toujours de manière interactive sur WebAssembly dans
une application Blazor WebAssembly.

Lors de l’utilisation des modes WebAssembly interactif ou Rendu automatique interactif,


le code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas
de code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration est de télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne sont pas inclus dans les exemples
d’applications, mais nous nous efforçons de transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Configuration
La configuration de la journalisation peut être chargée à partir de fichiers de paramètres
d’application. Pour plus d’informations, consultez Configuration d’ASP.NET Core Blazor.

Aux niveaux de journalisation par défaut et sans configuration de fournisseurs de


journalisation supplémentaires :

Sur le serveur, la journalisation se produit uniquement sur la console .NET côté


serveur dans l’environnement Development au niveau LogLevel.Information ou à un
niveau supérieur.
Sur le client, la journalisation se produit uniquement sur la console des outils de
développement du navigateur côté client au niveau LogLevel.Information ou à
un niveau supérieur.

Lorsque l’application est configurée dans le Fichier projet pour utiliser des espaces de
noms implicites ( <ImplicitUsings>enable</ImplicitUsings> ), une directive using pour
Microsoft.Extensions.Logging ou toute API de la classe LoggerExtensions n’est pas
nécessaire pour prendre en charge les achèvements Visual Studio IntelliSense pour les
API ou la génération d’applications. Si les espaces de noms implicites ne sont pas
activés, les composants Razor doivent définir explicitement des @usingdirectives pour la
journalisation des espaces de noms qui ne sont pas importés via le
_Imports.razor fichier.

Niveaux de journal
Les niveaux de journal sont conformes aux niveaux de journal de l’application ASP.NET
Core, qui sont répertoriés dans la documentation de l’API à l’adresse LogLevel.

Journalisation des composants Razor


L’exemple suivant :

Injecte un objet ILogger ( ILogger<Counter1> ) pour créer un enregistreur


d'événements. La catégorie du journal est le nom complet du type du composant,
Counter .
Appelle LogWarning pour journaliser au niveau Warning.

Counter1.razor :

razor

@page "/counter-1"
@inject ILogger<Counter1> Logger

<PageTitle>Counter 1</PageTitle>

<h1>Counter 1</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
Logger.LogWarning("Someone has clicked me!");

currentCount++;
}
}

L’exemple suivant illustre la journalisation avec un composant ILoggerFactory.

Counter2.razor :

razor

@page "/counter-2"
@inject ILoggerFactory LoggerFactory

<PageTitle>Counter 2</PageTitle>

<h1>Counter 2</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
var logger = LoggerFactory.CreateLogger<Counter2>();
logger.LogWarning("Someone has clicked me!");
currentCount++;
}
}

Journalisation côté serveur


Pour obtenir des instructions générales ASP.NET Core de journalisation, consultez
Journalisation dans .NET Core et ASP.NET Core.

Journalisation côté client


Toutes les fonctionnalités de la Journalisation ASP.NET Core ne sont pas prises en
charge côté client. Par exemple, les composants côté client n'ont pas accès au système
de fichiers ou au réseau du client, il n'est donc pas possible d'écrire des journaux sur le
stockage physique ou réseau du client. Lorsque vous utilisez un service de journalisation
tiers conçu pour fonctionner avec des applications monopages (SPA), suivez les
instructions de sécurité du service. Gardez à l’esprit que toutes les données, y compris
les clés ou les secrets stockés côté client, ne sont pas sécurisées et peuvent être
facilement découvertes par des utilisateurs malveillants.

Configurez la journalisation dans les applications côté client avec la propriété


WebAssemblyHostBuilder.Logging. La propriété Logging étant de type ILoggingBuilder,
les méthodes d’extension de ILoggingBuilder sont prises en charge.

Pour définir le niveau de journalisation minimal, appelez


LoggingBuilderExtensions.SetMinimumLevel sur le générateur d’hôte dans le fichier
Program avec le LogLevel. L’exemple suivant définit le niveau de journalisation minimal
sur Warning :

C#

builder.Logging.SetMinimumLevel(LogLevel.Warning);

Journal dans le fichier Program côté client


La journalisation est prise en charge dans les applications côté client une fois que
WebAssemblyHostBuilder est généré à l’aide du fournisseur d’enregistreurs
d’événements de console interne de l’infrastructure
(WebAssemblyConsoleLoggerProvider (source de référence) ).
Dans le fichier Program :

C#

var host = builder.Build();

var logger = host.Services.GetRequiredService<ILoggerFactory>()


.CreateLogger<Program>();

logger.LogInformation("Logged after the app is built in the Program file.");

await host.RunAsync();

Sortie de la console des outils du développeur :

info: Program[0]
Logged after the app is built in the Program file.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Catégorie de journal côté client


Les catégories de journaux sont prises en charge.

L’exemple suivant montre comment utiliser des catégories de journaux avec le


composant Counter d’une application créée à partir d’un modèle de projet Blazor.

Dans la méthode IncrementCount du composant Counter de l’application


( Counter.razor ) qui injecte un ILoggerFactory en tant que LoggerFactory :

C#

var logger = LoggerFactory.CreateLogger("CustomCategory");


logger.LogWarning("Someone has clicked me!");
Sortie de la console des outils du développeur :

warn: CustomCategory[0]
Someone has clicked me!

ID de l'événement du journal côté client


L’ID d’événement de journal est pris en charge.

L’exemple suivant montre comment utiliser des ID d’événements de journal avec le


composant Counter d’une application créée à partir d’un modèle de projet Blazor.

LogEvent.cs :

C#

public class LogEvent


{
public const int Event1 = 1000;
public const int Event2 = 1001;
}

Dans la méthode IncrementCount du composant Counter de l’application


( Counter.razor ) :

C#

logger.LogInformation(LogEvent.Event1, "Someone has clicked me!");


logger.LogWarning(LogEvent.Event2, "Someone has clicked me!");

Sortie de la console des outils du développeur :

info: BlazorSample.Pages.Counter[1000]
Someone has clicked me!
warn: BlazorSample.Pages.Counter[1001]
Someone has clicked me!

Modèle de message de journal côté client


Les modèles de message de journal sont pris en charge :

L’exemple suivant montre comment utiliser des modèles de message de journal avec le
composant Counter d’une application créée à partir d’un modèle de projet Blazor.
Dans la méthode IncrementCount du composant Counter de l’application
( Counter.razor ) :

C#

logger.LogInformation("Someone clicked me at {CurrentDT}!",


DateTime.UtcNow);

Sortie de la console des outils du développeur :

info: BlazorSample.Pages.Counter[0]
Someone clicked me at 04/21/2022 12:15:57!

Paramètres d'exception du journal côté client


Les paramètres d’exception de journal sont pris en charge.

L’exemple suivant montre comment utiliser des paramètres d’exception de journal avec
le composant Counter d’une application créée à partir d’un modèle de projet Blazor.

Dans la méthode IncrementCount du composant Counter de l’application


( Counter.razor ) :

C#

currentCount++;

try
{
if (currentCount == 3)
{
currentCount = 4;
throw new OperationCanceledException("Skip 3");
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "Exception (currentCount: {Count})!",
currentCount);
}

Sortie de la console des outils du développeur :

warn: BlazorSample.Pages.Counter[0]
Exception (currentCount: 4)!
System.OperationCanceledException: Skip 3
at BlazorSample.Pages.Counter.IncrementCount() in
C:UsersAlabaDesktopBlazorSamplePagesCounter.razor:line 28

Fonction de filtre côté client


Les fonctions de filtre sont prises en charge.

L’exemple suivant montre comment utiliser un filtre avec le composant Counter d’une
application créée à partir d’un modèle de projet Blazor.

Dans le fichier Program :

C#

builder.Logging.AddFilter((provider, category, logLevel) =>


category.Equals("CustomCategory2") && logLevel == LogLevel.Information);

Dans la méthode IncrementCount du composant Counter de l’application


( Counter.razor ) qui injecte un ILoggerFactory en tant que LoggerFactory :

C#

var logger1 = LoggerFactory.CreateLogger("CustomCategory1");


logger1.LogInformation("Someone has clicked me!");

var logger2 = LoggerFactory.CreateLogger("CustomCategory1");


logger2.LogWarning("Someone has clicked me!");

var logger3 = LoggerFactory.CreateLogger("CustomCategory2");


logger3.LogInformation("Someone has clicked me!");

var logger4 = LoggerFactory.CreateLogger("CustomCategory2");


logger4.LogWarning("Someone has clicked me!");

Dans la sortie de la console des outils du développeur, le filtre autorise uniquement la


journalisation pour la catégorie CustomCategory2 et le message de niveau journal
Information :

info: CustomCategory2[0]
Someone has clicked me!

L’application peut également configurer le filtrage des journaux pour des espaces de
noms spécifiques. Par exemple, définissez le niveau de journalisation sur Trace dans le
fichier Program :
C#

builder.Logging.SetMinimumLevel(LogLevel.Trace);

Normalement au niveau du journal Trace, la sortie de la console des outils du


développeur au niveau Détaillé inclut des messages de journalisation
Microsoft.AspNetCore.Components.RenderTree, tels que les suivants :

dbug: Microsoft.AspNetCore.Components.RenderTree.Renderer[3]
Rendering component 14 of type
Microsoft.AspNetCore.Components.Web.HeadOutlet

Dans le fichier Program , la journalisation des messages spécifiques à


Microsoft.AspNetCore.Components.RenderTree peut être désactivée à l’aide de l’une
des approches suivantes :

C#

builder.Logging.AddFilter("Microsoft.AspNetCore.Components.RenderTree.*
", LogLevel.None);

C#

builder.Services.PostConfigure<LoggerFilterOptions>(options =>
options.Rules.Add(
new LoggerFilterRule(null,

"Microsoft.AspNetCore.Components.RenderTree.*",
LogLevel.None,
null)
));

Une fois l’un des filtres précédents ajouté à l’application, la sortie de la console au
niveau Détaillé n’affiche pas les messages de journalisation de l’API
Microsoft.AspNetCore.Components.RenderTree.

Fournisseur d’enregistreurs d’événements


personnalisés côté client
L’exemple de cette section illustre un fournisseur d’enregistreurs d’événements
personnalisé pour une personnalisation ultérieure.
Ajoutez une référence de package à l’application pour le package
Microsoft.Extensions.Logging.Configuration .

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Ajoutez la configuration d’enregistreur d’événements personnalisée suivante. La


configuration établit un dictionnaire LogLevels qui définit un format de journal
personnalisé pour trois niveaux de journalisation : Information, Warning, et Error. Un
LogFormat enum est utilisé pour décrire les formats courts ( LogFormat.Short ) et longs

( LogFormat.Long ).

CustomLoggerConfiguration.cs :

C#

using Microsoft.Extensions.Logging;

public class CustomLoggerConfiguration


{
public int EventId { get; set; }

public Dictionary<LogLevel, LogFormat> LogLevels { get; set; } =


new()
{
[LogLevel.Information] = LogFormat.Short,
[LogLevel.Warning] = LogFormat.Short,
[LogLevel.Error] = LogFormat.Long
};

public enum LogFormat


{
Short,
Long
}
}

Ajoutez l’enregistreur d’événements personnalisé suivant à l’application. Le


CustomLogger génère des formats de journal personnalisés en fonction des valeurs
logLevel définies dans la configuration précédente CustomLoggerConfiguration .

C#
using Microsoft.Extensions.Logging;
using static CustomLoggerConfiguration;

public sealed class CustomLogger : ILogger


{
private readonly string name;
private readonly Func<CustomLoggerConfiguration> getCurrentConfig;

public CustomLogger(
string name,
Func<CustomLoggerConfiguration> getCurrentConfig) =>
(this.name, this.getCurrentConfig) = (name, getCurrentConfig);

public IDisposable BeginScope<TState>(TState state) => default!;

public bool IsEnabled(LogLevel logLevel) =>


getCurrentConfig().LogLevels.ContainsKey(logLevel);

public void Log<TState>(


LogLevel logLevel,
EventId eventId,
TState state,
Exception? exception,
Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}

CustomLoggerConfiguration config = getCurrentConfig();

if (config.EventId == 0 || config.EventId == eventId.Id)


{
switch (config.LogLevels[logLevel])
{
case LogFormat.Short:
Console.WriteLine($"{name}: {formatter(state,
exception)}");
break;
case LogFormat.Long:
Console.WriteLine($"[{eventId.Id, 2}: {logLevel, -12}]
{name} - {formatter(state, exception)}");
break;
default:
// No-op
break;
}
}
}
}
Ajoutez le fournisseur d’enregistreurs d’événements personnalisé suivant à l’application.
CustomLoggerProvider adopte une Optionsapproche basée sur pour configurer

l’enregistreur d’événements via des fonctionnalités de configuration de journalisation


intégrées. Par exemple, l’application peut définir ou modifier les formats de journal via
un fichier appsettings.json sans nécessiter de modifications de code pour l’enregistreur
d’événements personnalisé, comme illustré à la fin de cette section.

CustomLoggerProvider.cs :

C#

using System.Collections.Concurrent;
using Microsoft.Extensions.Options;

[ProviderAlias("CustomLog")]
public sealed class CustomLoggerProvider : ILoggerProvider
{
private readonly IDisposable onChangeToken;
private CustomLoggerConfiguration config;
private readonly ConcurrentDictionary<string, CustomLogger> loggers =
new(StringComparer.OrdinalIgnoreCase);

public CustomLoggerProvider(
IOptionsMonitor<CustomLoggerConfiguration> config)
{
this.config = config.CurrentValue;
onChangeToken = config.OnChange(updatedConfig => this.config =
updatedConfig);
}

public ILogger CreateLogger(string categoryName) =>


loggers.GetOrAdd(categoryName, name => new CustomLogger(name,
GetCurrentConfig));

private CustomLoggerConfiguration GetCurrentConfig() => config;

public void Dispose()


{
loggers.Clear();
onChangeToken.Dispose();
}
}

Ajoutez les extensions d’enregistreur d’événements personnalisées suivantes.

CustomLoggerExtensions.cs :

C#
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;

public static class CustomLoggerExtensions


{
public static ILoggingBuilder AddCustomLogger(
this ILoggingBuilder builder)
{
builder.AddConfiguration();

builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<ILoggerProvider,
CustomLoggerProvider>());

LoggerProviderOptions.RegisterProviderOptions
<CustomLoggerConfiguration, CustomLoggerProvider>
(builder.Services);

return builder;
}
}

Dans le fichier Program sur le générateur d’hôte, effacez le fournisseur existant en


appelant ClearProviders et ajoutez le fournisseur de journalisation personnalisé :

C#

builder.Logging.ClearProviders().AddCustomLogger();

Dans le composant CustomLoggerExample suivant :

Le message de débogage n’est pas journalisé.


Le message d’information est journalisé au format court ( LogFormat.Short ).
Le message d’avertissement est journalisé au format court ( LogFormat.Short ).
Le message d’erreur est journalisé au format long ( LogFormat.Long ).
Le message de trace n’est pas journalisé.

CustomLoggerExample.razor :

razor

@page "/custom-logger-example"
@inject ILogger<CustomLoggerExample> Logger

<p>
<button @onclick="LogMessages">Log Messages</button>
</p>

@code{
private void LogMessages()
{
Logger.LogDebug(1, "This is a debug message.");
Logger.LogInformation(3, "This is an information message.");
Logger.LogWarning(5, "This is a warning message.");
Logger.LogError(7, "This is an error message.");
Logger.LogTrace(5!, "This is a trace message.");
}
}

La sortie suivante s’affiche dans la console des outils du développeur du navigateur


lorsque le bouton Log Messages est sélectionné. Les entrées de journal reflètent les
formats appropriés appliqués par l’enregistreur d’événements personnalisé (l’application
cliente est nommée LoggingTest ) :

LoggingTest.Pages.CustomLoggerExample: This is an information message.


LoggingTest.Pages.CustomLoggerExample: This is a warning message.
[ 7: Error ] LoggingTest.Pages.CustomLoggerExample - This is an error message.

D’après une simple inspection de l’exemple précédent, il est évident que la définition
des formats de ligne de journal via le dictionnaire en CustomLoggerConfiguration n’est
pas strictement nécessaire. Les formats de ligne appliqués par l’enregistreur
d’événements personnalisé ( CustomLogger ) ont pu être appliqués en vérifiant
simplement le logLevel dans la méthode Log . L’objectif de l’affectation du format de
journal via la configuration consiste à permettre au développeur de modifier facilement
le format du journal via la configuration de l’application, comme le montre l’exemple
suivant.

Dans l’application côté client, ajoutez ou mettez à jour le fichier appsettings.json pour
inclure la configuration de journalisation. Définissez le format de journal sur Long pour
les trois niveaux de journalisation :

JSON

{
"Logging": {
"CustomLog": {
"LogLevels": {
"Information": "Long",
"Warning": "Long",
"Error": "Long"
}
}
}
}

Dans l’exemple précédent, notez que l’entrée pour la configuration de l’enregistreur


d’événements personnalisé est CustomLog , qui a été appliquée au fournisseur
d’enregistreurs d’événements personnalisés ( CustomLoggerProvider ) en tant qu’alias
avec [ProviderAlias("CustomLog")] . La configuration de journalisation aurait pu être
appliquée avec le nom CustomLoggerProvider au lieu de CustomLog , mais l’utilisation de
l’alias CustomLog est plus conviviale.

Dans le fichier Program , la configuration de la journalisation est consommée. Ajoutez le


code suivant :

C#

builder.Logging.AddConfiguration(
builder.Configuration.GetSection("Logging"));

L’appel à LoggingBuilderConfigurationExtensions.AddConfiguration peut être placé


avant ou après l’ajout du fournisseur d’enregistreur d’événements personnalisé.

Réexécutez l’application. Sélectionnez le bouton Log Messages . Notez que la


configuration de journalisation est appliquée à partir du fichier appsettings.json . Les
trois entrées de journal sont au format long ( LogFormat.Long ) (l'application cliente est
nommée LoggingTest ) :

[ 3: Information ] LoggingTest.Pages.CustomLoggerExample - This is an information


message.
[ 5: Warning ] LoggingTest.Pages.CustomLoggerExample - This is a warning
message.
[ 7: Error ] LoggingTest.Pages.CustomLoggerExample - This is an error message.

Étendue du journal côté client


L’enregistreur d’événements de la console des outils du développeur ne prend pas en
charge les étendues de journal. Toutefois, un enregistreur d’événements personnalisé
peut prendre en charge les étendues de journal. Pour obtenir un exemple non pris en
charge que vous pouvez développer davantage en fonction de vos besoins, consultez
l’exemple d’application BlazorWebAssemblyScopesLogger dans le dotnet/blazor-
samplesréférentiel GitHub .
L’exemple d’application utilise la syntaxe de journalisation standard ASP.NET Core
BeginScope pour indiquer les étendues des messages journalisés. Le service Logger
dans l’exemple suivant est un ILogger<CustomLoggerExample> , qui est injecté dans le
composant CustomLoggerExample de l’application ( CustomLoggerExample.razor ).

C#

using (Logger.BeginScope("L1"))
{
Logger.LogInformation(3, "INFO: ONE scope.");
}

using (Logger.BeginScope("L1"))
{
using (Logger.BeginScope("L2"))
{
Logger.LogInformation(3, "INFO: TWO scopes.");
}
}

using (Logger.BeginScope("L1"))
{
using (Logger.BeginScope("L2"))
{
using (Logger.BeginScope("L3"))
{
Logger.LogInformation(3, "INFO: THREE scopes.");
}
}
}

Sortie :

[ 3: Information ] {CLASS} - INFO : UNE étendue. => L1


blazor.webassembly.js:1:35542
[ 3: Information ] {CLASS} - INFO : DEUX étendues. => L1 => L2
blazor.webassembly.js:1:35542
[ 3: Information ] {CLASS} - INFO : TROIS étendues. => L1 => L2 => L3

L’espace réservé {CLASS} dans l’exemple précédent est


BlazorWebAssemblyScopesLogger.Pages.CustomLoggerExample .

Journalisation des composants prédéfinis


Les composants prédéfinis exécutent deux fois le code d'initialisation du composant. La
journalisation a lieu côté serveur lors de la première exécution du code d’initialisation et
côté client lors de la deuxième exécution du code d’initialisation. En fonction de
l’objectif de journalisation pendant l’initialisation, la vérification des journaux côté
serveur, côté client ou les deux.

La journalisation du client SignalR avec le


générateur du client SignalR
Cette section s'applique aux applications côté serveur.

Dans la configuration de démarrage du script Blazor, passez l’objet de configuration


configureSignalR qui appelle configureLogging avec le niveau de journal.

Pour la valeur de niveau de journal configureLogging , transmettez l’argument en tant


que niveau de journal de chaîne ou entier indiqué dans le tableau suivant.

ノ Agrandir le tableau

LogLevel Paramètre de chaîne Paramètre entier

Trace trace 0

Debug debug 1

Information information 2

Warning warning 3

Error error 4

Critical critical 5

None none 6

Exemple 1 : Définissez le niveau de journal Information avec une valeur de chaîne.

Application webBlazor :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
circuit: {
configureSignalR: function (builder) {
builder.configureLogging("information");
}
}
});
</script>

Blazor Server :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
configureSignalR: function (builder) {
builder.configureLogging("information");
}
});
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script, consultez
ASP.NET Core Blazor structure du projet.

Exemple 2 : Définissez le niveau de journal Information avec une valeur entière.

Application webBlazor :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
circuit: {
configureSignalR: function (builder) {
builder.configureLogging("information");
}
}
});
</script>

Blazor Server :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
configureSignalR: function (builder) {
builder.configureLogging(2);
}
});
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script, consultez
ASP.NET Core Blazor structure du projet.

Pour plus d’informations sur le démarrage Blazor ( Blazor.start() ), consultez


Démarrage Blazor d’ASP.NET Core .

La journalisation du client SignalR avec la


configuration de l'application
Installez la configuration des paramètres d’application comme décrit dans Configuration
Blazor ASP.NET Core. Placez les fichiers de paramètres d’application dans wwwroot qui
contiennent un paramètre d’application Logging:LogLevel:HubConnection .

7 Notes

En guise d’alternative à l’utilisation des paramètres d’application, vous pouvez


passer le LogLevel comme argument à
LoggingBuilderExtensions.SetMinimumLevel lorsque la connexion au hub est
créée dans un composant Razor. Toutefois, le déploiement accidentel de
l’application dans un environnement d’hébergement de production avec une
journalisation détaillée peut entraîner une baisse de performances. Nous vous
recommandons d’utiliser les paramètres d’application pour définir le niveau de
journal.

Fournissez un paramètre d’application Logging:LogLevel:HubConnection dans le fichier


par défaut appsettings.json et dans le fichier des paramètres de l’application
d’environnement Development . Utilisez un niveau de journal moins détaillé classique
pour la valeur par défaut, par exemple LogLevel.Warning. La valeur par défaut des
paramètres d’application correspond à ce qui est utilisé dans les environnements
Staging et Production si aucun fichier de paramètres d’application pour ces

environnements n’est présent. Utilisez un niveau de journal détaillé dans le fichier de


paramètres de l’application d’environnement Development , tel que LogLevel.Trace.

wwwroot/appsettings.json :

JSON
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"HubConnection": "Warning"
}
}
}

wwwroot/appsettings.Development.json :

JSON

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"HubConnection": "Trace"
}
}
}

) Important

La configuration des fichiers de paramètres d’application précédents n’est utilisée


par l’application que si les directives de la configuration Blazor d’ASP.NET Core
sont respectées.

En haut du fichier de composant Razor ( .razor ) :

Injectez un ILoggerProvider pour ajouter un WebAssemblyConsoleLogger aux


fournisseurs de journalisation passés à HubConnectionBuilder. Contrairement à un
ConsoleLogger classique, WebAssemblyConsoleLogger est un wrapper autour des API
de journalisation spécifiques au navigateur (par exemple, console.log ). L’utilisation
de WebAssemblyConsoleLogger rend la journalisation possible dans Mono dans un
contexte de navigateur.
Injectez un IConfiguration pour lire le paramètre d’application
Logging:LogLevel:HubConnection .

7 Notes
WebAssemblyConsoleLogger est interne et n’est pas pris en charge pour une

utilisation directe dans le code du développeur.

C#

@inject ILoggerProvider LoggerProvider


@inject IConfiguration Config

7 Notes

L’exemple suivant est basé sur la démonstration dans le SignalR avec le didacticiel
Blazor. Pour plus d’informations, consultez le tutoriel.

Dans la OnInitializedAsyncméthode du composant, utilisez


HubConnectionBuilderExtensions.ConfigureLogging pour ajouter le fournisseur de
journalisation et définir le niveau de journalisation minimal à partir de la configuration :

C#

protected override async Task OnInitializedAsync()


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.ConfigureLogging(builder =>
{
builder.AddProvider(LoggerProvider);
builder.SetMinimumLevel(
Config.GetValue<LogLevel>
("Logging:LogLevel:HubConnection"));
})
.Build();

hubConnection.On<string, string>("ReceiveMessage", (user, message) =>


...

await hubConnection.StartAsync();
}

7 Notes

Dans l’exemple précédent, Navigation est un NavigationManager injecté.

Pour plus d’informations sur la configuration de l’environnement de l’application,


consultez la section Environnements Blazor ASP.NET Core.
Journalisation de l’authentification côté client
Journalisez les messages d’authentification Blazor au niveau de journalisation
LogLevel.Debug ou LogLevel.Trace avec une configuration de journalisation dans les
paramètres de l’application ou à l’aide d’un filtre de journal pour
Microsoft.AspNetCore.Components.WebAssembly.Authentication dans le fichier
Program .

Utilisez l’une des approches suivantes :

Dans un fichier de paramètres d’application (par exemple,


wwwroot/appsettings.Development.json ) :

JSON

"Logging": {
"LogLevel": {
"Microsoft.AspNetCore.Components.WebAssembly.Authentication":
"Debug"
}
}

Pour plus d’informations sur la configuration d’une application côté client pour lire
les fichiers de paramètres d’application, consultez Configuration Blazor ASP.NET
Core.

À l’aide d’un filtre de journal, l’exemple suivant :


Active la journalisation pour la configuration de build Debug à l’aide d’une
directive de préprocesseur C#.
Journalise les messages d’authentification Blazor au niveau du journal Debug.

C#

#if DEBUG
builder.Logging.AddFilter(
"Microsoft.AspNetCore.Components.WebAssembly.Authentication",
LogLevel.Debug);
#endif

7 Notes

Les composants Razor rendus sur le client ne se journalisent que sur la console des
outils de développement du navigateur côté client.
Ressources supplémentaires
Journalisation dans .NET Core et ASP.NET Core
Loglevel Énumération (documentation de l’API)
Implémenter un fournisseur de journalisation personnalisé dans .NET
Documentation des outils du développeur du navigateur :
Outils de développement de Chrome
Outils de développement de Firefox
Présentation des outils de développement de Microsoft Edge
Blazor exemples de dépôt GitHub (dotnet/blazor-samples)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Gérer les erreurs dans les applications
ASP.NET Core Blazor
Article • 11/02/2024

Cet article décrit comment Blazor gère les exceptions non prises en charge et comment
développer des applications qui détectent et gèrent les erreurs.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du Blazorréférentiel GitHub
d’exemples correspondant à la version .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Erreurs détaillées pendant le développement


Lorsqu’une application Blazor ne fonctionne pas correctement pendant le
développement, la réception d’informations détaillées sur les erreurs de l’application
aide à diagnostiquer et résoudre le problème. Lorsqu’une erreur se produit, les
applications Blazor affichent une barre jaune clair en bas de l’écran :

Pendant le développement, la barre vous dirige vers la console du navigateur, où


vous pouvez voir l’exception.
En production, la barre avertit l’utilisateur qu’une erreur s’est produite et
recommande d’actualiser le navigateur.

L’interface utilisateur de cette expérience de gestion des erreurs fait partie des modèles
de projet Blazor. Les versions des modèles de projet Blazor n’utilisent pas toutes
l’attribut data-nosnippet pour signaler aux navigateurs de ne pas mettre en cache le
contenu de l’interface utilisateur des erreurs, mais toutes les versions de la
documentation Blazor appliquent l’attribut.

Dans une application web Blazor , personnalisez l’expérience dans le composant


MainLayout . Étant donné que le Assistance de balise d’environnement (par exemple,

<environment include="Production">...</environment> ) n’est pas pris en charge dans les

composants Razor , l’exemple suivant injecte IHostEnvironment pour configurer des


messages d’erreur pour différents environnements.

En haut de MainLayout.razor :

razor

@inject IHostEnvironment HostEnvironment

Créez ou modifiez le balisage de l’interface utilisateur d’erreur Blazor :

razor

<div id="blazor-error-ui" data-nosnippet>


@if (HostEnvironment.IsProduction())
{
<span>An error has occurred.</span>
}
else
{
<span>An unhandled exception occurred.</span>
}
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

Dans une application Blazor WebAssembly, personnalisez l’expérience dans le fichier


wwwroot/index.html :

HTML

<div id="blazor-error-ui" data-nosnippet>


An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

L’élément blazor-error-ui est normalement masqué en raison de la présence du style


display: none de la classe CSS blazor-error-ui dans la feuille de style générée

automatiquement de l’application. Lorsqu’une erreur se produit, le framework applique


display: block à l’élément.

Gérer les exceptions interceptées en dehors du


cycle de vie d’un composant Razor
Utilisez ComponentBase.DispatchExceptionAsync dans un composant Razor pour traiter
les exceptions levées en dehors de la pile des appels de cycle de vie du composant. Cela
permet au code du composant de traiter les exceptions comme si elles sont des
exceptions de méthode de cycle de vie. Par la suite, les mécanismes de gestion des
erreurs de Blazor, tels que limites d’erreur, peuvent traiter les exceptions.

7 Notes

ComponentBase.DispatchExceptionAsync est utilisé dans les fichiers de


composants Razor ( .razor ) qui héritent de ComponentBase. Lors de la création de
composants qui implement IComponent directly, utilisez
RenderHandle.DispatchExceptionAsync.
Pour gérer les exceptions interceptées en dehors du cycle de vie d’un composant Razor,
transmettez l’exception à DispatchExceptionAsync et attendez le résultat :

C#

try
{
...
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}

Un scénario courant est si un composant souhaite démarrer une opération asynchrone,


mais n’attend pas de Task. Si l’opération échoue, vous souhaiterez peut-être quand
même que le composant traite l’échec comme exception de cycle de vie des
composants pour les objectifs suivants :

Placez le composant dans un état d’erreur, par exemple pour déclencher une limite
d’erreur .
Terminez le circuit en l’absence de limite d’erreur.
Déclenchez la même journalisation que pour les exceptions de cycle de vie.

Dans l’exemple suivant, l’utilisateur sélectionne le bouton Envoyer un rapport pour


déclencher une méthode en arrière-plan, ReportSender.SendAsync , qui envoie un
rapport. Dans la plupart des cas, un composant attend la Task d’un appel asynchrone et
met à jour l’interface utilisateur pour indiquer que l’opération s’est terminée. Dans
l’exemple suivant, la méthode SendReport n’attend pas de Task et ne signale pas le
résultat à l’utilisateur. Étant donné que le composant ignore intentionnellement la Task
dans SendReport , les défaillances asynchrones se produisent hors de la pile d’appels de
cycle de vie normale, par conséquent ne sont pas visibles par Blazor:

razor

<button @onclick="SendReport">Send report</button>

@code {
private void SendReport()
{
_ = ReportSender.SendAsync();
}
}
Pour traiter les défaillances telles que les exceptions de méthode de cycle de vie,
renvoyez explicitement des exceptions au composant avec DispatchExceptionAsync,
comme l’illustre l’exemple suivant :

razor

<button @onclick="SendReport">Send report</button>

@code {
private void SendReport()
{
_ = SendReportAsync();
}

private async Task SendReportAsync()


{
try
{
await ReportSender.SendAsync();
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
}
}

Pour une démonstration opérationnelle, implémentez l’exemple de notification du


minuteur dans Appeler des méthodes de composant en externe pour mettre à jour
l’état. Dans une application Blazor, ajoutez les fichiers suivants à partir de l’exemple de
notification du minuteur et inscrivez les services dans le fichier Program , comme
l’explique la section :

TimerService.cs

NotifierService.cs
Notifications.razor

L’exemple utilise un minuteur en dehors du cycle de vie d’un composant Razor, où une
exception non gérée n’est normalement pas traitée par les mécanismes de gestion des
erreurs de Blazor, tels qu’une limite d’erreur.

Tout d’abord, modifiez le code de TimerService.cs pour créer une exception artificielle
en dehors du cycle de vie du composant. Dans la boucle while de TimerService.cs ,
levez une exception lorsque le elapsedCount atteint la valeur de deux :

C#
if (elapsedCount == 2)
{
throw new Exception("I threw an exception! Somebody help me!");
}

Placez une limite d’erreur dans la disposition principale de l’application. Remplacez le


balisage <article>...</article> par le balisage suivant.

Dans MainLayout.razor :

razor

<article class="content px-4">


<ErrorBoundary>
<ChildContent>
@Body
</ChildContent>
<ErrorContent>
<p class="alert alert-danger" role="alert">
Oh, dear! Oh, my! - George Takei
</p>
</ErrorContent>
</ErrorBoundary>
</article>

Dans Blazor Web Apps avec la limite d’erreur appliquée uniquement à un composant
MainLayout statique, la limite est active uniquement pendant la phase de rendu statique

côté serveur (SSR statique). La limite ne s’active pas juste parce qu’un composant plus
bas dans la hiérarchie des composants est interactif. Pour activer l’interactivité à grande
échelle pour le composant MainLayout et le reste des composants situés plus bas dans
la hiérarchie des composants, activez le rendu interactif pour les instances de
composant HeadOutlet et Routes dans le composant App ( Components/App.razor ).
L’exemple suivant adopte le mode de rendu Serveur interactif ( InteractiveServer ) :

razor

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Si vous exécutez l’application à ce stade, l’exception est levée lorsque le nombre écoulé
atteint une valeur de deux. Toutefois, l’interface utilisateur ne change pas. La limite
d’erreur n’affiche pas le contenu de l’erreur.
Modifiez la méthode OnNotify du composant Notifications ( Notifications.razor ) :

Habillage de l’appel à ComponentBase.InvokeAsync dans un bloc try-catch .


Transmettez les Exception à DispatchExceptionAsync et attendez le résultat.

C#

public async Task OnNotify(string key, int value)


{
try
{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
}

Lorsque le service du minuteur s’exécute et atteint le nombre de deux, l’exception est


envoyée au composant Razor, ce qui déclenche à son tour la limite d’erreur pour
afficher le contenu de l’erreur du <ErrorBoundary> dans le composant MainLayout :

Oh, mon Dieu ! Mon Dieu ! - George Takei

Erreurs de circuit détaillées


Cette section s’applique à Blazor Web Apps fonctionnant sur un circuit.

Les erreurs côté client n’incluent pas la pile des appels et ne fournissent pas de détails
sur la cause de l’erreur, mais les journaux du serveur contiennent de telles informations.
À des fins de développement, des informations d’erreur de circuit sensibles peuvent être
mises à la disposition du client en activant les erreurs détaillées.

Affectez la valeur CircuitOptions.DetailedErrors à true . Pour plus d’informations et un


exemple, consultez le Guide pour ASP.NET Core BlazorSignalR.

Une alternative à la définition de CircuitOptions.DetailedErrors consiste à définir la clé


de configuration DetailedErrors sur true dans le fichier des paramètres
d’environnement de Development de l’application ( appsettings.Development.json ). En
outre, définissez la journalisation côté serveur SignalR ( Microsoft.AspNetCore.SignalR )
sur Déboguer ou Trace pour une journalisation détaillée SignalR.

appsettings.Development.json :

JSON

{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore.SignalR": "Debug"
}
}
}

La clé de configuration DetailedErrors peut également être définie sur true à l’aide de la
variable d’environnement ASPNETCORE_DETAILEDERRORS avec une valeur de true sur des
serveurs d’environnement de Development / Staging ou sur votre système local.

2 Avertissement

Évitez toujours d’exposer des informations d’erreur aux clients sur Internet, ce qui
constitue un risque de sécurité.

Erreurs détaillées pour le rendu côté serveur du


composant Razor
Cette section s’applique aux applications web Blazor.

Utilisez l’option RazorComponentsServiceOptions.DetailedErrors pour contrôler la


production d’informations détaillées sur les erreurs pour le rendu côté serveur du
composant Razor. La valeur par défaut est false .

L’exemple suivant active les erreurs détaillées :

C#

builder.Services.AddRazorComponents(options =>
options.DetailedErrors = builder.Environment.IsDevelopment());
2 Avertissement

Activez uniquement les erreurs détaillées dans l’environnement de Development .


Des erreurs détaillées peuvent contenir des informations sensibles sur l’application
que les utilisateurs malveillants peuvent utiliser dans une attaque.

L’exemple précédent fournit un degré de sécurité en définissant la valeur de


DetailedErrors en fonction de la valeur retournée par IsDevelopment. Lorsque
l’application se trouve dans l’environnement Development , DetailedErrors est
définie sur true . Cette approche n’est pas infaillible, car il est possible d’héberger
une application de production sur un serveur public dans l’environnement
Development .

Gérer les exceptions non prises en charge dans


le code du développeur
Pour qu’une application continue après une erreur, elle doit avoir une logique de
gestion des erreurs. Les sections ultérieures de cet article décrivent les sources
potentielles d’exceptions non prises en charge.

En production, ne restituez pas les messages d’exception de framework ou les traces de


pile dans l’interface utilisateur. Le rendu des messages d’exception ou des traces de pile
peut :

Divulguer des informations sensibles aux utilisateurs finaux.


Aider un utilisateur malveillant à découvrir les faiblesses d’une application qui
peuvent compromettre la sécurité de l’application, du serveur ou du réseau.

Exceptions non gérées pour les circuits


Cette section s’applique aux applications Web côté serveur fonctionnant sur un circuit.

Razor composants avec l’interactivité du serveur activée sont avec état sur le serveur.
Bien que les utilisateurs interagissent avec le composant sur le serveur, ils conservent
une connexion au serveur appelé circuit. Le circuit contient des instances de composants
actifs, ainsi que de nombreux autres aspects de l’état, comme :

La sortie rendue la plus récente des composants.


L’ensemble actuel de délégués de gestion des événements qui peuvent être
déclenchés par des événements côté client.
Si un utilisateur ouvre l’application dans plusieurs onglets de navigateur, il crée plusieurs
circuits indépendants.

Blazor traite la plupart des exceptions non prises en charge comme irrécupérables pour
le circuit où elles se produisent. Si un circuit est arrêté en raison d’une exception non
prise en charge, l’utilisateur peut uniquement continuer à interagir avec l’application en
rechargeant la page pour créer un nouveau circuit. Les circuits en dehors de celui qui est
terminé, qui sont des circuits pour d’autres utilisateurs ou d’autres onglets de
navigateur, ne sont pas affectés. Ce scénario est similaire à une application de bureau
qui se bloque. L’application plantée doit être redémarrée, mais les autres applications ne
sont pas affectées.

Le framework met fin à un circuit lorsqu’une exception non prise en charge se produit
pour les raisons suivantes :

Une exception non prise en charge laisse souvent le circuit dans un état non défini.
Le fonctionnement normal de l’application ne peut pas être garanti après une
exception non prise en charge.
Des vulnérabilités de sécurité peuvent apparaître dans l’application si le circuit
continue dans un état non défini.

Gestion globale des exceptions


Pour la gestion globale des exceptions, consultez les sections suivantes :

Limites d’erreur
Gestion globale des exceptions alternative

Limites d’erreur
Les limites d’erreur fournissent une approche pratique pour la gestion des exceptions. Le
composant ErrorBoundary :

Affiche son contenu enfant lorsqu’une erreur ne s’est pas produite.


Affiche l’interface utilisateur d’erreur lorsqu’une exception non prise en charge est
levée.

Pour définir une limite d’erreur, utilisez le composant ErrorBoundary pour encapsuler le
contenu existant. L’application continue de fonctionner normalement, mais la limite
d’erreur gère les exceptions non prises en charge.

razor
<ErrorBoundary>
...
</ErrorBoundary>

Pour implémenter une limite d’erreur de manière globale, ajoutez la limite autour du
contenu du corps du layout principal de l’application.

Dans MainLayout.razor :

razor

<article class="content px-4">


<ErrorBoundary>
@Body
</ErrorBoundary>
</article>

Dans Blazor Web Apps avec la limite d’erreur appliquée uniquement à un composant
MainLayout statique, la limite est active uniquement pendant la phase de rendu statique

côté serveur (SSR statique). La limite ne s’active pas juste parce qu’un composant plus
bas dans la hiérarchie des composants est interactif. Pour activer l’interactivité à grande
échelle pour le composant MainLayout et le reste des composants situés plus bas dans
la hiérarchie des composants, activez le rendu interactif pour les instances de
composant HeadOutlet et Routes dans le composant App ( Components/App.razor ).
L’exemple suivant adopte le mode de rendu Serveur interactif ( InteractiveServer ) :

razor

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Si vous préférez ne pas activer l’interactivité du serveur sur l’ensemble de l’application


depuis le composant Routes , placez la limite d’erreur plus bas dans la hiérarchie des
composants. Par exemple, placez la limite d’erreur autour du balisage dans les
composants individuels qui activent l’interactivité, et non dans le layout principal de
l’application. Les concepts importants à garder à l’esprit sont que chaque fois que la
limite d’erreur est placée :

Si la limite d’erreur n’est pas interactive, elle ne peut s’activer sur le serveur que
pendant le rendu statique. Par exemple, la limite peut s’activer lorsqu’une erreur
est envoyée dans une méthode de cycle de vie des composants.
Si la limite d’erreur est interactive, elle peut s’activer pour les composants de rendu
Serveur interactif inclus.

Prenons l’exemple suivant, où le composant Counter lève une exception si le nombre


est incrémenté au-delà de cinq.

Dans Counter.razor :

C#

private void IncrementCount()


{
currentCount++;

if (currentCount > 5)
{
throw new InvalidOperationException("Current count is too big!");
}
}

Si l’exception non prises en charge est levée pour un currentCount de plus de cinq :

L’erreur est enregistrée normalement ( System.InvalidOperationException: Current


count is too big! ).

L’exception est gérée par la limite d’erreur.


L’interface utilisateur d’erreur est affichée par la limite d’erreur avec le message
d’erreur par défaut suivant : An error has occurred.

Par défaut, le composant ErrorBoundary restitue un élément <div> vide avec la classe
CSS blazor-error-boundary pour son contenu d’erreur. Les couleurs, le texte et l’icône
de l’interface utilisateur par défaut sont définis à l’aide de CSS dans la feuille de style de
l’application dans le dossier wwwroot . Vous êtes donc libre de personnaliser l’interface
utilisateur d’erreur.

Modifiez le contenu d’erreur par défaut en définissant la propriété ErrorContent :

razor

<ErrorBoundary>
<ChildContent>
@Body
</ChildContent>
<ErrorContent>
<p class="errorUI">😈 A rotten gremlin got us. Sorry!</p>
</ErrorContent>
</ErrorBoundary>

Étant donné que la limite d’erreur est définie dans la disposition dans les exemples
précédents, l’interface utilisateur d’erreur est visible, quelle que soit la page vers laquelle
l’utilisateur accède après l’erreur. Dans la plupart des scénarios, nous vous
recommandons de définir étroitement les limites d’erreur. Si vous limitez largement une
limite d’erreur, vous pouvez la réinitialiser à un état non d’erreur sur les événements de
navigation de page suivants en appelant la méthode Recover de la limite d’erreur.

Dans MainLayout.razor :

Ajoutez un champ pour le ErrorBoundary à capturer une référence à celle-ci avec la


directive d’attribut @ref.
Dans la OnParameterSetméthode de cycle de vie, déclenchez une récupération sur
la limite d’erreur avec Recover.

razor

...

<ErrorBoundary @ref="errorBoundary">
@Body
</ErrorBoundary>

...

@code {
private ErrorBoundary? errorBoundary;

protected override void OnParametersSet()


{
errorBoundary?.Recover();
}
}

Pour éviter la boucle infinie où la récupération revient simplement à réactiver un


composant qui lève à nouveau l’erreur, n’appelez pas Recover de la logique de rendu.
Appelez uniquement Recover lorsque :

L’utilisateur effectue un mouvement d’interface utilisateur, par exemple en


sélectionnant un bouton pour indiquer qu’il souhaite réessayer une procédure ou
lorsque l’utilisateur accède à un nouveau composant.
Une logique supplémentaire efface également l’exception. Lorsque le composant
est remangé, l’erreur ne se reproduit pas.
Gestion globale des exceptions alternative
Une alternative à l’utilisation des limites d’erreur (ErrorBoundary) consiste à passer un
composant d’erreur personnalisé en tant que CascadingValue aux composants enfants.
L’un des avantages de l’utilisation d’un composant par rapport à l’utilisation d’un service
injecté ou d’une implémentation d’enregistreur d’événements personnalisé est qu’un
composant en cascade peut restituer du contenu et appliquer des styles CSS en cas
d’erreur.

L’exemple de composant Error suivant journalise simplement les erreurs, mais les
méthodes du composant peuvent traiter les erreurs de n’importe quelle façon requise
par l’application, notamment en utilisant plusieurs méthodes de traitement des erreurs.

Error.razor :

razor

@inject ILogger<Error> Logger

<CascadingValue Value="this">
@ChildContent
</CascadingValue>

@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }

public void ProcessError(Exception ex)


{
Logger.LogError("Error:ProcessError - Type: {Type} Message:
{Message}",
ex.GetType(), ex.Message);

// Call StateHasChanged if ProcessError directly participates in


// rendering. If ProcessError only logs or records the error,
// there's no need to call StateHasChanged.
//StateHasChanged();
}
}

7 Notes

Pour plus d’informations sur RenderFragment, consultez Composants ASP.NET


Core Razor.
Dans le composant Routes , encapsulez le composant Router ( <Router>...</Router> )
avec le composant Error . Cela permet au composant Error de descendre en cascade
vers n’importe quel composant de l’application où le composant Error est reçu en tant
que CascadingParameter.

Dans Routes.razor :

razor

<Error>
<Router ...>
...
</Router>
</Error>

Pour traiter les erreurs dans un composant :

Désignez le composant Error en tant que CascadingParameter dans le bloc


@code. Dans un exemple de composant Counter dans une application basée sur
un modèle de projet Blazor, ajoutez la propriété Error suivante :

C#

[CascadingParameter]
public Error? Error { get; set; }

Appelez une méthode de traitement des erreurs dans n’importe quel bloc catch
avec un type d’exception approprié. L’exemple de composant Error n’offre qu’une
seule méthode ProcessError , mais le composant de traitement des erreurs peut
fournir un nombre quelconque de méthodes de traitement des erreurs pour
répondre à d’autres exigences de traitement des erreurs dans l’application. Dans
l’exemple de composant Counter suivant, une exception est levée et interceptée
lorsque le nombre est supérieur à cinq :

razor

@code {
private int currentCount = 0;

[CascadingParameter]
public Error? Error { get; set; }

private void IncrementCount()


{
try
{
currentCount++;

if (currentCount > 5)
{
throw new InvalidOperationException("Current count is
over five!");
}
}
catch (Exception ex)
{
Error?.ProcessError(ex);
}
}
}

À l’aide du composant Error précédent avec les modifications apportées à un


composant Counter , la console des outils de développement du navigateur indique
l’erreur interceptée et journalisée :

Console

fail: {COMPONENT NAMESPACE}.Error[0]


Error:ProcessError - Type: System.InvalidOperationException Message: Current
count is over five!

Si la méthode ProcessError participe directement au rendu, comme l’affichage d’une


barre de messages d’erreur personnalisée ou la modification des styles CSS des
éléments rendus, appelez StateHasChanged à la fin de la méthode ProcessErrors pour
réactiver l’interface utilisateur.

Étant donné que les approches de cette section gèrent les erreurs avec une instruction
try-catch, la connexion d’une application SignalR entre le client et le serveur n’est pas
interrompue lorsqu’une erreur se produit, et le circuit reste actif. Les autres exceptions
non prises en charge restent irrécupérables pour un circuit. Pour plus d’informations,
consultez la section sur comment un circuit réagit aux exceptions non gérées.

Erreurs de journalisation avec un fournisseur


persistant
Si une exception non gérée se produit, l’exception est enregistrée dans les instances
ILogger configurées dans le conteneur de service. Par défaut, les applications Blazor
consignent dans la sortie de la console avec le fournisseur de journalisation de console.
Envisagez de vous connecter à un emplacement sur le serveur (ou l’API web principale
pour les applications côté client) avec un fournisseur qui gère la taille des journaux et la
rotation des journaux. L’application peut également utiliser un service de gestion des
performances des applications (APM), comme Azure Application Insights (Azure
Monitor).

7 Notes

Les fonctionnalités de Application Insights natives pour prendre en charge les


applications côté client et la prise en charge native de Blazor framework pour
Google Analytics peuvent devenir disponibles dans les futures versions de ces
technologies. Pour plus d’informations, consultez Prise en charge d’App Insights
dans Blazor WASM côté client (microsoft/ApplicationInsights-dotnet #2143) et
Analyse web et diagnostics (comprend des liens vers des implémentations de la
communauté) (dotnet/aspnetcore #5461) . En attendant, une application côté
client peut utiliser le SDK JavaScript Application Insights avec JSinteropérabilité
pour consigner des erreurs directement dans Application Insights à partir d’une
application côté client.

Pendant le développement dans une application Blazor fonctionnant sur un circuit,


l’application envoie généralement les détails complets des exceptions à la console du
navigateur pour faciliter le débogage. En production, les erreurs détaillées ne sont pas
envoyées aux clients, mais les détails complets d’une exception sont enregistrés sur le
serveur.

Vous devez déterminer quels incidents journaliser et le niveau de gravité des incidents
journalisés. Des utilisateurs hostiles pourraient être en mesure de déclencher des erreurs
délibérément. Par exemple, ne journalisez pas d’incident à partir d’une erreur où un
ProductId inconnu est fourni dans l’URL d’un composant qui affiche les détails du

produit. Toutes les erreurs ne doivent pas être traitées comme des incidents pour la
journalisation.

Pour plus d’informations, consultez les articles suivants :

Journalisation ASP.NET Core Blazor


Gérer les erreurs dans ASP.NET Core‡
Créer des API web avec ASP.NET Core

‡S’applique aux applications Blazor côté serveur et à d’autres applications ASP.NET Core
côté serveur qui sont des applications back-end d’API web pour Blazor. Les applications
côté client peuvent intercepter et envoyer des informations d’erreur sur le client à une
API web, qui enregistre les informations d’erreur sur un fournisseur de journalisation
persistant.
Emplacements où des erreurs peuvent se
produire
Le code de framework et d’application peut déclencher des exceptions non prises en
charge dans l’un des emplacements suivants, qui sont décrits plus en détail dans les
sections suivantes de cet article :

Instanciation de composant
Méthodes de cycle de vie
Logique de rendu
Gestionnaires d’événements
Mise au rebut des supports
Interopérabilité JavaScript
Prérendu

Instanciation de composant
Quand Blazor crée une instance d’un composant :

Le constructeur du composant est appelé.


Les constructeurs des services de DI fournis au constructeur du composant via la
directive @inject ou l’attribut [Inject] sont appelés.

Une erreur dans un constructeur ou un setter exécuté pour toute propriété [Inject]
entraîne une exception non prise en charge et empêche le framework d’instancier le
composant. Si l’application fonctionne sur un circuit, le circuit échoue. Si la logique du
constructeur peut lever des exceptions, l’application doit intercepter les exceptions à
l’aide d’une instruction try-catch avec gestion des erreurs et journalisation.

Méthodes de cycle de vie


Pendant la durée de vie d’un composant, Blazor appelle des méthodes de cycle de vie. Si
une méthode de cycle de vie lève une exception, de manière synchrone ou asynchrone,
l’exception est irrécupérable pour un circuit. Pour que les composants traitent les erreurs
dans les méthodes de cycle de vie, ajoutez une logique de gestion des erreurs.

Dans l’exemple suivant, où OnParametersSetAsync appelle une méthode pour obtenir


un produit :

Une exception levée dans la méthode ProductRepository.GetProductByIdAsync est


gérée par une instruction try-catch.
Lorsque le bloc catch est exécuté :
loadFailed est défini sur true , qui est utilisé pour afficher un message d’erreur

à l’utilisateur.
L’erreur est journalisée.

razor

@page "/product-details/{ProductId:int?}"
@inject ILogger<ProductDetails> Logger
@inject IProductRepository Product

<PageTitle>Product Details</PageTitle>

<h1>Product Details Example</h1>

@if (details != null)


{
<h2>@details.ProductName</h2>
<p>
@details.Description
<a href="@details.Url">Company Link</a>
</p>

}
else if (loadFailed)
{
<h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
<h1>Loading...</h1>
}

@code {
private ProductDetail? details;
private bool loadFailed;

[Parameter]
public int ProductId { get; set; }

protected override async Task OnParametersSetAsync()


{
try
{
loadFailed = false;
details = await Product.GetProductByIdAsync(ProductId);
}
catch (Exception ex)
{
loadFailed = true;
Logger.LogWarning(ex, "Failed to load product {ProductId}",
ProductId);
}
}

public class ProductDetail


{
public string? ProductName { get; set; }
public string? Description { get; set; }
public string? Url { get; set; }
}

/*
* Register the service in Program.cs:
* using static BlazorSample.Components.Pages.ProductDetails;
* builder.Services.AddScoped<IProductRepository, ProductRepository>();
*/

public interface IProductRepository


{
public Task<ProductDetail> GetProductByIdAsync(int id);
}

public class ProductRepository : IProductRepository


{
public Task<ProductDetail> GetProductByIdAsync(int id)
{
return Task.FromResult(
new ProductDetail()
{
ProductName = "Flowbee ",
Description = "The Revolutionary Haircutting System
You've Come to Love!",
Url = "https://flowbee.com/"
});
}
}
}

Logique de rendu
Le balisage déclaratif dans un fichier de composant Razor ( .razor ) est compilé dans une
méthode C# appelée BuildRenderTree. Lorsqu’un composant effectue un rendu,
BuildRenderTree exécute et génère une structure de données décrivant les éléments, le
texte et les composants enfants du composant rendu.

La logique de rendu peut lever une exception. Un exemple de ce scénario se produit


lorsque @someObject.PropertyName est évalué, mais que @someObject est null . Pour
Blazor applications fonctionnant sur un circuit, une exception non gérée levée par la
logique de rendu est irrécupérable pour le circuit de l’application.
Pour empêcher une NullReferenceException dans une logique de rendu, recherchez un
objet null avant d’accéder à ses membres. Dans l’exemple suivant, les propriétés
person.Address ne sont pas accessibles si person.Address est null :

razor

@if (person.Address != null)


{
<div>@person.Address.Line1</div>
<div>@person.Address.Line2</div>
<div>@person.Address.City</div>
<div>@person.Address.Country</div>
}

Le code précédent suppose que person n’est pas null . Souvent, la structure du code
garantit qu’un objet existe au moment où le composant est rendu. Dans ces cas, il n’est
pas nécessaire de vérifier null dans la logique de rendu. Dans l’exemple précédent,
l’existence de person peut être garantie, car person est créé lorsque le composant est
instancié, comme l’illustre l’exemple suivant :

razor

@code {
private Person person = new();

...
}

Gestionnaires d’événements
Le code côté client déclenche des appels de code C# lorsque des gestionnaires
d’événements sont créés avec :

@onclick

@onchange

D’autres attributs @on...


@bind

Le code du gestionnaire d’événements peut lever une exception non prise en charge
dans ces scénarios.

Si l’application appelle du code susceptible d’échouer pour des raisons externes,


interceptez les exceptions à l’aide d’une instruction try-catch avec gestion et
journalisation des erreurs.
Si un gestionnaire d’événements lève une exception non prise en charge (par exemple,
si une requête de base de données échoue) qui n’est pas interceptée et gérée par le
code du développeur :

Le framework consigne l’exception.


Dans une application Blazor fonctionnant sur un circuit, l’exception est
irrécupérable pour le circuit de l’application.

Mise au rebut des supports


Un composant peut être supprimé de l’interface utilisateur, par exemple parce que
l’utilisateur a accédé à une autre page. Lorsqu’un composant qui implémente
System.IDisposable est supprimé de l’interface utilisateur, le framework appelle la
méthode Dispose du composant.

Si la méthode Dispose du composant lève une exception non gérée dans une
application Blazor fonctionnant sur un circuit, l’exception est irrécupérable pour le circuit
de l’application.

Si la logique de suppression peut lever des exceptions, l’application doit intercepter les
exceptions à l’aide d’une instruction try-catch avec gestion des erreurs et journalisation.

Pour plus d’informations sur la suppression de composants, consultez Cycle de vie des
composants ASP.NET Core Razor.

Interopérabilité JavaScript
IJSRuntime est inscrit par le framework Blazor. IJSRuntime.InvokeAsync permet au code
.NET d’effectuer des appels asynchrones au runtime JavaScript (JS) dans le navigateur de
l’utilisateur.

Les conditions suivantes s’appliquent à la gestion des erreurs avec InvokeAsync :

Si un appel à InvokeAsync échoue de façon synchrone, une exception .NET se


produit. Un appel à InvokeAsync peut échouer, par exemple si les arguments
fournis ne peuvent pas être sérialisés. Le code du développeur doit intercepter
l’exception. Si le code d’application d’un gestionnaire d’événements ou d’une
méthode de cycle de vie de composant ne gère pas une exception dans une
application Blazor fonctionnant sur un circuit, l’exception résultante est
irrécupérable pour le circuit de l’application.
Si un appel à InvokeAsync échoue de façon asynchrone, la Task .NET échoue. Un
appel à InvokeAsync peut échouer, par exemple, car le code côté JS lève une
exception ou retourne une Promise qui s’est terminée en tant que rejected . Le
code du développeur doit intercepter l’exception. Si vous utilisez l’opérateur await,
envisagez d’encapsuler l’appel de méthode dans une instruction try-catch avec la
gestion des erreurs et la journalisation. Dans le cas contraire, dans une application
Blazor fonctionnant sur un circuit, le code défaillant entraîne une exception non
gérée irrécupérable pour le circuit de l’application.
Par défaut, les appels à InvokeAsync doivent se terminer dans un certain délai ou si
l’appel expire. La période d’expiration par défaut est d’une minute. Le délai
d’expiration protège le code contre la perte de connectivité réseau ou le code JS
qui ne renvoie jamais de message d’achèvement. Si l’appel expire, la
System.Threading.Tasks résultante échoue avec une OperationCanceledException.
Interceptez et traitez l’exception avec journalisation.

De même, le code JS peut lancer des appels aux méthodes .NET indiquées par l’attribut
[JSInvokable]. Si ces méthodes .NET lèvent une exception non prise en charge :

Dans une application Blazor fonctionnant sur un circuit, l’exception n’est pas
considérée comme irrécupérable pour le circuit de l’application.
La Promise côté JS est rejetée.

Vous avez la possibilité d’utiliser le code de gestion des erreurs côté .NET ou côté JS de
l’appel de méthode.

Pour plus d’informations, consultez les articles suivants :

Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core
Blazor
Appeler des méthodes .NET à partir de fonctions JavaScript dans ASP.NET Core
Blazor

Prérendu
Razor composants sont pré-affichés par défaut afin que leur balisage HTML rendu soit
retourné dans le cadre de la requête HTTP initiale de l’utilisateur.

Dans une application Blazor fonctionnant sur un circuit, le pré-affichage fonctionne par :

Création d’un nouveau circuit pour tous les composants prérendus qui font partie
de la même page.
Génération du HTML initial.
Traitement du circuit comme disconnected jusqu’à ce que le navigateur de
l’utilisateur établisse une connexion SignalR au même serveur. Une fois la
connexion établie, l’interactivité sur le circuit reprend et le balisage HTML des
composants est mis à jour.

Pour les composants côté client pré-affichés, le pré-affichage fonctionne par :

Générant le code HTML initial sur le serveur pour tous les composants prérendus
qui font partie de la même page.
Rendant le composant interactif sur le client une fois que le navigateur a chargé le
code compilé de l’application et le runtime .NET (s’il n’est pas déjà chargé) en
arrière-plan.

Si un composant lève une exception non prise en charge pendant le prérendu, par
exemple, au cours d’une méthode de cycle de vie ou dans une logique de rendu :

Dans une application Blazor fonctionnant sur un circuit, l’exception est


irrécupérable pour le circuit. Pour les composants côté client prédéfinis, l’exception
empêche le rendu du composant.
L’exception est levée dans la pile des appels à partir du ComponentTagHelper.

Dans des circonstances normales où le prérendu échoue, la poursuite de la génération


et du rendu du composant n’a pas de sens, car un composant de travail ne peut pas être
rendu.

Pour tolérer les erreurs qui peuvent se produire pendant le prérendu, la logique de
gestion des erreurs doit être placée à l’intérieur d’un composant qui peut lever des
exceptions. Utilisez des instructions try-catch avec la gestion et la journalisation des
erreurs. Au lieu d’encapsuler le ComponentTagHelper dans une instruction try-catch,
placez la logique de gestion des erreurs dans le composant rendu par le
ComponentTagHelper.

Scénarios avancés

Rendu récursif
Les composants peuvent être imbriqués récursivement. Cela est utile pour représenter
des structures de données récursives. Par exemple, un composant TreeNode peut
afficher davantage de composants TreeNode pour chacun des enfants du nœud.

Lors d’un rendu récursif, évitez de coder des modèles qui entraînent une récursivité
infinie :

Ne restituez pas de manière récursive une structure de données qui contient un


cycle. Par exemple, ne restituez pas un nœud d’arborescence dont les enfants
s’incluent eux-mêmes.
Ne créez pas de chaîne de dispositions qui contiennent un cycle. Par exemple, ne
créez pas de dispositions dont la disposition est elle-même.
N’autorisez pas un utilisateur final à enfreindre les invariants de récursion (règles)
par le biais d’une entrée de données malveillante ou d’appels d’interopérabilité
JavaScript.

Les boucles infinies pendant le rendu :

Entraînent la poursuite indéfinie du processus de rendu.


Équivalent à créer une boucle non déterminée.

Dans ces scénarios, le Blazor échoue et tente généralement de :

Consommer autant de temps processeur que le système d’exploitation le permet,


indéfiniment.
Consommer une quantité illimitée de mémoire. La consommation de mémoire
illimitée équivaut au scénario où une boucle non déterminée ajoute des entrées à
une collection à chaque itération.

Pour éviter les cas de récursivité infinie, assurez-vous que le code de rendu récursif
contient des conditions d’arrêt appropriées.

Logique d’arborescence de rendu personnalisée


La plupart des composants Razor sont implémentés en tant que fichiers de composants
Razor ( .razor ) et sont compilés par l’infrastructure pour produire une logique qui
fonctionne sur un RenderTreeBuilder pour afficher leur sortie. Toutefois, un développeur
peut implémenter manuellement une logique RenderTreeBuilder à l’aide de code C#
procédural. Pour plus d’informations, consultez Scénarios avancés ASP.NET Core Blazor
(construction d’arborescence de rendu).

2 Avertissement

L’utilisation de la logique de générateur d’arborescences de rendu manuel est


considérée comme un scénario avancé et dangereux, non recommandé pour le
développement de composants généraux.

Si du code RenderTreeBuilder est écrit, le développeur doit garantir son exactitude. Par
exemple, le développeur doit s’assurer que :

Les appels à OpenElement et CloseElement sont correctement équilibrés.


Les attributs sont ajoutés uniquement aux emplacements appropriés.

Une logique de générateur d’arborescences de rendu manuelle incorrecte peut


entraîner un comportement arbitraire non défini, notamment les blocages d’applications
ou de serveurs et les vulnérabilités de sécurité.

Considérez la logique de générateur d’arborescences de rendu manuel au même niveau


de complexité et avec le même niveau de danger que l’écriture manuelle de code
d’assembly ou d’instructions Microsoft Intermediate Language (MSIL).

Ressources supplémentaires
Journalisation ASP.NET Core Blazor
Gérer les erreurs dans ASP.NET Core†
Créer des API web avec ASP.NET Core
Blazor exemples de dépôt GitHub (dotnet/blazor-samples)

†S’applique aux applications d’API web ASP.NET Core que les applications Blazor côté
client utilisent pour la journalisation.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Conseils pour ASP.NET Core
BlazorSignalR
Article • 09/02/2024

Cet article explique comment configurer et gérer les connexions SignalR dans les
applications Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du Blazorréférentiel GitHub
d’exemples qui correspond à la version de .NET que vous ciblez. Pour le moment, tous
les exemples de la documentation ne figurent pas dans les exemples d’applications,
mais nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans
les exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant
du premier trimestre 2024.

Pour obtenir des conseils généraux sur la configuration d’ASP.NET Core SignalR,
consultez les rubriques de la zone Vue d’ensemble d’ASP.NET Core SignalR de la
documentation, en particulier Configuration d’ASP.NET Core SignalR.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.
Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples
d’applications BlazorSample_{PROJECT TYPE} à partir du Blazorréférentiel GitHub
d’exemples qui correspond à la version de .NET que vous ciblez. Pour le moment, tous
les exemples de la documentation ne figurent pas dans les exemples d’applications,
mais nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans
les exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant
du premier trimestre 2024.

Désactiver la compression des réponses pour le


rechargement à chaud
Lorsque vous utilisez le Rechargement à chaud, désactivez l’intergiciel de compression
de réponse dans l’environnement Development . Que le code par défaut d’un modèle de
projet soit utilisé ou non, appelez toujours UseResponseCompression en premier dans le
pipeline de traitement des requêtes.

Dans le fichier Program :

C#

if (!app.Environment.IsDevelopment())
{
app.UseResponseCompression();
}

Négociation cross-origin SignalR côté client


pour l’authentification
Cette section explique comment configurer le client sous-jacent de SignalR pour qu’il
envoie des informations d’identification, comme des cookie ou des en-têtes
d’authentification HTTP.

Utilisez SetBrowserRequestCredentials pour définir Include sur les requêtes fetch


cross-origin.

IncludeRequestCredentialsMessageHandler.cs :

C#

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Http;

public class IncludeRequestCredentialsMessageHandler : DelegatingHandler


{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{

request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
return base.SendAsync(request, cancellationToken);
}
}

Quand une connexion hub est générée, affectez HttpMessageHandler à l’option


HttpMessageHandlerFactory :

C#

private HubConnectionBuilder? hubConnection;

...

hubConnection = new HubConnectionBuilder()


.WithUrl(new Uri(Navigation.ToAbsoluteUri("/chathub")), options =>
{
options.HttpMessageHandlerFactory = innerHandler =>
new IncludeRequestCredentialsMessageHandler { InnerHandler =
innerHandler };
}).Build();

L’exemple précédent montre comment configure l’URL de connexion du hub à l’adresse


d’URI absolu à /chathub . L’URI peut également être défini via une chaîne, par exemple
https://signalr.example.com , ou via une configuration. Navigation est un

NavigationManager injecté.

Pour plus d’informations, consultez Configuration d’ASP.NET Core SignalR.

Rendu côté client


Si le prérendu est configuré, il se produit avant l’établissement de la connexion du client
au serveur. Pour plus d’informations, consultez Prévisualiser les composants ASP.NET
Core Razor.

Taille d’état prérendu et limite de taille du


message SignalR
Une grande taille d’état prédéfini peut dépasser la limite de taille du message du circuit
SignalR, ce qui entraîne les résultats suivants :

Le circuit SignalR ne parvient pas à s’initialiser avec une erreur sur le client : Circuit
host not initialized.
La boîte de dialogue de reconnexion sur le client s’affiche lors de l’échec du circuit.
La récupération n’est pas possible.

Pour résoudre le problème, utilisez l’une des approches suivantes :

Réduisez la quantité de données que vous placez dans l’état prérendu.


Augmentez la limite de taille des messages SignalR. AVERTISSEMENT :
l’augmentation de la limite peut augmenter le risque d’attaques par déni de
service (DoS).

Ressources côté client supplémentaires


Sécuriser un hub SignalR
Héberger et déployer ASP.NET Core Blazor WebAssembly
Vue d’ensemble d’ASP.NET Core SignalR
C ASP.NET Core SignalR
Blazor exemples de dépôt GitHub (dotnet/blazor-samples)

Utiliser des sessions persistantes pour


l’hébergement de batterie de serveurs côté
serveur
Une application Blazor effectue le prérendu en réponse à la première requête du client,
ce qui crée l’état de l’interface utilisateur sur le serveur. Lorsque le client tente de créer
une connexion SignalR, il doit se reconnecter au même serveur. Lorsque plusieurs
serveurs principaux sont en cours d’utilisation, l’application doit implémenter des
sessions persistantes pour les connexions SignalR.

7 Notes

L’erreur suivante est générée par une application qui n’a pas activé les sessions
persistantes dans une batterie de serveurs :
blazor.server.js:1 Erreur non interceptée (dans promise) : Appel annulé en
raison de la fermeture de la connexion sous-jacente.

Service Azure SignalR côté serveur


Nous vous recommandons d’utiliser le service Azure SignalR pour le développement
côté serveur hébergé dans Microsoft Azure. Le service fonctionne conjointement avec le
hub Blazor de l’application pour effectuer un scale-up d’une application côté serveur
vers un grand nombre de connexions SignalR simultanées. De plus, la portée générale et
les centres de données hautes performances du service SignalR contribuent de manière
significative à réduire la latence en raison de la zone géographique.

Les sessions permanentes sont activées pour Azure SignalR Service en définissant
l’option ou la valeur de configuration ServerStickyMode du service sur Required . Pour
plus d’informations, consultez Héberger et déployer des applications ASP.NET Core
Blazor côté serveur.

Options de gestionnaire de circuit côté serveur


Configurez le circuit avec les CircuitOptions indiqués dans le tableau suivant.

ノ Agrandir le tableau

Option Default Description

DetailedErrors false Envoyez des messages d’exception


détaillés à JavaScript lorsqu’une
exception non gérée se produit sur le
circuit ou lorsqu’un appel de méthode
.NET via l’interopérabilité JS entraîne
une exception.

DisconnectedCircuitMaxRetained 100 Nombre maximal de circuits


déconnectés que le serveur conserve
en mémoire à la fois.

DisconnectedCircuitRetentionPeriod 3 minutes Durée maximale pendant laquelle un


circuit déconnecté est conservé en
mémoire avant d’être détruit.

JSInteropDefaultCallTimeout 1 minute Durée maximale d’attente du serveur


avant l’expiration d’un appel de
Option Default Description

fonction JavaScript asynchrone.

MaxBufferedUnacknowledgedRenderBatches 10 Nombre maximal de lots de rendu


non connus que le serveur conserve
en mémoire par circuit à un moment
donné pour prendre en charge une
reconnexion robuste. Une fois la
limite atteinte, le serveur cesse de
produire de nouveaux lots de rendu
jusqu’à ce qu’un ou plusieurs lots
soient reconnus par le client.

Configurez les options dans le fichier Program avec un délégué d’options sur
AddInteractiveServerComponents. L’exemple suivant affecte les valeurs d’option par
défaut indiquées dans le tableau précédent.

Dans le fichier Program :

C#

builder.Services.AddRazorComponents().AddInteractiveServerComponents(options
=>
{
options.DetailedErrors = false;
options.DisconnectedCircuitMaxRetained = 100;
options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(3);
options.JSInteropDefaultCallTimeout = TimeSpan.FromMinutes(1);
options.MaxBufferedUnacknowledgedRenderBatches = 10;
});

Pour configurer le HubConnectionContext, utilisez HubConnectionContextOptions avec


AddHubOptions. Pour obtenir une description des options, consultez Configuration
d’ASP.NET Core SignalR. L’exemple suivant affecte les valeurs d’option par défaut.

Dans le fichier Program :

C#

builder.Services.AddRazorComponents().AddInteractiveServerComponents().AddHu
bOptions(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
options.EnableDetailedErrors = false;
options.HandshakeTimeout = TimeSpan.FromSeconds(15);
options.KeepAliveInterval = TimeSpan.FromSeconds(15);
options.MaximumParallelInvocationsPerClient = 1;
options.MaximumReceiveMessageSize = 32 * 1024;
options.StreamBufferCapacity = 10;
});

2 Avertissement

La valeur par défaut de MaximumReceiveMessageSize est 32 Ko. L’augmentation


de la valeur peut augmenter le risque d’attaques par déni de service (DoS).

Pour plus d’informations sur la gestion de la mémoire, consultez Héberger et déployer


des applications ASP.NET Core Blazor côté serveur.

Options hub Blazor


Configurez les options MapBlazorHub pour contrôler HttpConnectionDispatcherOptions
du hub Blazor :

AllowStatefulReconnects
ApplicationMaxBufferSize
AuthorizationData (Lecture seule)
CloseOnAuthenticationExpiration
LongPolling (Lecture seule)
MinimumProtocolVersion
TransportMaxBufferSize
Transports
TransportSendTimeout
WebSockets (Lecture seule)

Placez l’appel à app.MapBlazorHub après l’appel à app.MapRazorComponents dans le fichier


Program de l’application :

C#

app.MapBlazorHub(options =>
{
options.{OPTION} = {VALUE};
});

Dans l’exemple précédent, l’espace réservé {OPTION} est l’option et l’espace réservé
{VALUE} est la valeur.

Taille maximale du message de réception


Cette section s’applique uniquement aux projets qui implémentent SignalR.

La taille maximale des messages SignalR entrants autorisée pour les méthodes hub est
limitée par HubOptions.MaximumReceiveMessageSize (valeur par défaut : 32 Ko). Les
messages SignalR de taille supérieure à MaximumReceiveMessageSize génèrent une
erreur. Le framework n’impose pas de limite à la taille d’un message SignalR du hub vers
un client.

Lorsque la journalisation de SignalR n’est pas définie sur Débogage ou Trace, une erreur
de taille de message s’affiche uniquement dans la console des outils de développement
du navigateur :

Erreur : Connexion déconnectée avec l’erreur « Erreur : Le serveur a renvoyé une


erreur à la fermeture : connexion fermée avec une erreur. ».

Lorsque la journalisation côté serveur SignalR est définie sur Débogage ou Trace, la
journalisation côté serveur affiche InvalidDataException pour une erreur de taille de
message.

appsettings.Development.json :

JSON

{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
...
"Microsoft.AspNetCore.SignalR": "Debug"
}
}
}

Erreur :

System.IO.InvalidDataException : la taille maximale du message de 32768 octets a


été dépassée. La taille du message peut être configurée dans AddHubOptions.

Une approche consiste à augmenter la limite en définissant


MaximumReceiveMessageSize dans le fichier Program . L’exemple suivant définit la taille
maximale des messages de réception à 64 Ko :

C#
builder.Services.AddRazorComponents().AddInteractiveServerComponents()
.AddHubOptions(options => options.MaximumReceiveMessageSize = 64 *
1024);

L’augmentation de la taille des messages entrants SignalR est limitée par le coût
d’exiger davantage de ressources serveur et augmente le risque d’attaques par déni de
service (DoS). En outre, la lecture d’une grande quantité de contenu dans la mémoire
sous forme de chaînes ou de tableaux d’octets peut également entraîner des allocations
qui fonctionnent mal avec le récupérateur de mémoire, ce qui entraîne des pénalités de
performances supplémentaires.

Une meilleure option de lecture de charges utiles volumineuses consiste à envoyer le


contenu en blocs plus petits et à traiter la charge utile en tant que Stream. Cela peut
être utilisé lors de la lecture de charges utiles JavaScript (JS) interopérabilité JSON
volumineuses ou si les données d’interopérabilité JS sont disponibles sous forme
d’octets bruts. Pour obtenir un exemple illustrant l’envoi de charges utiles binaires
volumineuses dans des applications côté serveur à l’aide de techniques similaires au
composant InputFile, consultez l’exemple d’application Envoi binaire et l’exemple de
composant BlazorInputLargeTextArea .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Les formulaires qui traitent des charges utiles volumineuses sur SignalR peuvent
également utiliser l’interopérabilité JS de diffusion en continu directement. Pour plus
d’informations, consultez Appeler des fonctions JavaScript à partir de méthodes .NET
dans ASP.NET Core Blazor. Pour obtenir un exemple de formulaire qui diffuse des
<textarea> données vers le serveur, reportez-vous aux formulairesBlazor Résoudre les

problèmes ASP.NET Core.

Tenez compte des conseils suivants lors du développement de code qui transfère une
grande quantité de données :
Tirez parti de la prise en charge de l’interopérabilité JS de diffusion en continu
native pour transférer des données de taille supérieure à la limite de taille de
message entrant SignalR :
Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core
Blazor
Appeler des méthodes .NET à partir de fonctions JavaScript dans ASP.NET Core
Blazor
Exemple de charge utile de formulaire : Résoudre les problèmes liés aux
formulaires ASP.NET Core Blazor
Conseils généraux :
N’allouez pas d’objets volumineux dans JS et le code C#.
Libérez la mémoire consommée lorsque le processus est terminé ou annulé.
Appliquez les exigences supplémentaires suivantes à des fins de sécurité :
Déclarez la taille de fichier ou de données maximale qui peut être transmise.
Déclarez le taux de chargement minimal du client vers le serveur.
Une fois les données reçues par le serveur, les données peuvent être :
Stockées temporairement dans une mémoire tampon jusqu’à ce que tous les
segments soient collectés.
Consommées immédiatement. Par exemple, les données peuvent être
stockées immédiatement dans une base de données ou écrites sur le disque
à mesure que chaque segment est reçu.

Configuration de l’itinéraire du point de


terminaison hub côté serveur Blazor
Dans le fichier Program , appelez MapBlazorHub pour mapper le BlazorHub au chemin
d’accès par défaut de l’application. Le script Blazor ( blazor.*.js ) pointe
automatiquement vers le point de terminaison créé par MapBlazorHub.

Refléter l’état de connexion côté serveur dans


l’interface utilisateur
Lorsque le client détecte que la connexion a été perdue, une interface utilisateur par
défaut s’affiche à l’utilisateur pendant que le client tente de se reconnecter. Si la
reconnexion échoue, l’utilisateur a la possibilité de réessayer.

Pour personnaliser l’interface utilisateur, définissez un élément unique avec un id de


components-reconnect-modal . L’exemple suivant place l’élément dans le composant App .
App.razor :

CSHTML

<div id="components-reconnect-modal">
There was a problem with the connection!
</div>

7 Notes

Si plusieurs éléments avec un id de components-reconnect-modal sont rendus par


l’application, seul le premier élément rendu reçoit des modifications de classe CSS
pour afficher ou masquer l’élément.

Ajoutez les styles CSS suivants à la feuille de style du site.

wwwroot/app.css :

css

#components-reconnect-modal {
display: none;
}

#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
display: block;
}

Le tableau suivant décrit les classes CSS appliquées à l’élément components-reconnect-


modal par le framework Blazor.

ノ Agrandir le tableau

Classe CSS Indique...

components- Une connexion perdue. Le client tente de se reconnecter. Affiche le modal.


reconnect-show

components- Une connexion active est rétablie avec le serveur. Masque le modal.
reconnect-hide

components- Échec de la reconnexion, probablement en raison d’une défaillance réseau.


reconnect- Pour tenter une reconnexion, appelez window.Blazor.reconnect() en
JavaScript.
failed
Classe CSS Indique...

components- La reconnexion a été rejetée. Le serveur a été atteint, mais a refusé la


reconnect- connexion, et l’état de l’utilisateur sur le serveur est perdu. Pour recharger
rejected l’application, appelez location.reload() en JavaScript. Cet état de connexion
peut se produire lorsque :

Un incident dans le circuit côté serveur se produit.


Le client est déconnecté suffisamment longtemps pour que le serveur
supprime l’état de l’utilisateur. Les instances des composants de
l’utilisateur sont supprimées.
Le serveur est redémarré ou le processus de travail de l’application est
recyclé.

Personnalisez le délai avant que l’affichage de reconnexion s’affiche en définissant la


propriété transition-delay dans le CSS du site pour l’élément modal. L’exemple suivant
modifie le délai de transition de 500 ms (par défaut) à 1 000 ms (1 seconde).

wwwroot/app.css :

css

#components-reconnect-modal {
transition: visibility 0s linear 1000ms;
}

Pour afficher la tentative de reconnexion actuelle, définissez un élément avec un id de


components-reconnect-current-attempt . Pour afficher le nombre maximal de nouvelles

tentatives de reconnexion, définissez un élément avec un id de components-reconnect-


max-retries . L’exemple suivant place ces éléments à l’intérieur d’un élément modal de

tentative de reconnexion à la suite de l’exemple précédent.

CSHTML

<div id="components-reconnect-modal">
There was a problem with the connection!
(Current reconnect attempt:
<span id="components-reconnect-current-attempt"></span> /
<span id="components-reconnect-max-retries"></span>)
</div>

Lorsque la fenêtre modale de reconnexion personnalisée s’affiche, elle affiche un


contenu similaire à ce qui suit en fonction du code précédent :

HTML
There was a problem with the connection! (Current reconnect attempt: 3 / 8)

Rendu côté serveur


Par défaut, les composants sont prérendus sur le serveur avant l’établissement de la
connexion cliente au serveur. Pour plus d’informations, consultez Prévisualiser les
composants ASP.NET Core Razor.

Surveiller l’activité du circuit côté serveur


Surveillez l’activité du circuit entrant à l’aide de la méthode
CreateInboundActivityHandler sur CircuitHandler. L’activité du circuit entrant est toute
activité envoyée du navigateur au serveur, comme les événements d’interface utilisateur
ou les appels d’interopérabilité JavaScript-to-.NET.

Par exemple, vous pouvez utiliser un gestionnaire d’activités de circuit pour détecter si le
client est inactif :

C#

public sealed class IdleCircuitHandler : CircuitHandler, IDisposable


{
readonly Timer timer;
readonly ILogger logger;

public IdleCircuitHandler(IOptions<IdleCircuitOptions> options,


ILogger<IdleCircuitHandler> logger)
{
timer = new Timer();
timer.Interval = options.Value.IdleTimeout.TotalMilliseconds;
timer.AutoReset = false;
timer.Elapsed += CircuitIdle;
this.logger = logger;
}

private void CircuitIdle(object? sender, System.Timers.ElapsedEventArgs


e)
{
logger.LogInformation("{Circuit} is idle", nameof(CircuitIdle));
}

public override Func<CircuitInboundActivityContext, Task>


CreateInboundActivityHandler(
Func<CircuitInboundActivityContext, Task> next)
{
return context =>
{
timer.Stop();
timer.Start();
return next(context);
};
}

public void Dispose()


{
timer.Dispose();
}
}

public class IdleCircuitOptions


{
public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(5);
}

public static class IdleCircuitHandlerServiceCollectionExtensions


{
public static IServiceCollection AddIdleCircuitHandler(
this IServiceCollection services,
Action<IdleCircuitOptions> configureOptions)
{
services.Configure(configureOptions);
services.AddIdleCircuitHandler();
return services;
}

public static IServiceCollection AddIdleCircuitHandler(


this IServiceCollection services)
{
services.AddScoped<CircuitHandler, IdleCircuitHandler>();
return services;
}
}

Les gestionnaires d’activités de circuit proposent également une approche pour accéder
aux services Blazor délimités à partir d’autres étendues d’injection de dépendances non
Blazor. Pour plus d’informations et d’exemples, consultez :

Injection de dépendances ASP.NET Core Blazor


Autres scénarios de sécurité ASP.NET Core Blazor côté serveur

Démarrage de Blazor
Configurez le démarrage manuel du circuit SignalR d’une application Blazordans le
fichier App.razor d’une application Web Blazor :
Ajoutez un attribut autostart="false" à la balise <script> pour le script
blazor.*.js .

Placez un script qui appelle Blazor.start() après le chargement du script Blazor


et à l’intérieur de la balise fermante </body> .

Quand autostart est désactivé, tout aspect de l’application qui ne dépend pas du
circuit fonctionne normalement. Par exemple, le routage côté client est opérationnel.
Toutefois, tout aspect qui dépend du circuit n’est pas opérationnel tant que
Blazor.start() n’est pas appelé. Le comportement de l’application est imprévisible sans

circuit établi. Par exemple, les méthodes de composant ne parviennent pas à s’exécuter
lorsque le circuit est déconnecté.

Pour plus d’informations, notamment la façon d’initialiser Blazor lorsque le document


est prêt et comment chaîner à un JS Promise , consultez Démarrage ASP.NET Core
Blazor.

Configurer les délais d’expiration SignalR et


Keep-Alive sur le client
Configurez les valeurs suivantes pour le client :

withServerTimeout : configure le délai d’attente du serveur en millisecondes. Si ce

délai d’attente s’écoule sans réception de messages du serveur, la connexion se


termine avec une erreur. La valeur de délai d'attente par défaut est de
30 secondes. Le délai d’expiration du serveur doit être au moins le double de la
valeur affectée à l’intervalle Keep-Alive ( withKeepAliveInterval ).
withKeepAliveInterval : configure l’intervalle Keep-Alive en millisecondes

(intervalle par défaut auquel effectuer un test ping sur le serveur). Ce paramètre
permet au serveur de détecter les déconnexions matérielles, par exemple lorsqu’un
client débranche son ordinateur du réseau. Le ping se produit au maximum aussi
souvent que le serveur envoie des pings. Si le serveur envoie un ping toutes les
cinq secondes, l’affectation d’une valeur inférieure à 5000 (5 secondes) correspond
à un test ping toutes les cinq secondes. La valeur par défaut est de 15 secondes.
L’intervalle Keep-Alive doit être inférieur ou égal à la moitié de la valeur affectée au
délai d’expiration du serveur ( withServerTimeout ).

L’exemple suivant pour le fichier App.razor (application web Blazor) affiche l’affectation
de valeurs par défaut.

Application webBlazor :
HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
circuit: {
configureSignalR: function (builder) {
builder.withServerTimeout(30000).withKeepAliveInterval(15000);
}
}
});
</script>

L’exemple suivant pour le fichier Pages/_Host.cshtml (Blazor Server, toutes les versions
sauf ASP.NET Core dans .NET 6) ou le fichier Pages/_Layout.cshtml (Blazor Server,
ASP.NET Core dans .NET 6).

Blazor Server:

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
configureSignalR: function (builder) {
builder.withServerTimeout(30000).withKeepAliveInterval(15000);
});
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script et le chemin
d’accès à utiliser, consultez Structure de projet Blazor ASP.NET Core.

Lors de la création d’une connexion hub dans un composant, définissez ServerTimeout


(valeur par défaut : 30 secondes) et KeepAliveInterval (valeur par défaut : 15 secondes)
sur le HubConnectionBuilder. Définissez HandshakeTimeout (valeur par défaut : 15
secondes) au niveau de la HubConnection générée. L’exemple suivant montre
l’affectation de valeurs par défaut :

C#

protected override async Task OnInitializedAsync()


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.WithServerTimeout(TimeSpan.FromSeconds(30))
.WithKeepAliveInterval(TimeSpan.FromSeconds(15))
.Build();
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);

hubConnection.On<string, string>("ReceiveMessage", (user, message) =>


...

await hubConnection.StartAsync();
}

Lors de la modification des valeurs du délai d’expiration du serveur (ServerTimeout) ou


de l’intervalle Keep-Alive (KeepAliveInterval :

Le délai d’expiration du serveur doit être au moins le double de la valeur affectée à


l’intervalle Keep-Alive.
L’intervalle Keep-Alive doit être inférieur ou égal à la moitié de la valeur affectée au
délai d’expiration du serveur.

Pour plus d’informations, consultez les sections Déploiement global et échecs de


connexion des articles suivants :

Héberger et déployer des applications Blazor ASP.NET Core côté serveur


Héberger et déployer ASP.NET Core Blazor WebAssembly

Modifier le gestionnaire de reconnexion côté


serveur
Les événements de reconnexion de circuit du gestionnaire de reconnexion peuvent être
modifiés pour des comportements personnalisés, par exemple :

Pour avertir l’utilisateur si la connexion est supprimée.


Pour effectuer la journalisation (à partir du client) lorsqu’un circuit est connecté.

Pour modifier les événements de connexion, inscrivez des rappels pour les modifications
de connexion suivantes :

Les connexions supprimées utilisent onConnectionDown .


Les connexions établies/rétablies utilisent onConnectionUp .

onConnectionDown et onConnectionUp doivent tous deux être spécifiés.

Application webBlazor :

HTML
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
circuit: {
reconnectionHandler: {
onConnectionDown: (options, error) => console.error(error),
onConnectionUp: () => console.log("Up, up, and away!")
}
}
});
</script>

Blazor Server:

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
reconnectionHandler: {
onConnectionDown: (options, error) => console.error(error),
onConnectionUp: () => console.log("Up, up, and away!")
}
});
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script et le chemin
d’accès à utiliser, consultez Structure de projet Blazor ASP.NET Core.

Actualiser automatiquement la page en cas d’échec de


reconnexion côté serveur
Le comportement de reconnexion par défaut nécessite que l’utilisateur effectue une
action manuelle pour actualiser la page après l’échec de la reconnexion. Toutefois, un
gestionnaire de reconnexion personnalisé peut être utilisé pour actualiser
automatiquement la page :

App.razor :

HTML

<div id="reconnect-modal" style="display: none;"></div>


<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script src="boot.js"></script>
Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script et le chemin
d’accès à utiliser, consultez Structure de projet Blazor ASP.NET Core.

Créez le fichier wwwroot/boot.js suivant.

Application webBlazor :

JavaScript

(() => {
const maximumRetryCount = 3;
const retryIntervalMilliseconds = 5000;
const reconnectModal = document.getElementById('reconnect-modal');

const startReconnectionProcess = () => {


reconnectModal.style.display = 'block';

let isCanceled = false;

(async () => {
for (let i = 0; i < maximumRetryCount; i++) {
reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of
${maximumRetryCount}`;

await new Promise(resolve => setTimeout(resolve,


retryIntervalMilliseconds));

if (isCanceled) {
return;
}

try {
const result = await Blazor.reconnect();
if (!result) {
// The server was reached, but the connection was rejected;
reload the page.
location.reload();
return;
}

// Successfully reconnected to the server.


return;
} catch {
// Didn't reach the server; try again.
}
}

// Retried too many times; reload the page.


location.reload();
})();

return {
cancel: () => {
isCanceled = true;
reconnectModal.style.display = 'none';
},
};
};

let currentReconnectionProcess = null;

Blazor.start({
circuit: {
reconnectionHandler: {
onConnectionDown: () => currentReconnectionProcess ??=
startReconnectionProcess(),
onConnectionUp: () => {
currentReconnectionProcess?.cancel();
currentReconnectionProcess = null;
}
}
}
});
})();

Blazor Server :

JavaScript

(() => {
const maximumRetryCount = 3;
const retryIntervalMilliseconds = 5000;
const reconnectModal = document.getElementById('reconnect-modal');

const startReconnectionProcess = () => {


reconnectModal.style.display = 'block';

let isCanceled = false;

(async () => {
for (let i = 0; i < maximumRetryCount; i++) {
reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of
${maximumRetryCount}`;

await new Promise(resolve => setTimeout(resolve,


retryIntervalMilliseconds));

if (isCanceled) {
return;
}

try {
const result = await Blazor.reconnect();
if (!result) {
// The server was reached, but the connection was rejected;
reload the page.
location.reload();
return;
}

// Successfully reconnected to the server.


return;
} catch {
// Didn't reach the server; try again.
}
}

// Retried too many times; reload the page.


location.reload();
})();

return {
cancel: () => {
isCanceled = true;
reconnectModal.style.display = 'none';
},
};
};

let currentReconnectionProcess = null;

Blazor.start({
reconnectionHandler: {
onConnectionDown: () => currentReconnectionProcess ??=
startReconnectionProcess(),
onConnectionUp: () => {
currentReconnectionProcess?.cancel();
currentReconnectionProcess = null;
}
}
});
})();

Pour plus d’informations sur le démarrage de Blazor, consultez Démarrage d’ASP.NET


Core Blazor.

Ajuster le nombre et l’intervalle des nouvelles


tentatives de reconnexion côté serveur
Pour ajuster le nombre et l’intervalle des nouvelles tentatives de reconnexion, définissez
le nombre de nouvelles tentatives ( maxRetries ) et la période autorisée en millisecondes
pour chaque nouvelle tentative ( retryIntervalMilliseconds ).

Application webBlazor :
HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
circuit: {
reconnectionOptions: {
maxRetries: 3,
retryIntervalMilliseconds: 2000
}
}
});
</script>

Blazor Server :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>


<script>
Blazor.start({
reconnectionOptions: {
maxRetries: 3,
retryIntervalMilliseconds: 2000
}
});
</script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script et le chemin
d’accès à utiliser, consultez Structure de projet Blazor ASP.NET Core.

Pour plus d’informations sur le démarrage de Blazor, consultez Démarrage d’ASP.NET


Core Blazor.

Déconnecter le circuit Blazor du client


Par défaut, un circuit Blazor est déconnecté lorsque l’événement de page unload est
déclenché. Pour déconnecter le circuit pour d’autres scénarios sur le client, appelez
Blazor.disconnect dans le gestionnaire d’événements approprié. Dans l’exemple

suivant, le circuit est déconnecté lorsque la page est masquée (événement pagehide ):

JavaScript

window.addEventListener('pagehide', () => {
Blazor.disconnect();
});

Pour plus d’informations sur le démarrage de Blazor, consultez Démarrage d’ASP.NET


Core Blazor.

Gestionnaire de circuit côté serveur


Vous pouvez définir un gestionnaire de circuit, ce qui permet d’exécuter du code sur les
modifications apportées à l’état du circuit d’un utilisateur. Un gestionnaire de circuit est
implémenté en dérivant de CircuitHandler et en inscrivant la classe dans le conteneur de
service de l’application. L’exemple suivant d’un gestionnaire de circuit effectue le suivi
des connexions SignalR ouvertes.

TrackingCircuitHandler.cs :

C#

using Microsoft.AspNetCore.Components.Server.Circuits;

public class TrackingCircuitHandler : CircuitHandler


{
private HashSet<Circuit> circuits = new();

public override Task OnConnectionUpAsync(Circuit circuit,


CancellationToken cancellationToken)
{
circuits.Add(circuit);

return Task.CompletedTask;
}

public override Task OnConnectionDownAsync(Circuit circuit,


CancellationToken cancellationToken)
{
circuits.Remove(circuit);

return Task.CompletedTask;
}

public int ConnectedCircuits => circuits.Count;


}

Les gestionnaires de circuit sont inscrits par DI. Les instances délimitées sont créées par
instance d’un circuit. À l’aide de TrackingCircuitHandler dans l’exemple précédent, un
service singleton est créé, car l’état de tous les circuits doit être suivi.

Dans le fichier Program :


C#

builder.Services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Si les méthodes d’un gestionnaire de circuit personnalisé lèvent une exception non prise
en charge, l’exception est irrécupérable pour le circuit. Pour tolérer des exceptions dans
le code ou les méthodes appelées d’un gestionnaire, encapsulez le code dans une ou
plusieurs instructions try-catch avec gestion des erreurs et journalisation.

Lorsqu’un circuit se termine parce qu’un utilisateur s’est déconnecté et que le


framework nettoie l’état du circuit, le framework supprime l’étendue de DI du circuit. La
suppression de l’étendue supprime tous les services de DI à l’étendue du circuit qui
implémentent System.IDisposable. Si un service de DI lève une exception non prise en
charge pendant la suppression, le framework journalise l’exception. Pour plus
d’informations, consultez Injection de dépendances Blazor ASP.NET Core.

Gestionnaire de circuit côté serveur pour


capturer des utilisateurs des services
personnalisés
Utilisez un CircuitHandler pour capturer un utilisateur à partir du
AuthenticationStateProvider et définissez cet utilisateur dans un service. Pour obtenir
des informations supplémentaires et des exemples de code, consultez la section Autres
scénarios de sécurité ASP.NET Core Blazor côté serveur.

Fermeture des circuits quand il ne reste plus de


composants de serveur interactif
Les composants de serveur interactif gèrent les événements de l’interface utilisateur web
en utilisant une connexion en temps réel avec le navigateur, appelée un circuit. Un
circuit et son état associé sont créés quand un composant de serveur interactif racine
est rendu. Le circuit est fermé quand il ne reste plus de composants de serveur interactif
sur la page, ce qui libère des ressources du serveur.

IHttpContextAccessor / HttpContext dans les


composants Razor
IHttpContextAccessor doit être évité avec le rendu interactif, car il n’existe pas de
HttpContext valide disponible.

IHttpContextAccessor peut être utilisé pour les composants rendus statiquement sur le
serveur. Toutefois, nous vous recommandons de l’éviter si possible.

HttpContext peut être utilisé comme paramètre en cascade uniquement dans les
composants racines rendus statiquement pour les tâches générales, telles que l’inspection
et la modification d’en-têtes ou d’autres propriétés dans le composant App
( Components/App.razor ). La valeur est toujours null pour le rendu interactif.

C#

[CascadingParameter]
public HttpContext? HttpContext { get; set; }

Pour les scénarios où HttpContext est requis dans les composants interactifs, nous vous
recommandons de transmettre les données via l’état du composant persistant à partir
du serveur. Pour plus d’informations, consultez Autres scénarios de sécurité ASP.NET
Core Blazor côté serveur.

Ressources côté serveur supplémentaires


Conseils sur l’hébergement et le déploiement côté serveur : configuration SignalR
Vue d’ensemble d’ASP.NET Core SignalR
C ASP.NET Core SignalR
Documentation sur la sécurité côté serveur
Authentification et autorisation avec ASP.NET Core Blazor
Applications Blazor sécurisées côté serveur ASP.NET Core
Conseils d’atténuation des menaces pour le rendu interactif côté serveur de
Blazor ASP.NET Core
Autres scénarios de sécurité ASP.NET Core Blazor côté serveur
Événements de reconnexion côté serveur et événements de cycle de vie des
composants
Qu’est-ce qu’Azure SignalR Service ?
Guide des performances pour Azure SignalR Service
Publier une application ASP.NET Core SignalR sur Azure
Blazor échantillons de référentiel GitHub (dotnet/blazor-samples)
6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Fichiers statiques ASP.NET Core Blazor
Article • 09/02/2024

Cet article décrit la configuration d’application Blazor pour servir des fichiers statiques.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du dépôt GitHub d’exemples
Blazor qui correspond à la version de .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Mode projet d’une ressource web statique


Cette section s’applique au projet .Client d’une application web Blazor.

Le paramètre requis <StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>


dans le projet .Client d’une application web Blazor rétablit les comportements de
ressources statiques Blazor WebAssembly par défaut afin que le projet se comporte
comme partie intégrante du projet hébergé. Le Kit de développement logiciel (SDK)
Blazor WebAssembly ( Microsoft.NET.Sdk.BlazorWebAssembly ) configure les ressources
web statiques d’une manière spécifique pour fonctionner en mode « autonome » avec
un serveur qui consomme simplement les sorties de la bibliothèque. Cette action n’est
pas appropriée pour une application web Blazor, où la partie WebAssembly de
l’application constitue une partie logique de l’hôte et doit se comporter davantage
comme une bibliothèque. Par exemple, le projet n’expose pas le bundle de styles (par
exemple BlazorSample.Client.styles.css ) et fournit uniquement l’hôte à la place avec
le bundle de projet afin que l’hôte puisse l’inclure dans son propre bundle de styles.

Middleware de fichiers statiques


Cette section s’applique aux applications Blazor côté serveur.

Configurez l’Intergiciel de fichiers statiques pour délivrer des ressources statiques aux
clients en appelant UseStaticFiles dans le pipeline de traitement des requêtes de
l’application. Pour plus d’informations, consultez Fichiers statiques dans ASP.NET Core.

Fichiers statiques dans des environnements


non Development
Cette section s’applique aux fichiers statiques côté serveur.

Lors de l’exécution locale d’applications, les ressources web statiques sont uniquement
activées par défaut dans l’environnement Development. Pour activer les fichiers
statiques pour des environnements autres que Development pendant le développement
et le test locaux (par exemple, Staging), appelez UseStaticWebAssets sur
WebApplicationBuilder dans le fichier Program .
2 Avertissement

Appelez UseStaticWebAssets pour l’environnement exact pour empêcher


l’activation de la fonctionnalité en production, car elle délivre des fichiers provenant
d’emplacements distincts sur le disque autres que le projet si elle est appelée dans
un environnement de production. L’exemple de cette section vérifie
l’environnement Staging en appelant IsStaging.

C#

if (builder.Environment.IsStaging())
{
builder.WebHost.UseStaticWebAssets();
}

Préfixe pour les ressources Blazor


WebAssembly
Cette section s’applique aux applications web Blazor.

Utilisez l’option WebAssemblyComponentsEndpointOptions.PathPrefix le point de


terminaison pour définir la chaîne de chemin qui indique le préfixe des ressources Blazor
WebAssembly. Le chemin d’accès doit correspondre à un projet d’application référencé
Blazor WebAssembly.

C#

endpoints.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode(options =>
options.PathPrefix = "{PATH PREFIX}");

Dans l’exemple précédent, l’espace réservé {PATH PREFIX} est le préfixe du chemin
d’accès et doit commencer par une barre oblique ( / ).

Dans l’exemple suivant, le préfixe de chemin d’accès est défini sur /path-prefix :

C#

endpoints.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode(options =>
options.PathPrefix = "/path-prefix");
Chemin d’accès de base des ressources web
statiques
Cette section s’applique aux applications Blazor WebAssembly autonomes.

Par défaut, la publication de l’application place les ressources statiques de l’application,


y compris les fichiers d’infrastructure Blazor (ressources de dossier _framework ), au
niveau du chemin d’accès racine ( / ) dans la sortie publiée. La propriété
<StaticWebAssetBasePath> spécifiée dans le Fichier projet ( .csproj ) définit le chemin

d’accès de base à un chemin d’accès non racine :

XML

<PropertyGroup>
<StaticWebAssetBasePath>{PATH}</StaticWebAssetBasePath>
</PropertyGroup>

Dans l’exemple précédent, l’espace réservé {PATH} est le chemin d’accès.

Sans définir la propriété <StaticWebAssetBasePath> , une application autonome est


publiée à /BlazorStandaloneSample/bin/Release/{TFM}/publish/wwwroot/ .

Dans l’exemple précédent, l’espace réservé {TFM} est le Moniker du framework cible
(TFM) (par exemple, net6.0 ).

Si la propriété <StaticWebAssetBasePath> dans une application Blazor WebAssembly


autonome définit le chemin d’accès à la ressource statique publiée sur app1 , le chemin
racine de l’application dans la sortie publiée est /app1 .

Dans le fichier projet de l’application Blazor WebAssembly autonome ( .csproj ) :

XML

<PropertyGroup>
<StaticWebAssetBasePath>app1</StaticWebAssetBasePath>
</PropertyGroup>

Dans la sortie publiée, le chemin d’accès à l’application Blazor WebAssembly autonome


est /BlazorStandaloneSample/bin/Release/{TFM}/publish/wwwroot/app1/ .

Dans l’exemple précédent, l’espace réservé {TFM} est le Moniker du framework cible
(TFM) (par exemple, net6.0 ).
Mappages de fichiers et options de fichier
statiques
Cette section s’applique aux fichiers statiques côté serveur.

Pour créer des mappages de fichiers supplémentaires avec un


FileExtensionContentTypeProvider ou configurer d’autres StaticFileOptions, utilisez l’une
des approches suivantes. Dans les exemples suivants, l’espace réservé {EXTENSION} est
l’extension de fichier et l’espace réservé {CONTENT TYPE} est le type de contenu.

Configurez les options via l’injection de dépendances (DI) dans le fichier Program à
l’aide de StaticFileOptions :

C#

using Microsoft.AspNetCore.StaticFiles;

...

var provider = new FileExtensionContentTypeProvider();


provider.Mappings["{EXTENSION}"] = "{CONTENT TYPE}";

builder.Services.Configure<StaticFileOptions>(options =>
{
options.ContentTypeProvider = provider;
});

Cette approche configure le même fournisseur de fichiers que celui utilisé pour
traiter le script Blazor. Assurez-vous que votre configuration personnalisée
n’interfère pas avec le service du script Blazor. Par exemple, ne supprimez pas le
mappage pour les fichiers JavaScript en configurant le fournisseur avec
provider.Mappings.Remove(".js") .

Utilisez deux appels de UseStaticFiles dans le fichier Program :


Configurez le fournisseur de fichiers personnalisé dans le premier appel avec
StaticFileOptions.
Le deuxième intergiciel délivre le script Blazor, qui utilise la configuration de
fichiers statiques par défaut fournie par l’infrastructure Blazor.

C#

using Microsoft.AspNetCore.StaticFiles;

...
var provider = new FileExtensionContentTypeProvider();
provider.Mappings["{EXTENSION}"] = "{CONTENT TYPE}";

app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider =


provider });
app.UseStaticFiles();

Vous pouvez éviter d’interférer avec le service _framework/blazor.web.js en


utilisant MapWhen pour exécuter un Intergiciel de fichier statique personnalisé :

C#

app.MapWhen(ctx => !ctx.Request.Path


.StartsWithSegments("/_framework/blazor.web.js"),
subApp => subApp.UseStaticFiles(new StaticFileOptions() { ...
}));

Ressources supplémentaires
Chemin de base de l’application

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Composants Razor ASP.NET Core
Article • 09/02/2024

Cet article explique comment créer et utiliser des composants Razor dans des
applications Blazor et fournit des conseils sur la syntaxe Razor, le nommage de
composants, les espaces de noms et les paramètres de composant.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du dépôt GitHub d’exemples
Blazor qui correspond à la version de .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Composants Razor
Les applications Blazor sont créées en utilisant Razor composants de , communément
appelés composants deBlazor ou uniquement composants. Un composant est une partie
autonome de l’interface utilisateur (IU), avec une logique de traitement pour prendre en
charge le comportement dynamique. Les composants peuvent être imbriqués, réutilisés,
partagés entre plusieurs projets et utilisés dans des applications MVC et Razor Pages.

Les composants s’affichent dans une représentation en mémoire du modèle DOM


(Document Object Model) du navigateur, appelée arborescence de rendu, qui permet
de mettre à jour l’IU de manière flexible et efficace.

Classes de composant
Les composants sont implémentés en utilisant une combinaison de balises C# et HTML
dans des fichiers de composants Razor avec l’extension de fichier .razor .

Par défaut, ComponentBase est la classe de base pour les composants décrits par les
fichiers de composants Razor. ComponentBase implémente l’abstraction la plus faible
des composants, l’interface IComponent. ComponentBase définit des propriétés et des
méthodes de composant pour les fonctionnalités de base, par exemple pour traiter un
ensemble d’événements de cycle de vie de composant intégrés.

ComponentBase dans la dotnet/aspnetcore source de référence : la source de


référence contient des remarques supplémentaires sur les événements de cycle de vie
intégrés. Toutefois, gardez à l’esprit que les implémentations internes des
fonctionnalités de composant sont susceptibles d’être modifiées à tout moment sans
préavis.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Les développeurs créent Razor généralement des composants à partir de fichiers de


composants Razor ( .razor ) ou basent leurs composants sur ComponentBase, mais les
composants peuvent également être générés en implémentant IComponent. Les
composants créés par le développeur qui implémentent IComponent peuvent prendre
un contrôle de bas niveau sur le rendu au prix de devoir déclencher manuellement le
rendu avec des événements et des méthodes de cycle de vie que le développeur doit
créer et gérer.

Syntaxe Razor
Les composants suivent la syntaxe Razor. Deux fonctionnalités Razor sont largement
utilisées par les composants : les directives et les attributs de directive. Il s’agit de mots
clés réservés, préfixés par @ , qui apparaissent dans le balisage Razor :

Directives : modifient le mode d’analyse ou de fonctionnement du balisage d’un


composant. Par exemple, la directive @page spécifie un composant routable avec
un modèle de routage accessible directement sur demande d’un utilisateur dans le
navigateur à une URL spécifique.

Par convention, les directives d’un composant en haut d’une définition de


composant (fichier .razor ) sont placées dans un ordre cohérent. Pour les
directives répétées, les directives sont placées par ordre alphabétique par espace
de noms ou par type, à l’exception des directives @using , qui ont un ordre spécial
de second niveau.

L’ordre suivant est adopté par des exemples d’applications et de documentation


Blazor. Les composants fournis par un modèle de projet Blazor peuvent différer de
l’ordre suivant et utiliser un autre format. Par exemple, les composants Identity du
framework Blazorincluent des lignes vides entre des blocs de directives @using et
des blocs de directives @inject . Vous êtes libre d’utiliser un format et un schéma
de classement personnalisé dans vos propres applications.

Documentation et exemple d’ordre de directive d’application Razor :


@page
@rendermode (.NET 8 ou version ultérieure)

@using

Espaces de noms de System (ordre alphabétique)


Espaces de noms de Microsoft (ordre alphabétique)
Espaces de noms d’API tiers (ordre alphabétique)
Espaces de noms d’application (ordre alphabétique)
Autres directives (ordre alphabétique)

Aucune ligne vide n’apparaît parmi les directives. Une ligne vide apparaît entre les
directives et la première ligne du balisage Razor.

Exemple :

razor

@page "/doctor-who-episodes/{season:int}"
@rendermode InteractiveWebAssembly
@using System.Globalization
@using System.Text.Json
@using Microsoft.AspNetCore.Localization
@using Mandrill
@using BlazorSample.Components.Layout
@attribute [Authorize]
@implements IAsyncDisposable
@inject IJSRuntime JS
@inject ILogger<DoctorWhoEpisodes> Logger

<PageTitle>Doctor Who Episode List</PageTitle>

...

Attributs de directive : modifient le mode d’analyse ou de fonctionnement d’un


élément de composant.

Exemple :

razor

<input @bind="episodeId" />

Les directives et attributs de directive utilisés dans des composants sont expliqués plus
loin dans cet article et dans d’autres articles de la documentation Blazor. Pour obtenir
une description générale de la syntaxe Razor, consultez les informations de référence
sur la syntaxe Razor pour ASP.NET Core.

Nom du composant, nom de classe et espace de noms


Le nom d’un composant doit commencer par une majuscule :
✔️ProductDetail.razor

❌ productDetail.razor

Parmi les conventions de nommage Blazor couramment utilisées dans la documentation


Blazor, citons les suivantes :

Les chemins d’accès aux fichiers et les noms de fichiers utilisent la casse Pascal† et
apparaissent avant d’afficher des exemples de code. Si un chemin d’accès est
présent, il indique l’emplacement du dossier classique. Par exemple,
Components/Pages/ProductDetail.razor indique que le composant ProductDetail a

pour nom de fichier ProductDetail.razor et réside dans le dossier Pages du


dossier Components de l’application.
Les chemins aux fichiers de composants routables correspondent à leurs URL dans
le kebab case ‡ avec des tirets apparaissant entre les mots dans le modèle de
routage d’un composant. Par exemple, un composant ProductDetail avec le
modèle de routage /product-detail ( @page "/product-detail" ) est demandé dans
un navigateur à l’URL relative /product-detail .

†La casse Pascal (casse mixte avec majuscules) est une convention de nommage sans
espaces ni ponctuation où la première lettre de chaque mot est en majuscule, y compris
le premier mot.
La casse kebab est une convention d’affectation de noms sans espaces et ponctuation
qui utilise des lettres minuscules et des tirets entre les mots.

Les composants sont des classes C# ordinaires que vous pouvez placer n’importe où
dans un projet. Les composants qui produisent des pages web résident généralement
dans le dossier Components/Pages . Les composants autres que des pages sont
fréquemment placés dans le dossier Components ou dans un dossier personnalisé ajouté
au projet.

En général, l’espace de noms d’un composant est dérivé de l’espace de noms racine de
l’application et de l’emplacement (dossier) du composant dans l’application. Si l’espace
de noms racine de l’application est BlazorSample et que le composant Counter réside
dans le dossier Components/Pages :

L’espace de noms du composant Counter est BlazorSample.Components.Pages .


Le nom de type complet du composant est
BlazorSample.Components.Pages.Counter .

Pour les dossiers personnalisés contenant des composants, ajoutez une directive @using
au composant parent ou au fichier _Imports.razor de l’application. L’exemple suivant
rend les composants du dossier AdminComponents disponibles :

razor

@using BlazorSample.AdminComponents

7 Notes

Les directives @using dans le fichier _Imports.razor ne s’appliquent qu’aux fichiers


Razor ( .razor ), pas aux fichiers C# ( .cs ).

Les instructions alias using sont prises en charge. Dans l’exemple suivant, la classe
publique WeatherForecast du composant GridRendering est rendue disponible comme
WeatherForecast dans un composant situé ailleurs dans l’application :

razor

@using WeatherForecast = Components.Pages.GridRendering.WeatherForecast

Vous pouvez également référencer des composants avec leurs noms complets, ce qui ne
nécessite pas de directive @using. L’exemple suivant référence directement le
composant ProductDetail dans le dossier AdminComponents/Pages de l’application :

razor

<BlazorSample.AdminComponents.Pages.ProductDetail />

L’espace de noms d’un composant créé avec Razor est basé sur les éléments suivants
(par ordre de priorité) :

Directive @namespace dans le balisage du fichier Razor (par exemple, @namespace


BlazorSample.CustomNamespace ).

RootNamespace du projet dans le fichier de projet (par exemple,

<RootNamespace>BlazorSample</RootNamespace> ).
Les Espace de noms du projet et chemin d’accès de la racine du projet au
composant. Par exemple, le framework résout {PROJECT
NAMESPACE}/Components/Pages/Home.razor avec l’espace de noms de projet

BlazorSample en espace de noms BlazorSample.Components.Pages pour le

composant Home . {PROJECT NAMESPACE} est l’espace de noms du projet. Les


composants suivent les règles de liaison de nom C#. Pour le composant Home de
cet exemple, les composants dans l’étendue sont tous les composants :
Dans le même dossier, Components/Pages .
Composants à la racine du projet qui ne spécifient pas explicitement un autre
espace de noms.

Les éléments suivants ne sont pas pris en charge :

Qualification global::.
Noms partiellement qualifiés. Par exemple, vous ne pouvez pas ajouter @using
BlazorSample.Components à un composant, puis référencer le composant NavMenu

dans le dossier Components/Layout de l’application


( Components/Layout/NavMenu.razor ) avec <Layout.NavMenu></Layout.NavMenu> .

Prise en charge de classes partielles


Les composants sont générés en tant que classes partielles C# et sont créés à l’aide de
l’une des approches suivantes :

Un seul fichier contient du code C# défini dans un ou plusieurs blocs @code, un


balisage HTML et un balisage Razor. Les modèles de projet Blazor définissent leurs
composants à l’aide de cette approche monofichier.
Les balisages HTML et Razor sont placés dans un fichier Razor ( .razor ). Le
code C# est placé dans un fichier code-behind défini en tant que classe partielle
( .cs ).

7 Notes

Une feuille de style de composant qui définit des styles spécifiques aux composants
est un fichier distinct ( .css ). L’isolation CSS Blazor est décrite plus loin dans
Isolation CSS dans Blazor ASP.NET Core.

L’exemple suivant montre le composant Counter par défaut avec un bloc @code dans
une application générée à partir d’un modèle de projet Blazor. Le balisage et le code C#
sont dans le même fichier. Il s’agit de l’approche la plus couramment adoptée pour
créer des composants.

Counter.razor :

razor
@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Le composant Counter suivant sépare le balisage de la présentation HTML et le Razor


du code C# en utilisant un fichier code-behind avec une classe partielle. Le
fractionnement du balisage du code C# est privilégié par certaines organisations et
développeurs pour organiser leur code de composant en fonction de leur mode de
travail. Par exemple, l’expert en interface utilisateur de l’organisation peut travailler sur la
couche de présentation indépendamment d’un autre développeur sui travaille sur la
logique C# du composant. L’approche est également utile lors de l’utilisation de
générateurs de code ou de sources générés automatiquement. Pour plus d’informations,
consultez Classes et méthodes partielles (Guide de programmation C#).

CounterPartialClass.razor :

razor

@page "/counter-partial-class"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

CounterPartialClass.razor.cs :

C#
namespace BlazorSample.Components.Pages;

public partial class CounterPartialClass


{
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Les directives @using dans le fichier _Imports.razor ne s’appliquent qu’aux fichiers


Razor ( .razor ), pas aux fichiers C# ( .cs ). Ajoutez des espaces de noms à un fichier de
classe partielle en fonction des besoins.

Espaces de noms standard utilisés par les composants :

C#

using System.Net.Http;
using System.Net.Http.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Sections
using Microsoft.AspNetCore.Components.Web;
using static Microsoft.AspNetCore.Components.Web.RenderMode;
using Microsoft.AspNetCore.Components.Web.Virtualization;
using Microsoft.JSInterop;

Les espaces de noms standard incluent également l’espace de noms de l’application et


l’espace de noms correspondant au dossier Components de l’application :

C#

using BlazorSample;
using BlazorSample.Components;

Des dossiers supplémentaires peuvent également être inclus, tels que le dossier Layout :

razor

using BlazorSample.Components.Layout;
Spécifier une classe de base
La directive @inherits est utilisée pour spécifier la classe de base d’un composant.
Contrairement à l’utilisation de classes partielles, qui fractionne uniquement le balisage
de la logique C#, l’utilisation d’une classe de base vous permet d’hériter du code C#
pour une utilisation dans un groupe de composants qui partagent les propriétés et
méthodes de la classe de base. L’utilisation de classes de base réduit la redondance du
code dans les applications et est utile lorsque vous fournissez du code de base des
bibliothèques de classes à plusieurs applications. Pour plus d’informations, consultez
Héritage en C# et .NET.

Dans l’exemple suivant, la classe de base BlazorRocksBase1 dérive de ComponentBase.

BlazorRocks1.razor :

razor

@page "/blazor-rocks-1"
@inherits BlazorRocksBase1

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 1</h1>

<p>
@BlazorRocksText
</p>

BlazorRocksBase1.cs :

C#

using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase1 : ComponentBase


{
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
}

Routage
Pour router dans Blazor, un modèle de routage est fourni à chaque composant
accessible dans l’application avec une directive @page. Quand un fichier Razor avec une
directive @page est compilé, la classe générée reçoit un RouteAttribute spécifiant le
modèle de routage. Au moment de l’exécution, le routeur recherche les classes de
composant avec un RouteAttribute et génère le rendu du composant dont le modèle de
routage correspond à l’URL demandée.

Le composant suivant HelloWorld utilise un modèle d’itinéraire de /hello-world , et la


page web rendue pour le composant est atteinte à l’URL /hello-world relative.

HelloWorld.razor :

razor

@page "/hello-world"

<PageTitle>Hello World!</PageTitle>

<h1>Hello World!</h1>

Le composant précédent se charge dans le navigateur à l’adresse /hello-world , que


vous ajoutiez ou non le composant à la navigation de l’interface utilisateur de
l’application. Si vous le souhaitez, vous pouvez ajouter un composant au composant
NavMenu pour faire apparaître un lien vers le composant dans la navigation basée sur
l’interface utilisateur de l’application.

Pour le composant HelloWorld précédent, vous pouvez ajouter un composant NavLink


au composant NavMenu . Pour obtenir plus d’informations, notamment les descriptions
des composants NavLink et NavMenu , consultez Routage et navigation dans Blazor
ASP.NET Core.

balisage
L’interface utilisateur d’un composant est définie à l’aide de la syntaxe Razor, qui
comprend le balisage Razor et du code C# et HTML. Quand une application est
compilée, le balisage HTML et la logique de rendu C# sont convertis en classe de
composant. Le nom de la classe générée correspond au nom du fichier.

Les membres de la classe de composant sont définis dans un ou plusieurs blocs @code.
Dans les blocs @code, l’état du composant est spécifié et traité avec C# :

Initialiseurs de propriété et de champ.


Valeurs de paramètre à partir d’arguments passés par les composants parents et
les paramètres de routage.
Méthodes de gestion des événements utilisateur, des événements de cycle de vie
et de la logique des composants personnalisés.

Les membres d’un composant sont utilisés dans la logique de rendu au moyen
d’expressions C# qui commencent par le symbole @ . Par exemple, un champ C# est
rendu en préfixant @ au nom du champ. Le composant Markup suivant évalue ce qui suit
et en génère le rendu :

headingFontStyle pour la valeur de propriété CSS font-style de l’élément de titre.

headingText pour le contenu de l’élément de titre.

Markup.razor :

razor

@page "/markup"

<PageTitle>Markup</PageTitle>

<h1>Markup Example</h1>

<h2 style="font-style:@headingFontStyle">@headingText</h2>

@code {
private string headingFontStyle = "italic";
private string headingText = "Put on your new Blazor!";
}

7 Notes

Les exemples fournis dans la documentation Blazor spécifient le modificateur


d’accès private pour les membres privés. Les membres privés sont délimités à la
classe d’un composant. Cependant, C# opte pour le modificateur d’accès private
quand aucun modificateur d’accès n’est présent. Vous n’êtes donc pas obligé de
marquer explicitement les membres avec « private ». Pour plus d’informations sur
les modificateurs d’accès, consultez Modificateurs d’accès (Guide de
programmation C#).

Le framework Blazor traite un composant en interne comme une arborescence de


rendu , qui est la combinaison du DOM et du modèle de l’objet CSSOM (Cascading
Style Sheet Object Model) . Après le rendu initial du composant, son arborescence de
rendu est regénérée en réponse aux événements. Blazor compare la nouvelle
arborescence de rendu à la précédente et applique toute modification apportée au
modèle DOM du navigateur pour affichage. Pour plus d’informations, consultez le rendu
de composants Razor ASP.NET Core.

Dans la syntaxe Razor, les structures de contrôle, directives et attributs de directive C#


sont en minuscules (exemples : @if, @code, @bind). Les noms de propriété sont en
majuscules (exemple : @Body pour LayoutComponentBase.Body).

Les méthodes asynchrones ( async ) ne prennent pas en


charge void en retour
Le framework Blazor ne suit pas les méthodes asynchrones retournant void ( async ). Par
conséquent, les exceptions ne sont pas interceptées si void est retourné. Retournez
toujours une Task à partir de méthodes asynchrones.

Composants imbriqués
Les composants peuvent inclure d’autres composants en les déclarant avec la syntaxe
HTML. Le balisage pour l’utilisation d’un composant ressemble à une balise HTML où le
nom de la balise est le type du composant.

Considérez le composant Heading suivant, qui peut être utilisé par d’autres composants
pour afficher un titre.

Heading.razor :

razor

<h1 style="font-style:@headingFontStyle">Heading Example</h1>

@code {
private string headingFontStyle = "italic";
}

Le balisage suivant dans le composant HeadingExample génère le rendu du composant


Heading précédent à l’emplacement où la balise <Heading /> apparaît.

HeadingExample.razor :

razor

@page "/heading-example"

<PageTitle>Heading</PageTitle>
<h1>Heading Example</h1>

<Heading />

Si un composant contient un élément HTML avec une première lettre majuscule qui ne
correspond pas à un nom de composant dans le même espace de noms, un
avertissement est émis indiquant que l’élément a un nom inattendu. L’ajout d’une
directive @using pour l’espace de noms du composant rend le composant disponible,
ce qui résout l’avertissement. Pour plus d’informations, consultez la section Nom du
composant, nom de la classe et espace de noms.

L’exemple de composant Heading présenté dans cette section n’ayant pas de directive
@page, un utilisateur ne peut pas accéder directement au composant Heading par le
biais d’une demande directe dans le navigateur. Toutefois, tout composant avec une
directive @page peut être imbriqué dans un autre composant. Si l’ajout de @page
"/heading" en haut du fichier Razor du composant Heading le rend directement
accessible, le composant est alors rendu pour les demandes de navigateur aux
emplacements /heading et /heading-example .

Paramètres de composant
Les paramètres de composant passent les données aux composants et sont définis à
l’aide de propriétés C# publiques sur la classe de composant avec l’attribut [Parameter].
Dans l’exemple suivant, un type de référence intégré (System.String) et un type de
référence défini par l’utilisateur ( PanelBody ) sont passés en tant que paramètres de
composant.

PanelBody.cs :

C#

namespace BlazorSample;

public class PanelBody


{
public string? Text { get; set; }
public string? Style { get; set; }
}

ParameterChild.razor :

razor
<div class="card w-25" style="margin-bottom:15px">
<div class="card-header font-weight-bold">@Title</div>
<div class="card-body" style="font-style:@Body.Style">
@Body.Text
</div>
</div>

@code {
[Parameter]
public string Title { get; set; } = "Set By Child";

[Parameter]
public PanelBody Body { get; set; } =
new()
{
Text = "Set by child.",
Style = "normal"
};
}

2 Avertissement

La spécification de valeurs initiales pour les paramètres de composant est prise en


charge, mais ne créez pas de composant qui écrit dans ses propres paramètres
après le rendu initial du composant. Pour plus d’informations, consultez Éviter le
remplacement de paramètres dans ASP.NET Core Blazor.

Les paramètres de composant Title et Body du composant ParameterChild sont définis


par des arguments dans la balise HTML qui génère le rendu de l’instance du composant.
Le composant ParameterParent suivant génère le rendu de deux composants
ParameterChild :

Le premier composant ParameterChild est rendu sans fournir d’arguments de


paramètre.
Le deuxième composant ParameterChild reçoit des valeurs pour Title et Body du
composant ParameterParent , qui utilise une expression C# explicite pour définir les
valeurs des propriétés de PanelBody .

Parameter1.razor :

razor

@page "/parameter-1"

<PageTitle>Parameter 1</PageTitle>
<h1>Parameter Example 1</h1>

<h1>Child component (without attribute values)</h1>

<ParameterChild />

<h1>Child component (with attribute values)</h1>

<ParameterChild Title="Set by Parent"


Body="@(new PanelBody() { Text = "Set by parent.", Style =
"italic" })" />

Le balisage HTML rendu suivant du composant ParameterParent montre les valeurs par
défaut du composant ParameterChild quand le composant ParameterParent ne fournit
pas de valeurs de paramètre de composant. Quand le composant ParameterParent
fournit des valeurs de paramètre de composant, celles-ci remplacent les valeurs par
défaut du composant ParameterChild .

7 Notes

Pour plus de clarté, les classes de style CSS rendues ne sont pas affichées dans le
balisage HTML rendu suivant.

HTML

<h1>Child component (without attribute values)</h1>

<div>
<div>Set By Child</div>
<div>Set by child.</div>
</div>

<h1>Child component (with attribute values)</h1>

<div>
<div>Set by Parent</div>
<div>Set by parent.</div>
</div>

Affectez un champ C#, une propriété ou le résultat d’une méthode à un paramètre de


composant en tant que valeur d’attribut HTML. La valeur de l’attribut peut généralement
être n’importe quelle expression C# qui correspond au type du paramètre. La valeur de
l’attribut peut éventuellement mener avec un Razor symbole réservé@, mais elle n’est
pas obligatoire.
Si le paramètre de composant est de type chaîne, la valeur de l’attribut est plutôt traitée
comme un littéral de chaîne C# par défaut. Si vous souhaitez spécifier une expression C#
à la place, utilisez le préfixe @ .

Le composant ParameterParent2 suivant affiche quatre instances du composant


ParameterChild précédent et définit leurs valeurs de paramètre Title avec :

La valeur du champ title .


Le résultat de la méthode C# GetTitle .
La date locale actuelle au format long avec ToLongDateString, qui utilise une
expression C# implicite.
La propriété Title de l’objet panelData .

Les guillemets autour des valeurs d’attribut de paramètre sont facultatifs dans la plupart
des cas, conformément à la spécification HTML5. Par exemple, Value=this est pris en
charge à la place de Value="this" . Toutefois, nous vous recommandons d’utiliser des
guillemets, car ils sont plus faciles à retenir et sont largement adoptés par les
technologies web.

Tout au long de la documentation, les exemples de code se caractérisent comme suit :

Ils utilisent toujours des guillemets. Exemple : Value="this" .


N’utilisez pas le préfixe @ avec des types non littéraux, sauf si nécessaire. Exemple :
Count="ct" , où ct est une variable de type nombre. Count="@ct" est une

approche stylistique valide, mais la documentation et les exemples n’adoptent pas


la convention.
Toujours éviter @ pour les littéraux, en dehors des expressions Razor. Exemple :
IsFixed="true" . Cela comprend les mots clés (par exemple, this ) et null , mais

vous pouvez choisir de les utiliser si vous le souhaitez. Par exemple,


IsFixed="@true" est rare mais pris en charge.

Parameter2.razor :

razor

@page "/parameter-2"

<PageTitle>Parameter 2</PageTitle>

<h1>Parameter Example 2</h1>

<ParameterChild Title="@title" />

<ParameterChild Title="@GetTitle()" />


<ParameterChild Title="@DateTime.Now.ToLongDateString()" />

<ParameterChild Title="@panelData.Title" />

@code {
private string title = "From Parent field";
private PanelData panelData = new();

private string GetTitle()


{
return "From Parent method";
}

private class PanelData


{
public string Title { get; set; } = "From Parent object";
}
}

7 Notes

Lorsque vous affectez un membre C# à un paramètre de composant, ne préfixez


pas l’attribut HTML du paramètre avec @ .

Correct ( Title est un paramètre de chaîne, Count est un paramètre de type


nombre) :

razor

<ParameterChild Title="@title" Count="ct" />

razor

<ParameterChild Title="@title" Count="@ct" />

Incorrect :

razor

<ParameterChild @Title="@title" @Count="ct" />

razor

<ParameterChild @Title="@title" @Count="@ct" />


Contrairement aux pages Razor ( .cshtml ), Blazor ne peut pas effectuer de travail
asynchrone dans une expression Razor lors du rendu d’un composant. En effet, Blazor
est conçu pour le rendu d’interfaces utilisateur interactives. Dans une interface
utilisateur interactive, l’écran doit toujours afficher quelque chose. Il n’est donc pas
judicieux de bloquer le flux de rendu. Au lieu de cela, le travail asynchrone est effectué
pendant l’un des événements de cycle de vie asynchrones. Après chaque événement de
cycle de vie asynchrone, le composant peut être à nouveau rendu. La syntaxe Razor
suivante n’est pas prise en charge :

razor

<ParameterChild Title="@await ..." />

Le code de l’exemple précédent génère une erreur de compilateur quand l’application


est générée :

L’opérateur « await » ne peut être utilisé que dans une méthode asynchrone.
Envisagez de marquer cette méthode avec le modificateur « async » et de changer
son type de retour en « Task ».

Pour obtenir une valeur pour le paramètre Title dans l’exemple précédent de manière
asynchrone, le composant peut utiliser l’événement de cycle de vie OnInitializedAsync,
comme le montre l’exemple suivant :

razor

<ParameterChild Title="@title" />

@code {
private string? title;

protected override async Task OnInitializedAsync()


{
title = await ...;
}
}

Pour plus d’informations, consultez le cycle de vie des composants Razor ASP.NET Core.

L’utilisation d’une expression Razor explicite pour concaténer du texte avec un résultat
d’expression pour affectation à un paramètre n’est pas prise en charge. L’exemple
suivant cherche à concaténer le texte « Set by » avec la valeur de propriété d’un objet.
Bien que cette syntaxe soit prise en charge dans une page Razor ( .cshtml ), elle n’est pas
valide pour une affectation au paramètre Title de l’enfant dans un composant. La
syntaxe Razor suivante n’est pas prise en charge :

razor

<ParameterChild Title="Set by @(panelData.Title)" />

Le code de l’exemple précédent génère une erreur de compilateur quand l’application


est générée :

Les attributs de composant ne prennent pas en charge le contenu complexe


(mélange C# et balisage).

Pour prendre en charge l’affectation d’une valeur composée, utilisez une méthode, un
champ ou une propriété. L’exemple suivant concatène « Set by » et la valeur de
propriété d’un objet dans la méthode C# GetTitle :

Parameter3.razor :

razor

@page "/parameter-3"

<PageTitle>Parameter 3</PageTitle>

<h1>Parameter Example 3</h1>

<ParameterChild Title="@GetTitle()" />

@code {
private PanelData panelData = new();

private string GetTitle() => $"Set by {panelData.Title}";

private class PanelData


{
public string Title { get; set; } = "Parent";
}
}

Pour en savoir plus, consultez les informations de référence sur la syntaxe Razor pour
ASP.NET Core.

2 Avertissement
La spécification de valeurs initiales pour les paramètres de composant est prise en
charge, mais ne créez pas de composant qui écrit dans ses propres paramètres
après le rendu initial du composant. Pour plus d’informations, consultez Éviter le
remplacement de paramètres dans ASP.NET Core Blazor.

Les paramètres de composant doivent être déclarés en tant que propriétés


automatiques, ce qui signifie qu’ils ne doivent pas contenir de logique personnalisée
dans leurs accesseurs get ou set . Par exemple, la propriété StartData suivante est une
propriété automatique :

C#

[Parameter]
public DateTime StartData { get; set; }

Ne placez pas de logique personnalisée dans l’accesseur get ou set , car les paramètres
de composant servent uniquement de canaux à l’aide desquels un composant parent
peut transmettre des informations à un composant enfant. Si un accesseur set d’une
propriété de composant enfant contient une logique entraînant un nouveau rendu du
composant parent, il en résulte une boucle de rendu infinie.

Pour transformer une valeur de paramètre reçue :

Laissez la propriété de paramètre comme propriété automatique pour représenter


les données brutes fournies.
Créez une autre propriété ou méthode pour fournir les données transformées en
fonction de la propriété de paramètre.

Remplacez OnParametersSetAsync pour transformer un paramètre reçu chaque fois que


de nouvelles données sont reçues.

L’écriture d’une valeur initiale dans un paramètre de composant est prise en charge, car
les affectations de valeurs initiales n’interfèrent pas avec le rendu de composant
automatique de Blazor. L’affectation suivante du DateTime local actuel avec
DateTime.Now à StartData est une syntaxe valide dans un composant :

C#

[Parameter]
public DateTime StartData { get; set; } = DateTime.Now;

Après l’affectation initiale de DateTime.Now, n’attribuez pas de valeur à StartData dans


le code du développeur. Pour plus d’informations, consultez Éviter le remplacement de
paramètres dans ASP.NET Core Blazor.

Appliquez l’attribut [EditorRequired] pour spécifier un paramètre de composant


obligatoire. Si une valeur de paramètre n’est pas fournie, les éditeurs ou les outils de
génération peuvent présenter des avertissements à l’utilisateur. Cet attribut n’est valide
que sur les propriétés également marquées avec l’attribut [Parameter].
EditorRequiredAttribute est appliqué au moment de la conception et quand application
est générée. L’attribut n’est pas appliqué au moment de l’exécution et ne garantit pas
une valeur de paramètre différente de null .

C#

[Parameter]
[EditorRequired]
public string? Title { get; set; }

Les listes d’attributs monolignes sont également prises en charge :

C#

[Parameter, EditorRequired]
public string? Title { get; set; }

N’utilisez pas le modificateurrequired ou l’accesseurinit propriétés des paramètres du


composant. Les composants sont généralement instanciés et attribués aux valeurs de
paramètre à l’aide de la réflexion, ce qui contourne les garanties qui init et required
sont conçus pour effectuer. À la place, appliquez l’attribut[EditorRequired] pour spécifier
un paramètre de composant obligatoire.

Les Tuples (documentation de l’API) sont pris en charge pour les paramètres de
composant et les types RenderFragment. L’exemple de paramètre de composant suivant
passe trois valeurs dans un Tuple :

RenderTupleChild.razor :

razor

<div class="card w-50" style="margin-bottom:15px">


<div class="card-header font-weight-bold">Tuple Card</div>
<div class="card-body">
<ul>
<li>Integer: @Data?.Item1</li>
<li>String: @Data?.Item2</li>
<li>Boolean: @Data?.Item3</li>
</ul>
</div>
</div>

@code {
[Parameter]
public (int, string, bool)? Data { get; set; }
}

RenderTupleParent.razor :

razor

@page "/render-tuple-parent"

<PageTitle>Render Tuple Parent</PageTitle>

<h1>Render Tuple Parent Example</h1>

<RenderTupleChild Data="data" />

@code {
private (int, string, bool) data = new(999, "I aim to misbehave.",
true);
}

Les tuples nommés sont pris en charge, comme indiqué dans l’exemple suivant :

NamedTupleChild.razor :

razor

<div class="card w-50" style="margin-bottom:15px">


<div class="card-header font-weight-bold">Tuple Card</div>
<div class="card-body">
<ul>
<li>Integer: @Data?.TheInteger</li>
<li>String: @Data?.TheString</li>
<li>Boolean: @Data?.TheBoolean</li>
</ul>
</div>
</div>

@code {
[Parameter]
public (int TheInteger, string TheString, bool TheBoolean)? Data { get;
set; }
}

NamedTuples.razor :

razor
@page "/named-tuples"

<PageTitle>Named Tuples</PageTitle>

<h1>Named Tuples Example</h1>

<NamedTupleChild Data="data" />

@code {
private (int TheInteger, string TheString, bool TheBoolean) data =
new(999, "I aim to misbehave.", true);
}

Citation ©2005 Universal Pictures : Serenity (Nathan Fillion )

Paramètres de routage
Les composants peuvent spécifier des paramètres de routage dans le modèle de
routage de la directive @page. Le routeur Blazor utilise des paramètres de routage pour
remplir les paramètres de composant correspondants.

Les paramètres de routage facultatifs sont pris en charge. Dans l’exemple suivant, le
paramètre facultatif text affecte la valeur du segment de routage à la propriété Text
du composant. Si le segment n’est pas présent, la valeur de Text est définie sur
« fantastic » dans la méthode de cycle de vie OnInitialized.

OptionalParameter.razor :

razor

@page "/optional-parameter/{text?}"

<h1>Blazor is @Text!</h1>

@code {
[Parameter]
public string? Text { get; set; }

protected override void OnInitialized()


{
Text = Text ?? "fantastic";
}
}

Pour plus d’informations sur les paramètres de routage catch-all ( {*pageRoute} ), qui
capturent les chemins au-delà des limites des dossiers, consultez Routage et navigation
dans Blazor ASP.NET Core .

Fragments de rendu de contenu enfant


Les composants peuvent définir le contenu d’un autre composant. Le composant
d’affectation fournit le contenu entre les balises d’ouverture et de fermeture du
composant enfant.

Dans l’exemple suivant, le composant RenderFragmentChild a un paramètre de


composant ChildContent qui représente un segment de l’interface utilisateur rendu en
tant que RenderFragment. La position de ChildContent dans le balisage Razor du
composant correspond à l’emplacement où le contenu est rendu dans la sortie HTML
finale.

RenderFragmentChild.razor :

razor

<div class="card w-25" style="margin-bottom:15px">


<div class="card-header font-weight-bold">Child content</div>
<div class="card-body">@ChildContent</div>
</div>

@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}

) Important

La propriété qui reçoit le contenu RenderFragment doit être nommée


ChildContent par convention.

Les rappels d’événements ne sont pas pris en charge pour RenderFragment.

Le composant suivant fournit du contenu pour générer le rendu de


RenderFragmentChild . Pour cela, le contenu est placé à l’intérieur des balises d’ouverture

et de fermeture du composant enfant.

RenderFragments.razor :

razor
@page "/render-fragments"

<PageTitle>Render Fragments</PageTitle>

<h1>Render Fragments Example</h1>

<RenderFragmentChild>
Content of the child component is supplied
by the parent component.
</RenderFragmentChild>

Compte tenu de la façon dont Blazor génère le rendu du contenu enfant, le rendu de
composants à l’intérieur d’une boucle for nécessite une variable d’index local si la
variable de boucle d’incrémentation est utilisée dans le contenu du composant
RenderFragmentChild . L’exemple suivant peut être ajouté au composant parent

précédent :

razor

<h1>Three children with an index variable</h1>

@for (int c = 0; c < 3; c++)


{
var current = c;

<RenderFragmentChild>
Count: @current
</RenderFragmentChild>
}

Vous pouvez également utiliser une boucle foreach avec Enumerable.Range à la place
d’une boucle for. L’exemple suivant peut être ajouté au composant parent précédent :

razor

<h1>Second example of three children with an index variable</h1>

@foreach (var c in Enumerable.Range(0,3))


{
<RenderFragmentChild>
Count: @c
</RenderFragmentChild>
}

Des fragments de rendu sont utilisés pour générer le rendu du contenu enfant dans les
applications Blazor et sont décrits avec des exemples dans les articles et sections
d’article suivants :
DispositionsBlazor
Passer des données dans une hiérarchie de composants
Composants basés sur un modèle
Gestion globale des exceptions

7 Notes

Les composants Razor intégrés du framework Blazor utilisent la même convention


de paramètre de composant ChildContent pour définir leur contenu. Vous pouvez
voir les composants qui définissent le contenu enfant en recherchant le nom de
propriété de paramètre de composant ChildContent dans la documentation de
l’API (filtre l’API avec le terme de recherche « ChildContent »).

Fragments de rendu pour une logique de rendu


réutilisable
Vous pouvez employer des composants enfants dans le seul but de réutiliser la logique
de rendu. Dans le bloc @code d’un composant quelconque, définissez un
RenderFragment et générez le rendu du fragment à partir de n’importe quel
emplacement autant de fois que nécessaire :

razor

@RenderWelcomeInfo

<p>Render the welcome info a second time:</p>

@RenderWelcomeInfo

@code {
private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!
</p>;
}

Pour plus d’informations, consultez Réutiliser la logique de rendu.

Capturer les références à des composants


Les références de composants permettent de référencer une instance de composant
pour émettre des commandes. Pour capturer une référence de composant :

Ajoutez un attribut @ref au composant enfant.


Définissez un champ du même type que le composant enfant.

Quand le composant est rendu, le champ est rempli avec l’instance du composant. Vous
pouvez ensuite appeler des méthodes .NET sur l’instance.

Considérez le composant ReferenceChild suivant qui journalise un message quand son


ChildMethod est appelé.

ReferenceChild.razor :

razor

@inject ILogger<ReferenceChild> Logger

@if (value > 0)


{
<p>
<code>value</code>: @value
</p>
}

@code {
private int value;

public void ChildMethod(int value)


{
Logger.LogInformation("Received {Value} in ChildMethod", value);

this.value = value;
StateHasChanged();
}
}

Une référence de composant n’est remplie qu’après le rendu du composant et sa sortie


inclut l’élément de ReferenceChild . Tant que le composant n’est pas rendu, il n’y a rien à
référencer.

Pour manipuler les références de composant une fois le rendu du composant terminé,
utilisez les méthodes OnAfterRender ou OnAfterRenderAsync.

Pour utiliser une variable de référence avec un gestionnaire d’événements, utilisez une
expression lambda ou affectez le délégué du gestionnaire d’événements dans les
méthodes OnAfterRender ou OnAfterRenderAsync. De cette façon, la variable de
référence est affectée avant l’affectation du gestionnaire d’événements.

L’approche lambda suivante utilise le composant ReferenceChild précédent.

ReferenceParent1.razor :
razor

@page "/reference-parent-1"

<PageTitle>Reference Parent 1</PageTitle>

<h1>Reference Parent Example 1</h1>

<button @onclick="@(() => childComponent?.ChildMethod(5))">


Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>

<ReferenceChild @ref="childComponent" />

@code {
private ReferenceChild? childComponent;
}

L’approche déléguée suivante utilise le composant ReferenceChild précédent.

ReferenceParent2.razor :

razor

@page "/reference-parent-2"

<PageTitle>Reference Parent 2</PageTitle>

<h1>Reference Parent Example 2</h1>

<button @onclick="@(() => callChildMethod?.Invoke())">


Call <code>ReferenceChild.ChildMethod</code> with an argument of 5
</button>

<ReferenceChild @ref="childComponent" />

@code {
private ReferenceChild? childComponent;
private Action? callChildMethod;

protected override void OnAfterRender(bool firstRender)


{
if (firstRender)
{
callChildMethod = CallChildMethod;
}
}

private void CallChildMethod()


{
childComponent?.ChildMethod(5);
}
}

Bien que la capture de références de composants utilise une syntaxe similaire à la


capture de références d’éléments, il ne s’agit pas d’une fonctionnalité d’interopérabilité
JavaScript. Les références de composants ne sont pas passées au code JavaScript. Les
références de composants sont uniquement utilisées dans le code .NET.

) Important

N’utilisez pas de références de composants pour muter l’état des composants


enfants. Au lieu de cela, utilisez des paramètres de composant déclaratifs normaux
pour passer des données aux composants enfants. L’utilisation de paramètres de
composant entraîne la regénération automatique du rendu des composants
enfants au bon moment. Pour plus d’informations, consultez la section sur les
paramètres de composant et l’article sur la liaison de données Blazor
ASP.NET Core.

Appliquer un attribut
Des attributs peuvent être appliqués à des composants avec la directive @attribute.
L’exemple suivant applique l’attribut [Authorize] à la classe du composant :

razor

@page "/"
@attribute [Authorize]

Attributs d’élément HTML conditionnels


Les propriétés d’attribut d’élément HTML sont définies de manière conditionnelle en
fonction de la valeur .NET. Si la valeur est false ou null , la propriété n’est pas définie.
Si la valeur est true , la propriété est définie.

Dans l’exemple suivant, IsCompleted détermine si la propriété checked de l’élément


<input> est définie.

ConditionalAttribute.razor :

razor
@page "/conditional-attribute"

<PageTitle>Conditional Attribute</PageTitle>

<h1>Conditional Attribute Example</h1>

<label>
<input type="checkbox" checked="@IsCompleted" />
Is Completed?
</label>

<button @onclick="@(() => IsCompleted = !IsCompleted)">


Change IsCompleted
</button>

@code {
[Parameter]
public bool IsCompleted { get; set; }
}

Pour en savoir plus, consultez les informations de référence sur la syntaxe Razor pour
ASP.NET Core.

2 Avertissement

Certains attributs HTML, tels que aria-pressed , ne fonctionnent pas correctement


lorsque le type .NET est un bool . Dans ces cas, utilisez un type string au lieu de
bool .

HTML brut
Les chaînes sont normalement rendues à l’aide de nœuds de texte DOM, ce qui signifie
que le balisage qu’elles peuvent contenir est ignoré et traité comme du texte littéral.
Pour générer le rendu de code HTML brut, wrappez le contenu HTML dans une valeur
MarkupString. La valeur est analysée au format HTML ou SVG et insérée dans le DOM.

2 Avertissement

Le rendu de code HTML brut construit à partir d’une source non approuvée
présente un risque de sécurité et doit toujours être évité.

L’exemple suivant montre comment utiliser le type MarkupString pour ajouter un bloc
de contenu HTML statique à la sortie rendue d’un composant.
MarkupStrings.razor :

razor

@page "/markup-strings"

<PageTitle>Markup Strings</PageTitle>

<h1>Markup Strings Example</h1>

@((MarkupString)myMarkup)

@code {
private string myMarkup =
"<p class=\"text-danger\">This is a dangerous <em>markup
string</em>.</p>";
}

Modèles Razor
Les fragments de rendu peuvent être définis avec la syntaxe de modèle Razor pour
définir un extrait de code d’interface utilisateur. Les modèles Razor utilisent le format
suivant :

razor

@<{HTML tag}>...</{HTML tag}>

L’exemple suivant montre comment spécifier les valeurs RenderFragment et


RenderFragment<TValue> et générer le rendu de modèles directement dans un
composant. Les fragments de rendu peuvent également être passés en tant
qu’arguments aux composants basés sur un modèle.

RazorTemplate.razor :

razor

@page "/razor-template"

<PageTitle>Razor Template</PageTitle>

<h1>Razor Template Example</h1>

@timeTemplate

@petTemplate(new Pet { Name = "Nutty Rex" })


@code {
private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.
</p>;
private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet:
@pet.Name</p>;

private class Pet


{
public string? Name { get; set; }
}
}

Sortie rendue du code précédent :

HTML

<p>The time is 4/19/2021 8:54:46 AM.</p>


<p>Pet: Nutty Rex</p>

Les ressources statiques


Blazor suit la convention des applications ASP.NET Core pour les ressources statiques.
Les ressources statiques se trouvent dans le dossier web root (wwwroot) du projet ou
dans des dossiers sous le dossier wwwroot .

Utilisez un chemin relatif de base ( / ) pour faire référence à la racine web d’une
ressource statique. Dans l’exemple suivant, logo.png se trouve physiquement dans le
dossier {PROJECT ROOT}/wwwroot/images . {PROJECT ROOT} est la racine du projet de
l’application.

razor

<img alt="Company logo" src="/images/logo.png" />

Les composants ne prennent pas en charge la notation tilde-barre oblique ( ~/ ).

Pour plus d’informations sur la définition du chemin de base d’une application,


consultez Héberger et déployer Blazor ASP.NET Core.

Tag Helpers non pris en charge dans les


composants
Les Tag Helpers ne sont pas pris en charge dans les composants. Pour bénéficier d’une
fonctionnalité de type Tag Helper dans Blazor, créez un composant avec la même
fonctionnalité que le Tag Helper et utilisez le composant à la place.

Images SVG (Scalable Vector Graphics)


Étant donné que Blazor génère le rendu de code HTML, les images prises en charge par
les navigateurs, notamment les images SVG (.svg) , sont prises en charge avec la balise
<img> :

HTML

<img alt="Example image" src="image.svg" />

De même, les images SVG sont prises en charge dans les règles CSS d’un fichier de
feuille de style ( .css ) :

css

.element-class {
background-image: url("image.svg");
}

Blazor prend en charge l’élément <foreignObject> pour afficher du code HTML


arbitraire dans un SVG. Le balisage peut représenter du code HTML arbitraire, un
RenderFragment ou un composant Razor.

L’exemple suivant illustre cette situation :

Affichage d’un string ( @message ).


Liaison bidirectionnelle avec un élément <input> et un champ value .
Composant Robot .

razor

<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">


<rect x="0" y="0" rx="10" ry="10" width="200" height="200"
stroke="black"
fill="none" />
<foreignObject x="20" y="20" width="160" height="160">
<p>@message</p>
</foreignObject>
</svg>

<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="200" height="200">
<label>
Two-way binding:
<input @bind="value" @bind:event="oninput" />
</label>
</foreignObject>
</svg>

<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject>
<Robot />
</foreignObject>
</svg>

@code {
private string message = "Lorem ipsum dolor sit amet, consectetur
adipiscing " +
"elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua.";

private string? value;


}

Comportement de rendu des espaces blancs


À moins que la directive @preservewhitespace ne soit utilisée avec la valeur true , un
espace blanc supplémentaire est supprimé par défaut dans les cas suivants :

Espace blanc de début ou de fin au sein d’un élément.


Espace blanc de début ou de fin au sein d’un paramètre
RenderFragment/RenderFragment<TValue> (par exemple, contenu enfant passé à
un autre composant).
Espace blanc précédant ou suivant un bloc de code C#, comme @if ou @foreach .

La suppression de l’espace blanc peut affecter la sortie rendue lors de l’utilisation d’une
règle CSS, par exemple white-space: pre . Pour désactiver cette optimisation des
performances et conserver l’espace blanc, effectuez l’une des actions suivantes :

Ajoutez la directive @preservewhitespace true en haut du fichier Razor ( .razor )


pour appliquer la préférence à un composant spécifique.
Ajoutez la directive @preservewhitespace true dans un fichier _Imports.razor pour
appliquer la préférence à un sous-répertoire ou à l’ensemble du projet.

Dans la plupart des cas, aucune action n’est requise car les applications continuent
généralement à se comporter normalement (mais plus rapidement). Si la suppression
d’un espace blanc entraîne un problème de rendu pour un composant particulier,
utilisez @preservewhitespace true dans ce composant pour désactiver cette
optimisation.

Composant racine
Un composant Razor racine (composant racine) est le premier composant chargé d’une
hiérarchie de composants créée par l’application.

Dans une application créée à partir du modèle de projet Web App Blazor, le composant
App ( App.razor ) est spécifié comme composant racine par défaut par le paramètre de

type déclaré pour l’appel dans MapRazorComponents<TRootComponent> le fichier


Program appartenant au côté serveur. L’exemple suivant montre l’utilisation du

composant App comme composant racine, qui est la valeur par défaut d’une application
créée à partir du modèle de projet Blazor :

C#

app.MapRazorComponents<App>();

7 Notes

La création d’un composant racine interactif, tel que le composant App , n’est pas
prise en charge.

Dans une application créée à partir du modèle de projet Blazor WebAssembly, le


composant App ( App.razor ) est créé en tant que composant racine par défaut dans le
fichier Program :

C#

builder.RootComponents.Add<App>("#app");

Dans le code précédent, le sélecteur CSS, #app , indique que le composant App est
spécifié pour le <div> dans wwwroot/index.html avec un id de app :

HTML

<div id="app">...</app>
Les applications MVC et Razor Pages peuvent également utiliser le Tag Helper de
composant pour inscrire les composants racines rendus Blazor WebAssembly de
manière statique :

CSHTML

<component type="typeof(App)" render-mode="WebAssemblyPrerendered" />

Les composants rendus de manière statique peuvent uniquement être ajoutés à


l’application. Ils ne peuvent pas être supprimés ou mis à jour par la suite.

Pour plus d'informations, reportez-vous aux ressources suivantes :

Tag Helper de composant dans ASP.NET Core


Intégrer des composants Razor ASP.NET Core aux applications ASP.NET Core

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Modes de rendu ASP.NET Core Blazor
Article • 09/02/2024

Cet article explique le contrôle du rendu des composants Razor dans les applications
web Blazor, au moment de la compilation ou au moment de l’exécution.

7 Notes

Cette aide ne s’applique pas aux applications Blazor WebAssembly autonomes.

Modes de rendu
Chaque composant d’une application web Blazor adopte un mode de rendu pour
déterminer le modèle d’hébergement qu’il utilise, l’endroit où il est affiché et s’il est
interactif ou non.

Le tableau suivant présente les modes de rendu disponibles pour le rendu des
composants Razor dans l’application web Blazor. Pour appliquer un mode de rendu à un
composant, utilisez la directive @rendermode sur l'instance du composant ou sur la
définition du composant. Plus loin dans cet article, des exemples sont présentés pour
chaque scénario de mode de rendu.

ノ Agrandir le tableau

Nom Description Emplacement Interactive


du rendu

Serveur statique Rendu statique côté serveur (SSR statique) Serveur ❌

Serveur Rendu côté serveur interactif (SSR interactif) à Serveur ✔️


interactif l’aide de Blazor Server.

WebAssembly Rendu côté client (CSR) à l’aide de Blazor Client ✔️


interactif WebAssembly†.

Voiture SSR interactif à l’aide de Blazor Server dans Serveur, puis ✔️


interactive un premier temps, puis CSR lors des visites client
ultérieures après le téléchargement de l’offre
groupée Blazor.

† Le rendu côté client (CSR) est supposé être interactif. Le « rendu interactif côté client »
et « CSR interactif » ne sont pas utilisés par le secteur ou dans la documentation Blazor.
Le pré-rendu est activé par défaut pour les composants interactifs. Des conseils sur le
contrôle du pré-rendu sont fournis plus loin dans cet article. Pour connaître la
terminologie générale du secteur sur les concepts de rendu du client et du serveur,
consultez Notions de base d’ASP.NET Blazor.

Les exemples suivants illustrent la définition du mode de rendu du composant avec


quelques fonctionnalités de composant Razor de base.

Pour tester les comportements du mode de rendu localement, vous pouvez placer les
composants suivants dans une application créée à partir du modèle de projet de
l’application web Blazor. Lorsque vous créez l’application, cochez les cases (Visual
Studio) ou appliquez les options CLI (CLI .NET) pour activer l’interactivité côté serveur et
côté client. Pour obtenir des conseils sur la création d’une application web Blazor,
consultez Outils pour ASP.NET Core Blazor.

Activez la prise en charge des modes de rendu


interactifs
Une application web Blazor doit être configurée pour prendre en charge les modes de
rendu interactifs. Les extensions suivantes sont automatiquement appliquées aux
applications créées à partir du modèle de projet d’application web Blazor lors de la
création de l’application. Les composants individuels doivent toujours déclarer leur
mode de rendu conformément à la section Modes de rendu après la configuration des
services et points de terminaison des composants dans le fichier Program de
l’application.

Les services pour les composants Razor sont ajoutés en appelant AddRazorComponents.

Extensions du générateur de composants :

AddInteractiveServerComponents ajoute des services pour prendre en charge le


rendu des composants de serveur interactif.
AddInteractiveWebAssemblyComponents ajoute des services pour prendre en
charge le rendu des composants WebAssembly interactifs.

MapRazorComponents découvre les composants disponibles et spécifie le composant


racine de l’application (le premier composant chargé), qui est par défaut le composant
App ( App.razor ).

Extensions du générateur de conventions de point de terminaison :

AddInteractiveServerRenderMode configure le rendu côté serveur interactif (SSR


interactif) pour l’application.
AddInteractiveWebAssemblyRenderMode configure le mode de rendu
WebAssembly interactif pour l’application.

7 Notes

Pour obtenir des indications sur le placement de l’API dans les exemples suivants,
examinez le fichier Program d’une application générée à partir du modèle de projet
de l’application web Blazor. Pour obtenir des conseils sur la création d’une
application web Blazor, consultez Outils pour ASP.NET Core Blazor.

Exemple 1 : l’API de fichier Program suivante ajoute des services et une configuration
pour activer SSR interactif :

C#

builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();

C#

app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();

Exemple 2 : l’API de fichier Program suivante ajoute des services et une configuration
pour activer le mode de rendu WebAssembly interactif :

C#

builder.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents();

C#

app.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode();

Exemple 3 : l’API de fichier Program suivante ajoute des services et une configuration
pour activer les modes de rendu Serveur interactif, WebAssembly interactif et Auto
interactif :

C#
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();

C#

app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode();

Blazor utilise le modèle d’hébergement Blazor WebAssembly pour télécharger et


exécuter des composants qui utilisent le mode de rendu WebAssembly interactif. Un
projet client distinct est requis pour configurer l’hébergement Blazor WebAssembly de
ces composants. Le projet client contient le code de démarrage de l’hôte Blazor
WebAssembly et configure le runtime .NET pour qu’il s’exécute dans un navigateur. Le
modèle d’application web Blazor ajoute ce projet client pour vous lorsque vous
sélectionnez l’option permettant d’activer l’interactivité WebAssembly. Tous les
composants utilisant le mode de rendu WebAssembly doivent être générés à partir du
projet client. Ils sont donc inclus dans l’ensemble d’applications téléchargé.

Appliquez un mode de rendu à une instance de


composant
Pour appliquer un mode de rendu à une instance de composant, utilisez l’attribut de
directive @rendermodeRazor où le composant est utilisé.

Dans l’exemple suivant, le rendu côté serveur interactif (SSR interactif) est appliqué à
l’instance de composant Dialog :

razor

<Dialog @rendermode="InteractiveServer" />

7 Notes

Les modèles Blazor incluent une directive statique using pour RenderMode dans le
fichier _Imports de l’application ( Components/_Imports.razor ) pour une syntaxe de
@rendermode plus courte :

razor
@using static Microsoft.AspNetCore.Components.Web.RenderMode

Sans la directive précédente, les composants doivent spécifier la classe statique


RenderMode dans la syntaxe @rendermode :

razor

<Dialog @rendermode="RenderMode.InteractiveServer" />

Vous pouvez référencer des instances en mode de rendu personnalisé instanciées


directement avec une configuration personnalisée. Pour plus d’informations, consultez la
section Modes de rendu abrégé personnalisés plus loin dans cet article.

Appliquez un mode de rendu à une définition


de composant
Pour spécifier le mode de rendu d’un composant dans le cadre de sa définition, utilisez
la directive @rendermodeRazor et l’attribut de mode de rendu correspondant.

razor

@page "..."
@rendermode InteractiveServer

L’application d’un mode de rendu à une définition de composant est couramment


utilisée lors de l’application d’un mode de rendu à une page spécifique. Les pages
routables utilisent par défaut le même mode de rendu que le composant Router qui a
rendu la page.

Techniquement, @rendermode est à la fois une Razordirective et un Razorattribut de


directive. La sémantique est similaire, mais il existe des différences. La directive
@rendermode se trouve sur la définition du composant, donc l'instance du mode de

rendu référencée doit être statique. L'attribut directive @rendermode peut prendre
n'importe quelle instance de mode de rendu.

7 Notes

Les auteurs de composants doivent éviter le couplage de l’implémentation d’un


composant à un mode de rendu spécifique. Au lieu de cela, les auteurs de
composants doivent généralement concevoir des composants permettant de
prendre en charge tout mode de rendu ou modèle d’hébergement.
L’implémentation d’un composant doit éviter les hypothèses sur l’emplacement où
il est en cours d’exécution (serveur ou client) et doit se dégrader correctement
lorsqu’il est affiché de manière statique. La spécification du mode de rendu dans la
définition du composant peut être nécessaire si le composant n’est pas instancié
directement (par exemple, avec un composant de page routable) ou pour spécifier
un mode de rendu pour toutes les instances de composant.

Appliquer un mode de rendu à l’ensemble de


l’application
Pour définir le mode de rendu pour l’ensemble de l’application, indiquez le mode de
rendu au composant interactif de niveau le plus élevé dans la hiérarchie des composants
de l’application qui n’est pas un composant racine.

7 Notes

Rendre un composant racine interactif, comme le composant App , n’est pas pris en
charge. Par conséquent, le mode de rendu de l’application entière ne peut pas être
défini directement par le composant App .

Pour les applications basées sur le modèle de projet Web App Blazor, un mode de rendu
affecté à l’ensemble de l’application est généralement spécifié, où le composant Routes
est utilisé dans le composant App ( Components/App.razor ) :

razor

<Routes @rendermode="InteractiveServer" />

Le composant Router propage son mode de rendu aux pages qu’il achemine.

Vous devez également définir le même mode de rendu interactif sur le composant
HeadOutlet, qui se trouve également dans le composant App d’une application web
Blazor générée à partir du modèle de projet :

<HeadOutlet @rendermode="InteractiveServer" />


Pour les applications qui adoptent un mode de rendu côté client interactif
(WebAssembly ou Auto) et qui activent le mode de rendu pour l’ensemble de
l’application via le composant Routes :

Placez ou déplacez les fichiers de mise en page et de navigation du dossier


Components/Layout de l’application serveur dans le dossier .Client du projet

Layout . S’il n’existe pas déjà, créez un dossier Layout dans le projet .Client .

Placez ou déplacez les composants du dossier Components/Pages de l’application


serveur dans le dossier .Client du projet Pages . S’il n’existe pas déjà, créez un
dossier Pages dans le projet .Client .
Placez ou déplacez les composants Routes du dossier Components de l’application
serveur dans le dossier racine du projet .Client .

Pour activer l’interactivité globale lors de la création d’une Web App Blazor :

Visual Studio : définissez la liste déroulante Emplacement d'interactivité sur


Global.
CLI .NET : utilisez l’option -ai|--all-interactive .

Pour plus d’informations, consultez Outils pour ASP.NET Core Blazor.

Appliquer un mode de rendu


programmatiquement
Les propriétés et les champs peuvent affecter un mode de rendu.

La deuxième approche décrite dans cette section, qui consiste à définir le mode de
rendu par instance de composant, est particulièrement utile lorsque les spécifications de
votre application prévoient l’un ou l’autre des scénarios suivants :

Vous avez une zone (un dossier) de l’application avec des composants qui doivent
adopter un rendu statique côté serveur (SSR statique) et ne s’exécuter que sur le
serveur. L’application contrôle le mode de rendu globalement en définissant le
mode de rendu sur le Routes composant dans le App composant en fonction du
chemin d’accès au dossier.
Vous disposez de composants autour de l’application à différents emplacements
(pas dans un seul dossier) qui doivent adopter la SSR statique et s’exécuter
uniquement sur le serveur. L’application contrôle le mode de rendu par composant
en définissant le mode de rendu avec la directive dans les @rendermode instances
de composant. La réflexion est utilisée dans le App composant pour définir le mode
de rendu du Routes composant.
Dans les deux cas, le composant qui doit adopter le SSR statique doit également forcer
un rechargement de page complète.

Les deux scénarios précédents sont abordés avec des exemples dans la section Contrôle
précis des modes de rendu plus loin dans cet article. Les deux sous-sections suivantes se
concentrent sur les approches de base pour définir le mode de rendu.

Définir le mode de rendu par définition de composant


Une définition de composant peut définir un mode de rendu via un champ privé :

razor

@rendermode renderModeForPage

...

@code {
private static IComponentRenderMode renderModeForPage =
InteractiveServer;
}

Définir le mode de rendu par instance de composant


L’exemple suivant applique le rendu interactif côté serveur (SSR interactif) à tout
requête.

razor

<Routes @rendermode="RenderModeForPage" />

...

@code {
private IComponentRenderMode? RenderModeForPage => InteractiveServer;
}

Des informations supplémentaires sur la propagation du mode de rendu sont fournies


dans la section Propagation du mode de rendu plus loin dans cet article. La section
Contrôle précis des modes de rendu montre comment utiliser l’approche précédente
pour adopter la SSR statique dans des zones spécifiques de l’application (dossiers) ou
pour des composants spécifiques répartis autour de l’application avec des affectations
de mode de rendu par composant.
Prérendu
Le prérendu est le processus de rendu initial du contenu d’une page sur le serveur sans
activation des gestionnaires d’événements pour les contrôles rendus. Le serveur génère
l’interface utilisateur HTML de la page dès que possible en réponse à la demande
initiale, ce qui rend l’application plus réactive pour les utilisateurs. Le prérendu peut
aussi améliorer l’optimisation du référencement d’un site auprès d’un moteur de
recherche (SEO) en rendant le contenu de la réponse HTTP initiale qui est utilisée par
les moteurs de recherche pour calculer le rang de la page.

Le pré-rendu est activé par défaut pour les composants interactifs.

Pour désactiver le prérendu pour une instance de composant, transmettez l'indicateur


prerender avec une valeur de false au mode de rendu :

<... @rendermode="new InteractiveServerRenderMode(prerender: false)" />

<... @rendermode="new InteractiveWebAssemblyRenderMode(prerender: false)" />


<... @rendermode="new InteractiveAutoRenderMode(prerender: false)" />

Pour désactiver le prérendu dans une définition de composant :

@rendermode @(new InteractiveServerRenderMode(prerender: false))

@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))

@rendermode @(new InteractiveAutoRenderMode(prerender: false))

Pour désactiver le prérendu pour l’ensemble de l’application, indiquez le mode de rendu


au composant interactif de niveau le plus élevé dans la hiérarchie des composants de
l’application qui n’est pas un composant racine.

Pour les applications basées sur le modèle de projet Web App Blazor, un mode de rendu
affecté à l’ensemble de l’application est spécifié, où le composant Routes est utilisé dans
le composant App ( Components/App.razor ). L’exemple suivant montre comment définir le
mode de rendu de l’application sur Interactive Server avec désactivation du prérendu :

razor

<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />

Désactivez également le prérendu pour le composant HeadOutlet du composant App :

razor

<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)"


/>

Le fait de rendre un composant racine, tel que le composant App , interactif avec la
directive @rendermode en haut du fichier de définition du composant racine ( .razor )
n’est pas pris en charge. Par conséquent, le prérendu ne peut pas être désactivé
directement par le composant App .

Rendu statique côté serveur (SSR statique)


Par défaut, les composants utilisent le rendu côté serveur statique (SSR statique). Le
composant s’affiche dans le flux de réponses et l’interactivité n’est pas activé.

Dans l’exemple suivant, il n’existe aucune désignation pour le mode de rendu du


composant, et le composant hérite du mode de rendu par défaut de son parent. Par
conséquent, le composant est affiché de manière statique sur le serveur. Le bouton n’est
pas interactif et n’appelle pas la méthode UpdateMessage lorsqu’il est sélectionné. La
valeur de message ne change pas et le composant n’est pas affiché à nouveau en
réponse aux événements d’interface utilisateur.

RenderMode1.razor :

razor

@page "/render-mode-1"

<button @onclick="UpdateMessage">Click me</button> @message

@code {
private string message = "Not clicked yet.";

private void UpdateMessage()


{
message = "Somebody clicked me!";
}
}

Si vous utilisez le composant précédent localement dans une application web Blazor,
placez le composant dans le dossier Components/Pages du projet serveur. Le projet
serveur est le projet de la solution avec un nom qui ne se termine pas par .Client .
Lorsque l’application est en cours d’exécution, accédez à /render-mode-1 dans la barre
d’adresse du navigateur.

La navigation améliorée avec SSR statique nécessite une attention particulière lors du
chargement de JavaScript. Pour plus d’informations, consultez JavaScript Blazor ASP.NET
Core avec rendu côté serveur statique (SSR statique).

Rendu côté serveur interactif (SSR interactif)


Le rendu côté serveur interactif (SSR interactif) affiche le composant de manière
interactive à partir du serveur en utilisant Blazor Server. Les interactions avec l’utilisateur
sont gérées via une connexion en temps réel avec le navigateur. La connexion de circuit
est établie lorsque le composant Serveur est affiché.

Dans l’exemple suivant, le mode de rendu est défini sur SSR interactif en ajoutant
@rendermode InteractiveServer à la définition du composant. Le bouton appelle la

méthode UpdateMessage lorsqu’il est sélectionné. La valeur message est modifiée et le


composant est affiché à nouveau pour mettre à jour le message dans l’interface
utilisateur.

RenderMode2.razor :

razor

@page "/render-mode-2"
@rendermode InteractiveServer

<button @onclick="UpdateMessage">Click me</button> @message

@code {
private string message = "Not clicked yet.";

private void UpdateMessage()


{
message = "Somebody clicked me!";
}
}

Si vous utilisez le composant précédent localement dans une application web Blazor,
placez le composant dans le dossier Components/Pages du projet serveur. Le projet
serveur est le projet de la solution avec un nom qui ne se termine pas par .Client .
Lorsque l’application est en cours d’exécution, accédez à /render-mode-2 dans la barre
d’adresse du navigateur.

Rendu côté client (CSR)


Le rendu côté client (CSR) affiche le composant de manière interactive sur le client en
utilisant Blazor WebAssembly. Le runtime .NET et l’ensemble d'applications sont
téléchargés et mis en cache lorsque le composant WebAssembly est initialement affiché.
Les composants utilisant CSR doivent être générés à partir d’un projet client distinct qui
configure l’hôte Blazor WebAssembly.

Dans l’exemple suivant, le mode de rendu est défini sur CSR avec @rendermode
InteractiveWebAssembly . Le bouton appelle la méthode UpdateMessage lorsqu’il est

sélectionné. La valeur message est modifiée et le composant est affiché à nouveau pour
mettre à jour le message dans l’interface utilisateur.

RenderMode3.razor :

razor

@page "/render-mode-3"
@rendermode InteractiveWebAssembly

<button @onclick="UpdateMessage">Click me</button> @message

@code {
private string message = "Not clicked yet.";

private void UpdateMessage()


{
message = "Somebody clicked me!";
}
}

Si vous utilisez le composant précédent localement dans une application web Blazor,
placez le composant dans le dossier Pages du projet client. Le projet client est le projet
de la solution dont le nom se termine par .Client . Lorsque l’application est en cours
d’exécution, accédez à /render-mode-3 dans la barre d’adresse du navigateur.

Rendu automatique (Auto)


Le rendu automatique (Auto) détermine comment afficher le composant au moment de
l’exécution. Le composant est initialement affiché avec le rendu côté serveur interactif
(SSR interactif) en utilisant le modèle d’hébergement Blazor Server. Le runtime .NET et
l’ensemble d'applications sont téléchargés sur le client en arrière-plan et mis en cache
afin de pouvoir être utilisés lors de visites ultérieures.

Le mode de rendu automatique ne modifie jamais dynamiquement le mode de rendu


d’un composant déjà sur la page. Le mode de rendu automatique prend une décision
initiale sur le type d’interactivité à utiliser pour un composant, puis le composant
conserve ce type d’interactivité tant qu’il se trouve sur la page. L’un des facteurs de cette
décision initiale consiste à déterminer si les composants existent déjà sur la page avec
l’interactivité WebAssembly/Server. Le mode automatique préfère sélectionner un mode
de rendu qui correspond au mode de rendu des composants interactifs existants. La
raison pour laquelle le mode automatique préfère utiliser un mode d’interactivité
existant consiste à éviter d’introduire un nouveau runtime interactif qui ne partage pas
l’état avec le runtime existant.

Les composants utilisant le mode de rendu Auto doivent être générés à partir d’un
projet client distinct qui configure l’hôte Blazor WebAssembly.

Dans l’exemple suivant, le composant est interactif tout au long du processus. Le bouton
appelle la méthode UpdateMessage lorsqu’il est sélectionné. La valeur message est
modifiée et le composant est affiché à nouveau pour mettre à jour le message dans
l’interface utilisateur. Initialement, le composant est affiché de manière interactive à
partir du serveur, mais lors des visites suivantes, il est affiché à partir du client après que
le runtime .NET et l’ensemble d’applications ont été téléchargés et mis en cache.

RenderMode4.razor :

razor

@page "/render-mode-4"
@rendermode InteractiveAuto

<button @onclick="UpdateMessage">Click me</button> @message

@code {
private string message = "Not clicked yet.";

private void UpdateMessage()


{
message = "Somebody clicked me!";
}
}

Si vous utilisez le composant précédent localement dans une application web Blazor,
placez le composant dans le dossier Pages du projet client. Le projet client est le projet
de la solution dont le nom se termine par .Client . Lorsque l’application est en cours
d’exécution, accédez à /render-mode-4 dans la barre d’adresse du navigateur.

Propagation du mode de rendu


Les modes de rendu se propagent dans la hiérarchie des composants.

Règles d’application des modes de rendu :


Le mode de rendu par défaut est Statique.
Les modes de rendu Serveur interactif (InteractiveServer), WebAssembly interactif
(InteractiveWebAssembly) et Auto interactif (InteractiveAuto) peuvent être utilisés
à partir d’un composant, notamment en utilisant des modes de rendu différents
pour les composants frères.
Vous ne pouvez pas basculer vers un autre mode de rendu interactif dans un
composant enfant. Par exemple, un composant Serveur ne peut pas être un enfant
d’un composant WebAssembly.
Les paramètres transmis à un composant enfant interactif à partir d'un parent
statique doivent être sérialisables JSON. Cela signifie que vous ne pouvez pas
transmettre de fragments de rendu ou de contenu enfant d’un composant parent
statique à un composant enfant interactif.

Les exemples suivants utilisent un composant SharedMessage non routable et non page.
Le composant SharedMessage indépendant du mode de rendu n’applique pas de mode
de rendu avec une directive @attribute. Si vous testez ces scénarios avec une
application web Blazor, placez le composant suivant dans le dossier Components de
l’application.

SharedMessage.razor :

razor

<p>@Greeting</p>

<button @onclick="UpdateMessage">Click me</button> @message

<p>@ChildContent</p>

@code {
private string message = "Not clicked yet.";

[Parameter]
public RenderFragment? ChildContent { get; set; }

[Parameter]
public string Greeting { get; set; } = "Hello!";

private void UpdateMessage()


{
message = "Somebody clicked me!";
}
}

Héritage du mode de rendu


Si le composant SharedMessage est placé dans un composant parent rendu
statiquement, le composant SharedMessage est également affiché de manière statique et
n’est pas interactif. Le bouton n’appelle pas UpdateMessage et le message n’est pas mis à
jour.

RenderMode5.razor :

razor

@page "/render-mode-5"

<SharedMessage />

Si le composant SharedMessage est placé dans un composant qui définit le mode de


rendu, il hérite du mode de rendu appliqué.

Dans l’exemple suivant, le composant SharedMessage est interactif via une connexion
SignalR au client. Le bouton appelle UpdateMessage et le message est mis à jour.

RenderMode6.razor :

razor

@page "/render-mode-6"
@rendermode InteractiveServer

<SharedMessage />

Composants enfants avec différents modes de rendu


Dans l’exemple suivant, les deux composants SharedMessage sont pré-rendus (par
défaut) et apparaissent lorsque la page est affichée dans le navigateur.

Le premier composant SharedMessage avec rendu côté serveur interactif (SSR


interactif) est interactif une fois le circuit SignalR établi.
Le deuxième composant SharedMessage avec rendu côté client (CSR) est interactif
une fois l’ensemble d’applications Blazor téléchargé et le runtime .NET actif sur le
client.

RenderMode7.razor :

razor
@page "/render-mode-7"

<SharedMessage @rendermode="InteractiveServer" />


<SharedMessage @rendermode="InteractiveWebAssembly" />

Composant enfant avec un paramètre sérialisable


L’exemple suivant illustre un composant enfant interactif qui prend un paramètre. Les
paramètres doivent être sérialisables.

RenderMode8.razor :

razor

@page "/render-mode-8"

<SharedMessage @rendermode="InteractiveServer" Greeting="Welcome!" />

Les paramètres de composants non sérialisables, tels que le contenu enfant ou un


fragment de rendu, ne sont pas pris en charge. Dans l’exemple suivant, le passage du
contenu enfant au composant SharedMessage entraîne une erreur de runtime.

RenderMode9.razor :

razor

@page "/render-mode-9"

<SharedMessage @rendermode="InteractiveServer">
Child content
</SharedMessage>

❌Erreur :

System.InvalidOperationException : impossible de transmettre le paramètre


« ChildContent » au composant « SharedMessage » avec le mode de rendu
« InteractiveServerRenderMode ». Cela est dû au fait que le paramètre est de type
délégué « Microsoft.AspNetCore.Components.RenderFragment », qui est un code
arbitraire et ne peut pas être sérialisé.

Pour contourner la limitation précédente, enveloppez le composant enfant dans un


autre composant qui n’a pas le paramètre. Il s’agit de l’approche adoptée dans le
modèle de projet de l’application web Blazor avec le composant Routes
( Components/Routes.razor ) pour envelopper le composant Router.

WrapperComponent.razor :

razor

<SharedMessage>
Child content
</SharedMessage>

RenderMode10.razor :

razor

@page "/render-mode-10"

<WrapperComponent @rendermode="InteractiveServer" />

Dans l'exemple précédent :

Le contenu enfant est transmis au composant SharedMessage sans générer d’erreur


de runtime.
Le composant SharedMessage s’affiche de manière interactive sur le serveur.

Composant enfant avec un mode de rendu différent de


son parent
N’essayez pas d’appliquer à un composant enfant un mode de rendu interactif différent
de celui de son parent.

Le composant suivant génère une erreur de runtime lorsque le composant est rendu :

RenderMode11.razor :

razor

@page "/render-mode-11"
@rendermode InteractiveServer

<SharedMessage @rendermode="InteractiveWebAssembly" />

❌Erreur :
Cannot create a component of type 'BlazorSample.Components.SharedMessage'
because its render mode
'Microsoft.AspNetCore.Components.Web.InteractiveWebAssemblyRenderMode' is
not supported by Interactive Server rendering.

Contrôle précis des modes de rendu


Dans certains cas, les spécifications de l’application prévoient que les composants
adoptent un rendu statique côté serveur (static SSR) et ne s’exécutent que sur le serveur,
tandis que le reste de l’application utilise un mode de rendu interactif.

Deux approches peuvent être adoptées pour le contrôle fin des modes de rendu,
chacune d’entre elles étant décrite dans les sous-sections suivantes :

Zone (dossier) des composants SSR statiques : vous disposez d’une zone (dossier)
de l’application avec des composants qui doivent adopter le SSR statique et
partager le même préfixe de chemin d’accès d’itinéraire. L’application contrôle le
mode de rendu globalement en définissant le mode de rendu sur le Routes
composant dans le App composant en fonction du chemin d’accès au dossier.

Composants SSR statiques répartis sur l’application : vous avez des composants
répartis dans l’application à différents emplacements qui doivent adopter le SSR
statique et s’exécuter uniquement sur le serveur. Les composants statiques du SSR
ne se trouvent pas dans un dossier unique et ne partagent pas le même préfixe de
chemin d’accès. L’application contrôle le mode de rendu par composant en
définissant le mode de rendu avec la directive dans les @rendermode instances de
composant. La réflexion est utilisée dans le App composant pour définir le mode de
rendu du Routes composant.

Dans les deux cas, le composant qui doit adopter le SSR statique doit également forcer
un rechargement de page complète.

Les exemples suivants utilisent le HttpContext paramètre en cascade pour déterminer si


la page est rendue statiquement. Un null HttpContext indique que le composant a un
rendu interactif, ce qui est utile comme signal dans le code de l’application pour
déclencher un rechargement complet de la page.

Zone (dossier) des composants SSR statiques


L’approche décrite dans cette sous-section est utilisée par le modèle de Blazor projet
Web App avec authentification individuelle et interactivité globale.
Une zone (dossier) de l’application contient les composants qui doivent adopter un SSR
statique et ne s’exécuter que sur le serveur. Les composants du dossier partagent le
même préfixe de chemin d’accès. Par exemple, les IdentityRazor composants du Blazor
modèle de projet Web App se trouvent dans le Components/Account/Pages dossier et
partagent le préfixe du chemin d’accès racine /Account .

Le dossier contient également un fichier _Imports.razor , qui applique un layout de


compte personnalisé aux composants du dossier :

razor

@using BlazorSample.Components.Account.Shared
@layout AccountLayout

Le dossier Shared gère le composant de layout AccountLayout . Le composant utilise


HttpContext pour déterminer si le composant est rendu sur le serveur.
Identity composants doivent s’afficher sur le serveur avec un SSR statique, car ils
définissent Identitycookies. Si la valeur est HttpContext null , le composant est rendu de
manière interactive et un rechargement de page complète est effectué en appelant
NavigationManager.Refresh avec forceLoad la valeur définie sur true . Cela force une
nouvelle version complète de la page à l’aide de la fonction SSR statique.

Components/Account/Shared/AccountLayout.razor :

razor

@inherits LayoutComponentBase
@layout BlazorSample.Components.Layout.MainLayout
@inject NavigationManager NavigationManager

@if (HttpContext is null)


{
<p>Loading...</p>
}
else
{
@Body
}

@code {
[CascadingParameter]
private HttpContext? HttpContext { get; set; }

protected override void OnParametersSet()


{
if (HttpContext is null)
{
NavigationManager.Refresh(forceReload: true);
}
}
}

7 Notes

Dans le Blazor modèle de projet Web App, il existe un deuxième fichier de layout
( ManageLayout.razor dans le Components/Account/Shared dossier) pour Identity les
composants du Components/Account/Pages/Manage dossier. Le Manage dossier
possède son propre _Imports.razor fichier à appliquer aux ManageLayout
composants du dossier. Dans vos propres applications, l’utilisation de fichiers
imbriqués _Imports.razor est une approche utile pour appliquer des layouts
personnalisés à des groupes de pages.

Dans le App composant, toute requête d’un composant dans le Account dossier
applique un null mode de rendu, qui applique la SSR statique. D’autres demandes de
composant reçoivent une application globale du mode de rendu SSR interactif
( InteractiveServer ).

) Important

L’application d’un null mode de rendu n’applique pas toujours la SSR statique. Il
arrive simplement de se comporter de cette façon à l’aide de l’approche présentée
dans cette section.

Un null mode de rendu est effectivement identique à ne pas spécifier de mode de


rendu, ce qui entraîne l’héritage du mode de rendu de son parent. Dans ce cas, le
App composant est rendu à l’aide du SSR statique. Par conséquent, un null mode

de rendu entraîne l’héritage Routes du SSR statique du App composant. Si un


mode de rendu nul est spécifié pour un composant enfant dont le parent utilise un
mode de rendu interactif, l’enfant hérite du même mode de rendu interactif.

Components/App.razor :

razor

<Routes @rendermode="RenderModeForPage" />

...

@code {
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;

private IComponentRenderMode? RenderModeForPage =>


HttpContext.Request.Path.StartsWithSegments("/Account")
? null
: {INTERACTIVE RENDER MODE};
}

Dans le code précédent, remplacez l’espace {INTERACTIVE RENDER MODE} réservé par la
valeur appropriée, selon que le reste de l’application doit adopter le rendu global
InteractiveServer, InteractiveWebAssemblyou le rendu InteractiveAuto.

Les composants qui doivent adopter le SSR statique dans le Account dossier ne sont pas
nécessaires pour définir le layout, car elles sont appliquées via le _Imports.razor fichier
et les composants ne définissent pas de mode de rendu, car ils doivent s’afficher avec le
SSR statique. Rien d’autre ne doit être effectué pour les composants du Account dossier
afin d’appliquer la SSR statique.

Composants SSR statiques répartis dans l’application


Dans la sous-section précédente, l’application contrôle le mode de rendu des
composants en définissant le mode de rendu globalement dans le composant App . App
Le composant peut également adopter des modes de rendu par composant pour définir
le mode de rendu, ce qui permet aux composants répartis autour de l’application
d’appliquer l’adoption de la SSR statique. Cette sous-section décrit l’approche.

L’application a un layout personnalisé qui peut être appliqué aux composants autour de
l’application. En règle générale, un composant partagé pour l’application est placé dans
le dossier Components/Layout . Le composant utilise HttpContext pour déterminer si le
composant est rendu sur le serveur. Si la valeur est HttpContext null , le composant est
rendu de manière interactive et un rechargement de page complète est effectué en
appelant NavigationManager.Refresh avec forceLoad la valeur définie sur true . Cela
déclenche une requête au serveur pour le composant.

Components/Layout/StaticSsrLayout.razor :

razor

@inherits LayoutComponentBase
@layout MainLayout
@inject NavigationManager NavigationManager

@if (HttpContext is null)


{
<p>Loading...</p>
}
else
{
@Body
}

@code {
[CascadingParameter]
private HttpContext? HttpContext { get; set; }

protected override void OnParametersSet()


{
if (HttpContext is null)
{
NavigationManager.Refresh(forceReload: true);
}
}
}

Dans le composant App , la réflexion est utilisée pour définir le mode de rendu. Quel que
soit le mode de rendu affecté au fichier de définition de composant individuel, il est
appliqué au composant Routes .

Components/App.razor :

razor

<Routes @rendermode="RenderModeForPage" />

...

@code {
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;

private IComponentRenderMode? RenderModeForPage =>


HttpContext.GetEndpoint()?.Metadata.GetMetadata<RenderModeAttribute>
()?
.Mode;
}

Chaque composant qui doit adopter le SSR statique définit le layout personnalisée et ne
spécifie pas de mode de rendu. L’absence de spécification d’un mode de rendu entraîne
une null valeur du RenderModeAttribute.Mode dans le composant App , ce qui
n’entraîne aucun mode de rendu affecté à l’instance du composant Routes et à
l’application de la SSR statique.
) Important

L’application d’un null mode de rendu n’applique pas toujours la SSR statique. Il
arrive simplement de se comporter de cette façon à l’aide de l’approche présentée
dans cette section.

Un null mode de rendu est effectivement identique à ne pas spécifier de mode de


rendu, ce qui entraîne l’héritage du mode de rendu de son parent. Dans ce cas, le
App composant est rendu à l’aide du SSR statique. Par conséquent, un null mode

de rendu entraîne l’héritage Routes du SSR statique du App composant. Si un


mode de rendu nul est spécifié pour un composant enfant dont le parent utilise un
mode de rendu interactif, l’enfant hérite du même mode de rendu interactif.

Rien d’autre ne doit être fait pour que les composants appliquent la SSR statique que
l’application de le layout personnalisé :

razor

@layout BlazorSample.Components.Layout.StaticSsrLayout

D’autres composants autour de l’application définissent un mode de rendu interactif


approprié, qui, lors de la réflexion dans le App composant, est appliqué au composant
Routes . Les composants interactifs évitent d’appliquer la layouts SSR statiques

personnalisés :

razor

@rendermode {INTERACTIVE RENDER MODE}

Dans le code précédent, remplacez l’espace réservé {INTERACTIVE RENDER MODE} par la
valeur appropriée, selon que le reste de l’application doit adopter le rendu global
InteractiveServer, InteractiveWebAssemblyou le rendu InteractiveAuto.

Les services côté client ne parviennent pas à


être résolus lors du prérendu
En supposant que le prérendu n’est pas désactivé pour un composant ou pour
l’application, un composant du projet .Client est prérendu sur le serveur. Le serveur
n’ayant pas accès aux services Blazor côté client inscrits, il n’est pas possible d’injecter
ces services dans un composant sans recevoir d’erreur indiquant que le service n’a pas
pu être trouvé lors du prérendu.

Par exemple, considérez le composant Home suivant dans le projet .Client d’une Web
App Blazor avec rendu global Interactive WebAssembly ou Interactive Auto. Le
composant tente d’injecter IWebAssemblyHostEnvironment pour obtenir le nom de
l’environnement.

razor

@page "/"
@inject IWebAssemblyHostEnvironment Environment

<PageTitle>Home</PageTitle>

<h1>Home</h1>

<p>
Environment: @Environment.Environment
</p>

Aucune erreur de temps de compilation ne se produit, mais une erreur de runtime se


produit pendant le prérendu :

Impossible de fournir une valeur pour la propriété « Environment » sur le type


« BlazorSample.Client.Pages.Home ». Il n’existe aucun service inscrit de type «
Microsoft.AspNetCore.Components.WebAssembly.Hosting.IWebAssemblyHostEnviro
nment ».

Cette erreur se produit, car le composant doit compiler et s’exécuter sur le serveur
pendant le prérendu, mais IWebAssemblyHostEnvironment n’est pas un service inscrit
sur le serveur.

Si l’application n’a pas besoin de la valeur pendant le prérendu, ce problème peut être
résolu en injectant IServiceProvider pour obtenir le service au lieu du type de service lui-
même :

razor

@page "/"
@using Microsoft.AspNetCore.Components.WebAssembly.Hosting
@inject IServiceProvider Services

<PageTitle>Home</PageTitle>

<h1>Home</h1>
<p>
<b>Environment:</b> @environmentName
</p>

@code {
private string? environmentName;

protected override void OnInitialized()


{
if (Services.GetService<IWebAssemblyHostEnvironment>() is { } env)
{
environmentName = env.Environment;
}
}
}

Toutefois, l’approche précédente n’est pas utile si votre logique nécessite une valeur
pendant le prérendu.

Vous pouvez également éviter le problème si vous désactivez le prérendu pour le


composant, mais c’est une mesure extrême à prendre dans de nombreux cas qui
peuvent ne pas répondre aux spécifications de votre composant.

Il existe trois approches que vous pouvez prendre pour aborder ce scénario. Les
éléments suivants sont listés du plus recommandé au moins recommandé :

Recommandé pour les services de framework partagé : pour les services de


framework partagé qui ne sont simplement pas inscrits côté serveur dans le projet
principal, inscrivez les services dans le projet principal, ce qui les rend disponibles
lors de la préversion. Pour obtenir un exemple de ce scénario, consultez les
conseils pour les services HttpClient dans Appeler une API web à partir d’une
application Blazor ASP.NET Core.

Recommandé pour les services en dehors du cadre partagé : créez une


implémentation de service personnalisé pour le service sur le serveur. Utilisez
normalement le service dans les composants interactifs du projet .Client . Pour
une démonstration de cette approche, consultez ASP.NET Core Blazor
environnements.

Créez une abstraction de service et créez des implémentations pour le service dans
les projets .Client et serveur. Inscrivez les services dans chaque projet. Injectez le
service personnalisé dans le composant.

Vous pouvez peut-être ajouter une référence de package de projet .Client à un


package côté serveur et revenir à l’utilisation de l’API côté serveur pendant le
prérendu sur le serveur.

Découvrir des composants d’assemblys


supplémentaires
Des assemblys supplémentaires doivent être divulgués à l’infrastructure Blazor pour
découvrir les composants Razor routables dans les projets référencés. Pour plus
d’informations, consultez Routage et navigation ASP.NET Core Blazor.

Fermeture des circuits quand il ne reste plus de


composants de serveur interactif
Les composants de serveur interactif gèrent les événements de l’interface utilisateur web
en utilisant une connexion en temps réel avec le navigateur, appelée un circuit. Un
circuit et son état associé sont créés quand un composant de serveur interactif racine
est rendu. Le circuit est fermé quand il ne reste plus de composants de serveur interactif
sur la page, ce qui libère des ressources du serveur.

Modes de rendu abrégés personnalisés


La directive @rendermode prend un seul paramètre qui est une instance statique de type
IComponentRenderMode. L'attribut directive @rendermode peut prendre n'importe
quelle instance de mode de rendu, statique ou non. Le framework Blazor fournit à la
classe statique RenderMode avec certains modes de rendu prédéfinis pour plus de
commodité, mais vous pouvez créer les vôtres.

Normalement, un composant utilise la directive @rendermode suivante pour désactiver le


prérendu :

razor

@rendermode @(new InteractiveServerRenderMode(prerender: false))

Toutefois, considérez l’exemple suivant qui crée un mode de rendu côté serveur
interactif abrégé sans prérendu via le fichier _Imports de l’application
( Components/_Imports.razor ) :

C#
public static IComponentRenderMode InteractiveServerWithoutPrerendering {
get; } =
new InteractiveServerRenderMode(prerender: false);

Utilisez le mode de rendu abrégé dans les composants dans tout le dossier Components :

razor

@rendermode InteractiveServerWithoutPrerendering

Une instance à composant unique peut également définir un mode de rendu


personnalisé via un champ privé :

razor

@rendermode interactiveServerWithoutPrerendering

...

@code {
private static IComponentRenderMode interactiveServerWithoutPrerendering
=
new InteractiveServerRenderMode(prerender: false);
}

Pour le moment, l'approche du mode de rendu abrégé n'est probablement utile que
pour réduire la verbosité de la spécification de l'indicateur prerender . L'approche
abrégée pourrait être plus utile à l'avenir si des indicateurs supplémentaires deviennent
disponibles pour le rendu interactif et si vous souhaitez créer des modes de rendu
abrégés avec différentes combinaisons d'indicateurs.

Ressources supplémentaires
JavaScript ASP.NET CoreBlazor avec rendu côté serveur statique (SSR statique)
Valeurs/paramètres en cascade et limites du mode de rendu : consultez aussi la
section Paramètres en cascade au niveau racine plus haut dans l’article.
Bibliothèques de classes ASP.NET Core Razor (RCL) avec rendu statique côté
serveur (SSR statique)

6 Collaborer avec nous sur Commentaires sur ASP.NET


Core
GitHub ASP.NET Core est un projet open
source. Sélectionnez un lien pour
La source de ce contenu se
fournir des commentaires :
trouve sur GitHub, où vous
pouvez également créer et  Ouvrir un problème de
examiner les problèmes et les
documentation
demandes de tirage. Pour plus
d’informations, consultez notre
 Indiquer des commentaires sur
guide du contributeur.
le produit
Prérendu des composants ASP.NET Core
Razor
Article • 09/02/2024

Cet article explique les scénarios de prérendu des composants Razor pour les
composants rendus par serveur dans les Web Apps Blazor.

Le prérendu est le processus de rendu initial du contenu d’une page sur le serveur sans
activation des gestionnaires d’événements pour les contrôles rendus. Le serveur génère
l’interface utilisateur HTML de la page dès que possible en réponse à la demande
initiale, ce qui rend l’application plus réactive pour les utilisateurs. Le prérendu peut
aussi améliorer l’optimisation du référencement d’un site auprès d’un moteur de
recherche (SEO) en rendant le contenu de la réponse HTTP initiale qui est utilisée par
les moteurs de recherche pour calculer le rang de la page.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : dans une application web Blazor, rendu côté serveur
interactif (SSR interactif) qui fonctionne sur une connexion SignalR avec le client ou
rendu côté serveur statique (SSR statique).
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un composant doit avoir un mode de rendu
interactif appliqué pour l’interactivité sur une connexion SignalR avec le client,
dans le fichier de définition du composant ou hérité d’un composant parent. Les
composants qui ne définissent pas de mode de rendu ou qui n’en héritent pas
sont rendus avec un SSR statique sur le serveur. Aucune connexion SignalR n’est
établie pour les composants rendus statiquement. Pour plus d’informations,
consultez Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.
Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du Blazor référentiel d’exemples
GitHub qui correspond à la version de .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Conserver l’état prérendu


Si l’état utilisé durant le prérendu n’est pas conservé, il est perdu et doit être recréé
lorsque l’application est entièrement chargée. Si un état est créé de façon asynchrone,
l’interface utilisateur peut scintiller pendant que l’IU prérendue est remplacée alors que
le composant régénère le rendu.

Considérez le compteur de composants PrerenderedCounter1 suivant. Le composant


définit une valeur de compteur aléatoire initiale pendant un prérendu dans une
méthode de cycle de vie OnInitialized. Une fois la connexion SignalR avec le client
établie, le composant régénère le rendu et la valeur du compteur initiale est remplacée
lorsque OnInitialized s’exécute une seconde fois.

PrerenderedCounter1.razor :

razor

@page "/prerendered-counter-1"
@rendermode @(new InteractiveServerRenderMode(prerender: true))
@inject ILogger<PrerenderedCounter1> Logger

<PageTitle>Prerendered Counter 1</PageTitle>

<h1>Prerendered Counter 1</h1>

<p role="status">Current count: @currentCount</p>


<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount;
private Random r = new Random();

protected override void OnInitialized()


{
currentCount = r.Next(100);
Logger.LogInformation("currentCount set to {Count}", currentCount);
}

private void IncrementCount()


{
currentCount++;
}
}

Exécutez l’application et inspectez la journalisation à partir du composant. Voici un


exemple de sortie.

7 Notes

Si l’application adopte un routage interactif (amélioré) et que la page est atteinte


par le biais d’une navigation interne, le prérendu ne se produit pas. Par conséquent,
vous devez effectuer un rechargement de page complet pour que le composant
PrerenderedCounter1 affiche la sortie suivante.

info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 41
info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 92

Le premier compte journalisé a lieu lors du prérendu. Le compte est à nouveau défini
après le prérendu lorsque le composant régénère le rendu. Un scintillement dans
l’interface utilisateur est également possible lorsque le compte passe de 41 à 92.

Pour conserver la valeur initiale du compteur lors du prérendu, Blazor prend en charge
l’état persistant dans une page prérendue à l’aide du service PersistentComponentState
(et pour les composants incorporés dans des pages ou des vues d’applications Razor
Pages ou MVC, via le Tag Helper d’état du composant persistant).

Pour conserver l’état prérendu, décidez de l’état à conserver à l’aide du service


PersistentComponentState. PersistentComponentState.RegisterOnPersisting enregistre
un rappel pour conserver l’état du composant avant que l’application ne soit suspendue.
L’état est récupéré lorsque l’application reprend.

L’exemple suivant illustre le modèle général :

L’espace réservé {TYPE} représente le type de données à conserver.


L’espace réservé {TOKEN} est une chaîne de l’identificateur d’état.

razor

@implements IDisposable
@inject PersistentComponentState ApplicationState

...

@code {
private {TYPE} data;
private PersistingComponentStateSubscription persistingSubscription;

protected override async Task OnInitializedAsync()


{
persistingSubscription =
ApplicationState.RegisterOnPersisting(PersistData);

if (!ApplicationState.TryTakeFromJson<{TYPE}>(
"{TOKEN}", out var restored))
{
data = await ...;
}
else
{
data = restored!;
}
}

private Task PersistData()


{
ApplicationState.PersistAsJson("{TOKEN}", data);

return Task.CompletedTask;
}

void IDisposable.Dispose()
{
persistingSubscription.Dispose();
}
}

Le composant compteur suivant conserve l’état du compteur pendant le prérendu, puis


récupère l’état pour initialiser le composant.
PrerenderedCounter2.razor :

razor

@page "/prerendered-counter-2"
@implements IDisposable
@inject ILogger<PrerenderedCounter2> Logger
@inject PersistentComponentState ApplicationState

<PageTitle>Prerendered Counter 2</PageTitle>

<h1>Prerendered Counter 2</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount;
private Random r = new Random();
private PersistingComponentStateSubscription persistingSubscription;

protected override void OnInitialized()


{
persistingSubscription =
ApplicationState.RegisterOnPersisting(PersistCount);

if (!ApplicationState.TryTakeFromJson<int>(
"count", out var restoredCount))
{
currentCount = r.Next(100);
Logger.LogInformation("currentCount set to {Count}",
currentCount);
}
else
{
currentCount = restoredCount!;
Logger.LogInformation("currentCount restored to {Count}",
currentCount);
}
}

private Task PersistCount()


{
ApplicationState.PersistAsJson("count", currentCount);

return Task.CompletedTask;
}

void IDisposable.Dispose()
{
persistingSubscription.Dispose();
}
private void IncrementCount()
{
currentCount++;
}
}

Lorsque le composant s’exécute, currentCount n’est défini que lors du prérendu. La


valeur est restaurée lors du nouveau rendu du composant. Voici un exemple de sortie.

7 Notes

Si l’application adopte un routage interactif et que la page est atteinte par le biais
d’une navigation interne, le prérendu ne se produit pas. Par conséquent, vous
devez effectuer un rechargement de page complet pour que le composant
PrerenderedCounter2 affiche la sortie suivante.

info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount restored to 96

En initialisant des composants avec le même état que celui utilisé durant le prérendu,
toutes les étapes d’initialisation coûteuses ne sont exécutées qu’une seule fois.
L’interface utilisateur rendue correspond également à l’interface utilisateur prérendue,
de sorte qu’aucun scintillement ne se produit dans le navigateur.

Composants incorporés dans des pages et des


vues (MVC/Pages Razor)
Pour les composants incorporés dans une page ou une vue d’une application Razor
Pages ou MVC, vous devez ajouter le Tag Helper d’état du composant persistant avec la
balise HTML <persist-component-state /> à l’intérieur de la balise fermante </body> du
layout de l’application. Cela n’est nécessaire que pour les applications Razor Pages et
MVC. Pour plus d’informations, consultez l’assistance Tag Helper d’état du composant
persistant dans ASP.NET Core.

Pages/Shared/_Layout.cshtml :

CSHTML
<body>
...

<persist-component-state />
</body>

Routage interactif et prérendu


La navigation interne pour le routage interactif n’implique pas de demander le nouveau
contenu de la page auprès du serveur. Par conséquent, un prérendu n’est pas effectué
pour les demandes de pages internes.

Le service PersistentComponentState fonctionne uniquement au chargement initial de la


page et non sur les événements de navigation de page améliorés. Si l’application
effectue une navigation complète (non améliorée) vers une page utilisant l’état du
composant persistant, l’état persistant est mis à la disposition de l’application lorsqu’elle
devient interactive. Toutefois, si un circuit interactif a déjà été établi et qu’une navigation
améliorée est effectuée sur une page qui rend l’état du composant persistant, cet état
n’est pas mis à disposition dans le circuit existant. Actuellement, le service
PersistentComponentState ne connaît pas la navigation améliorée, et il n’existe aucun
mécanisme permettant de fournir des mises à jour d’état aux composants déjà en cours
d’exécution.

Conseils de prérendu
Les conseils de prérendu sont organisés par sujets dans la documentation de Blazor. Les
liens suivants couvrent tous les conseils de prérendu dans la documentation organisée
par sujets :

Notions de base
OnNavigateAsync est exécuté deux fois lors du prérendu : gérer les événements
de navigation asynchrone avec OnNavigateAsync
Démarrage : contrôle des en-têtes dans le code C#
Gérer les erreurs : prérendu
SignalR :Taille d’état prérendu et limite de taille du message SignalR

Modes de rendu : Prérendu

Composants
Contrôlez le contenu <head> pendant le prérendu
Sujets de cycle de vie des composants Razor qui se rapportent au prérendu
Initialisation des composants (OnInitialized{Async})
Après le rendu de composant (OnAfterRender{Async})
Reconnexion avec état après le prérendu
Prérendu avec l’interopérabilité JavaScript : cette section apparaît également
dans les deux articles d’interopérabilité JS sur l’appel JavaScript à partir de
.NET et l’appel de .NET à partir de JavaScript.
Exemple d’application de composant QuickGrid : QuickGrid pour l’exemple
d’application Blazor est hébergé sur les pages GitHub. Le site se charge
rapidement grâce au pré-rendu statique utilisant le
BlazorWasmPrerendering.Buildprojet GitHub géré par la communauté.
Prérendu lors de l’intégration de composants dans des applications Razor Pages
et MVC

Authentification et autorisation
Atténuation des menaces côté serveur : scripting inter-sites (XSS)
Affichage de contenu non autorisé côté serveur lors du prérendu avec un
AuthenticationStateProviderpersonnalisé
Authentification des composants rendus Blazor WebAssembly avec prérendu

Gestion de l’état : Gérer le prérendu : En plus de la section Gérer le preréndu,


plusieurs autres sections de l’article incluent des remarques sur le prérendu.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Prise en charge des types génériques de
composants Razor ASP.NET Core
Article • 21/12/2023

Cet article décrit la prise en charge des types génériques dans les composants Razor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode de rendu interactif avec une directive @rendermode dans le fichier de définition du
composant ( .razor ) :

Dans une application web Blazor, le composant doit avoir un mode de rendu
interactif appliqué, soit dans le fichier de définition du composant, soit hérité d’un
composant parent. Pour plus d’informations, consultez Modes de rendu ASP.NET
Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode de rendu, car
les composants s’exécutent toujours de manière interactive sur WebAssembly dans
une application Blazor WebAssembly.

Lors de l’utilisation des modes WebAssembly interactif ou Rendu automatique interactif,


le code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas
de code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration est de télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne sont pas inclus dans les exemples
d’applications, mais nous nous efforçons de transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Prise en charge des paramètres de type


générique
La directive @typeparam déclare un paramètre de type générique pour la classe de
composant générée :

razor

@typeparam TItem

La syntaxe C# avec les contraintes de type where est prise en charge :

razor

@typeparam TEntity where TEntity : IEntity

Dans l’exemple suivant, le composant ListGenericTypeItems1 a le type générique


TExample .

ListGenericTypeItems1.razor :

razor

@typeparam TExample

@if (ExampleList is not null)


{
<ul>
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}

@code {
[Parameter]
public IEnumerable<TExample>? ExampleList{ get; set; }
}
Le composant suivant génère le rendu de deux ListGenericTypeItems1 composants :

Des données de chaîne ou d’entier sont affectées au paramètre ExampleList de


chaque composant.
Le type string ou int qui correspond au type des données affectées est défini
pour le paramètre de type ( TExample ) de chaque composant.

GenericType1.razor :

razor

@page "/generic-type-1"

<PageTitle>Generic Type 1</PageTitle>

<h1>Generic Type Example 1</h1>

<ListGenericTypeItems1 ExampleList="@(new List<string> { "Item 1", "Item 2"


})"
TExample="string" />

<ListGenericTypeItems1 ExampleList="@(new List<int> { 1, 2, 3 })"


TExample="int" />

Pour en savoir plus, consultez les informations de référence sur la syntaxe Razor pour
ASP.NET Core. Pour obtenir un exemple de type générique avec des composants basés
sur un modèle, consultez les composants basés sur un modèle Blazor ASP.NET Core.

Prise en charge du type générique en cascade


Un composant ancêtre peut passer en cascade un paramètre de type par nom à des
descendants avec l’attribut [CascadingTypeParameter]. Cet attribut permet à une
inférence de type générique d’utiliser automatiquement le paramètre de type spécifié
avec les descendants qui ont un paramètre de type portant le même nom.

Si vous ajoutez @attribute [CascadingTypeParameter(...)] à un composant, l’argument


de type générique spécifié est automatiquement utilisé par les descendants qui :

Sont imbriqués comme contenu enfant pour le composant dans le même


document .razor .
Déclarent également un @typeparam avec le même nom.
N’ont pas d’autre valeur explicitement fournie ou implicitement déduite pour le
paramètre de type. Si une autre valeur est fournie ou déduite, elle a priorité sur le
type générique en cascade.
Lors de la réception d’un paramètre de type cascade, les composants obtiennent la
valeur de paramètre de l’ancêtre le plus proche qui a un attribut
[CascadingTypeParameter] avec un nom correspondant. Les paramètres de type
générique en cascade sont remplacés dans une sous-arborescence particulière.

La correspondance n’est effectuée que par nom. Nous vous recommandons donc
d’éviter d’utiliser un paramètre de type générique en cascade avec un nom générique,
par exemple T ou TItem . Si un développeur choisit de passer en cascade un paramètre
de type, il promet implicitement que son nom est suffisamment unique pour ne pas
entrer en conflit avec d’autres paramètres de type en cascade provenant de composants
non liés.

Les types génériques peuvent être passés en cascade à des composants enfants selon
l’une des approches suivantes avec des composants ancêtres (parents). Ces approches
sont présentées dans les deux sous-sections suivantes :

Définir explicitement le type générique en cascade.


Déduire explicitement le type générique en cascade.

Les sous-sections suivantes fournissent des exemples des approches précédentes en


utilisant les deux composants ListDisplay suivants. Les composants reçoivent et
génèrent le rendu des données de liste et sont du type générique TExample . Ces
composants sont fournis à des fins de démonstration et ne diffèrent que par la couleur
du texte de la liste rendue. Si vous souhaitez expérimenter les composants dans les
sous-sections suivantes dans une application de test locale, ajoutez d’abord les deux
composants suivants à l’application.

ListDisplay1.razor :

razor

@typeparam TExample

@if (ExampleList is not null)


{
<ul style="color:blue">
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}

@code {
[Parameter]
public IEnumerable<TExample>? ExampleList { get; set; }
}

ListDisplay2.razor :

razor

@typeparam TExample

@if (ExampleList is not null)


{
<ul style="color:red">
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}

@code {
[Parameter]
public IEnumerable<TExample>? ExampleList { get; set; }
}

Types génériques explicites basés sur des composants


ancêtres
La démonstration dans cette section passe en cascade un type de manière explicite pour
TExample .

7 Notes

Cette section utilise les deux composants ListDisplay de la section Prise en charge
des types génériques en cascade.

Le composant ListGenericTypeItems2 suivant reçoit des données et passe en cascade


un paramètre de type générique nommé TExample à ses composants descendants. Dans
le composant parent à venir, le composant ListGenericTypeItems2 est utilisé pour
afficher des données de liste avec le composant ListDisplay précédent.

ListGenericTypeItems2.razor :

razor
@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample

<h2>List Generic Type Items 2</h2>

@ChildContent

@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}

Le composant parent suivant définit le contenu enfant (RenderFragment) de deux


ListGenericTypeItems2 composants spécifiant les ListGenericTypeItems2 types

( TExample ), qui sont passés en cascade aux composants enfants. Les composants
ListDisplay sont rendus avec les données d’élément de liste présentées dans l’exemple.

Les données de type chaîne sont utilisées avec le premier composant


ListGenericTypeItems2 tandis que les données de type int sont utilisées avec le second

composant ListGenericTypeItems2 .

GenericType2.razor :

razor

@page "/generic-type-2"

<h1>Generic Type Example 2</h1>

<ListGenericTypeItems2 TExample="string">
<ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })"
/>
<ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })"
/>
</ListGenericTypeItems2>

<ListGenericTypeItems2 TExample="int">
<ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" />
<ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" />
</ListGenericTypeItems2>

La spécification explicite du type permet également l’utilisation de valeurs et paramètres


en cascade pour fournir des données aux composants enfants, comme le montre la
démonstration suivante.

ListDisplay3.razor :

razor
@typeparam TExample

@if (ExampleList is not null)


{
<ul style="color:blue">
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}

@code {
[CascadingParameter]
protected IEnumerable<TExample>? ExampleList { get; set; }
}

ListDisplay4.razor :

razor

@typeparam TExample

@if (ExampleList is not null)


{
<ul style="color:red">
@foreach (var item in ExampleList)
{
<li>@item</li>
}
</ul>
}

@code {
[CascadingParameter]
protected IEnumerable<TExample>? ExampleList { get; set; }
}

ListGenericTypeItems3.razor :

razor

@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample

<h2>List Generic Type Items 3</h2>

@ChildContent

@if (ExampleList is not null)


{
<ul style="color:green">
@foreach(var item in ExampleList)
{
<li>@item</li>
}
</ul>

<p>
Type of <code>TExample</code>: @typeof(TExample)
</p>
}

@code {
[CascadingParameter]
protected IEnumerable<TExample>? ExampleList { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }
}

Lors du passage en cascade des données dans l’exemple suivant, le type doit être fourni
au composant .

GenericType3.razor :

razor

@page "/generic-type-3"

<h1>Generic Type Example 3</h1>

<CascadingValue Value="@stringData">
<ListGenericTypeItems3 TExample="string">
<ListDisplay3 />
<ListDisplay4 />
</ListGenericTypeItems3>
</CascadingValue>

<CascadingValue Value="@integerData">
<ListGenericTypeItems3 TExample="int">
<ListDisplay3 />
<ListDisplay4 />
</ListGenericTypeItems3>
</CascadingValue>

@code {
private List<string> stringData = new() { "Item 1", "Item 2" };
private List<int> integerData = new() { 1, 2, 3 };
}
Quand plusieurs types génériques sont passés en cascade, les valeurs de tous les types
génériques de l’ensemble doivent être passées. Dans l’exemple suivant, TItem , TValue
et TEdit sont des types génériques GridColumn , mais le composant parent qui place
GridColumn ne spécifie pas le type TItem :

razor

<GridColumn TValue="string" TEdit="@TextEdit" />

L’exemple précédent génère une erreur de compilation indiquant que le composant


GridColumn ne contient pas le paramètre de type TItem . Le code valide spécifie tous les

types :

razor

<GridColumn TValue="string" TEdit="@TextEdit" TItem="@User" />

Déduire les types génériques en fonction de composants


ancêtres
La démonstration dans cette section passe en cascade un type déduit pour TExample .

7 Notes

Cette section utilise les deux composants ListDisplay de la section Prise en charge
des types génériques en cascade.

ListGenericTypeItems4.razor :

razor

@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample

<h2>List Generic Type Items 4</h2>

@ChildContent

@if (ExampleList is not null)


{
<ul style="color:green">
@foreach(var item in ExampleList)
{
<li>@item</li>
}
</ul>

<p>
Type of <code>TExample</code>: @typeof(TExample)
</p>
}

@code {
[Parameter]
public IEnumerable<TExample>? ExampleList { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }
}

Le composant suivant avec des types en cascade déduits fournit des données
différentes à des fins d’affichage.

GenericType4.razor :

razor

@page "/generic-type-4"

<h1>Generic Type Example 4</h1>

<ListGenericTypeItems4 ExampleList="@(new List<string> { "Item 5", "Item 6"


})">
<ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })"
/>
<ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })"
/>
</ListGenericTypeItems4>

<ListGenericTypeItems4 ExampleList="@(new List<int> { 7, 8, 9 })">


<ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" />
<ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" />
</ListGenericTypeItems4>

Le composant suivant avec des types en cascade déduits fournit les mêmes données à
des fins d’affichage. L’exemple suivant affecte directement les données aux composants.

GenericType5.razor :

razor

@page "/generic-type-5"

<h1>Generic Type Example 5</h1>


<ListGenericTypeItems4 ExampleList="@stringData">
<ListDisplay1 ExampleList="@stringData" />
<ListDisplay2 ExampleList="@stringData" />
</ListGenericTypeItems4>

<ListGenericTypeItems4 ExampleList="@integerData">
<ListDisplay1 ExampleList="@integerData" />
<ListDisplay2 ExampleList="@integerData" />
</ListGenericTypeItems4>

@code {
private List<string> stringData = new() { "Item 1", "Item 2" };
private List<int> integerData = new() { 1, 2, 3 };
}

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Contexte de synchronisation Blazor
ASP.NET Core
Article • 09/02/2024

Blazor utilise un contexte de synchronisation (SynchronizationContext) pour appliquer


un seul thread logique d’exécution. Les méthodes de cycle de vie d’un composant et les
rappels d’événements déclenchés par Blazor sont exécutés sur le contexte de
synchronisation.

Le contexte de synchronisation côté du serveur de Blazor tente d’émuler un


environnement monothread afin qu’il corresponde étroitement au modèle
WebAssembly dans le navigateur, celui-ci étant monothread. À n’importe quel moment,
le travail est effectué sur exactement un thread, ce qui donne l’impression d’un seul
thread logique. Aucune opération ne s’exécute en même temps qu’une autre.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du Blazorréférentiel GitHub
d’exemples qui correspond à la version de .NET que vous ciblez. Pour le moment, tous
les exemples de la documentation ne figurent pas dans les exemples d’applications,
mais nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans
les exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant
du premier trimestre 2024.

Éviter les appels de blocage de thread


En règle générale, n’appelez pas les méthodes suivantes dans les composants. Les
méthodes suivantes bloquent le thread d’exécution, ce qui empêche l’application de
reprendre le travail tant que le Task sous-jacent n’est pas terminé :

Result
Wait
WaitAny
WaitAll
Sleep
GetResult

7 Notes

Les exemples de la documentation Blazor qui utilisent les méthodes de blocage de


thread mentionnées dans cette section sont fournis à titre de démonstration et ne
constituent pas une approche recommandée en matière de programmation. Par
exemple, quelques démonstrations de code de composant simulent un processus
de longue durée en appelant Thread.Sleep.

Appeler des méthodes de composant en


externe pour mettre à jour l’état
Dans le cas où un composant doit être mis à jour en fonction d’un événement externe,
comme un minuteur ou une autre notification, utilisez la méthode InvokeAsync qui
distribue l’exécution du code au contexte de synchronisation de Blazor. Par exemple,
prenez le service de notification suivant qui peut notifier n’importe quel composant
d’écoute de l’état mis à jour. La méthode Update peut être appelée de n’importe où
dans l’application.

TimerService.cs :

C#

namespace BlazorSample;

public class TimerService(NotifierService notifier,


ILogger<TimerService> logger) : IDisposable
{
private int elapsedCount;
private readonly static TimeSpan heartbeatTickRate =
TimeSpan.FromSeconds(5);
private readonly ILogger<TimerService> logger = logger;
private readonly NotifierService notifier = notifier;
private PeriodicTimer? timer;

public async Task Start()


{
if (timer is null)
{
timer = new(heartbeatTickRate);
logger.LogInformation("Started");

using (timer)
{
while (await timer.WaitForNextTickAsync())
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation("ElapsedCount {Count}",
elapsedCount);
}
}
}
}

public void Dispose()


{
timer?.Dispose();

// The following prevents derived types that introduce a


// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
}

NotifierService.cs :

C#

namespace BlazorSample;

public class NotifierService


{
public async Task Update(string key, int value)
{
if (Notify != null)
{
await Notify.Invoke(key, value);
}
}

public event Func<string, int, Task>? Notify;


}

Inscrivez les services :

Pour le développement côté client, inscrivez les services en tant que bases de
données unique dans le fichier Program côté client :

C#

builder.Services.AddSingleton<NotifierService>();
builder.Services.AddSingleton<TimerService>();

Pour le développement côté serveur, inscrivez les services tels qu’ils sont définis
dans le fichier Program du serveur :

C#

builder.Services.AddScoped<NotifierService>();
builder.Services.AddScoped<TimerService>();

Utilisez NotifierService pour mettre à jour un composant.

Notifications.razor :

razor
@page "/notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer

<PageTitle>Notifications</PageTitle>

<h1>Notifications Example</h1>

<h2>Timer Service</h2>

<button @onclick="StartTimer">Start Timer</button>

<h2>Notifications</h2>

<p>
Status:
@if (lastNotification.key is not null)
{
<span>@lastNotification.key = @lastNotification.value</span>
}
else
{
<span>Awaiting first notification</span>
}
</p>

@code {
private (string key, int value) lastNotification;

protected override void OnInitialized()


{
Notifier.Notify += OnNotify;
}

public async Task OnNotify(string key, int value)


{
await InvokeAsync(() =>
{
lastNotification = (key, value);
StateHasChanged();
});
}

private async Task StartTimer()


{
await Timer.Start();
}

public void Dispose() => Notifier.Notify -= OnNotify;


}

Dans l'exemple précédent :


NotifierService appelle la méthode OnNotify du composant en dehors du

contexte de synchronisation de Blazor. InvokeAsync sert à basculer vers le bon


contexte et à mettre en file d’attente un rendu. Pour plus d’informations, consultez
le rendu de composants Razor ASP.NET Core.
Le composant implémente IDisposable. Le délégué OnNotify est désabonné dans
la méthode Dispose , qui est appelée par le framework quand le composant est
supprimé. Pour plus d’informations, consultez le cycle de vie des composants
Razor ASP.NET Core.

) Important

Si un composant Razor définit un événement déclenché à partir d’un thread


d’arrière-plan, il peut être nécessaire pour capturer et restaurer le contexte
d’exécution (ExecutionContext) au moment où le gestionnaire est inscrit. Pour plus
d’informations, consultez L’appel à InvokeAsync(StateHasChanged) cause le repli
de la page sur la culture par défaut (dotnet/aspnetcore #28521) .

Pour distribuer les exceptions interceptées de l’arrière-plan TimerService au composant,


afin de traiter les exceptions comme celles liées à un événement normal du cycle de vie,
consultez Gérer les exceptions interceptées en dehors du cycle de vie d’un
composantRazor dans l’articleGérer les erreurs.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Conserver les relations entre éléments,
composants et modèles dans ASP.NET
Core Blazor
Article • 09/02/2024

Cet article explique comment utiliser l’attribut de directive @key , pour la conservation
des relations d’élément, de composant et de modèle, lors du rendu et lorsque les
éléments ou composants changent par la suite.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du dépôt GitHub d’exemples
Blazor qui correspond à la version de .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Utilisation de l’attribut de directive @key


Si, après le rendu d’une liste d’éléments ou de composants, les éléments ou composants
changent, Blazor doit décider lesquels des éléments ou composants précédents peuvent
être conservés et déterminer le mode de mappage entre les objets de modèle et ces
éléments ou composants. Normalement, ce processus est automatique et suffisant pour
le rendu général, mais il existe souvent des cas où le contrôle du processus à l’aide de
l’attribut @key de directive est nécessaire.

Considérez l’exemple suivant qui illustre un problème de mappage de collection résolu


à l’aide de @key.

Pour les composants suivants :

Le composant Details reçoit du composant parent des données ( Data ), qui sont
affichées dans un élément <input> . Tout élément <input> affiché donné peut
recevoir le focus de la page de l’utilisateur lorsqu’il sélectionne l’un des éléments
<input> .

Le composant parent crée une liste d’objets de personne à afficher avec le


composant Details . Toutes les trois secondes, une nouvelle personne est ajoutée
à la collection.

Cette démonstration vous permet de :

Sélectionner un <input> parmi plusieurs composants Details rendus.


Étudier le comportement du focus de la page à mesure que la collection de
personnes augmente automatiquement.

Details.razor :

razor
<input value="@Data" />

@code {
[Parameter]
public string? Data { get; set; }
}

Dans le composant parent suivant, chaque itération d’ajout d’une personne dans
OnTimerCallback oblige Blazor à reconstruire l’intégralité de la collection. Le focus de la

page restant sur la même position d’index des éléments <input> , le focus change chaque
fois qu’une personne est ajoutée. Il n’est pas souhaitable de déplacer le focus hors de la
sélection de l’utilisateur. Après avoir démontré le mauvais comportement avec le
composant suivant, l’attribut de directive @key est utilisé pour améliorer l’expérience de
l’utilisateur.

People.razor :

razor

@page "/people"
@using System.Timers
@implements IDisposable

<PageTitle>People</PageTitle>

<h1>People Example</h1>

@foreach (var person in people)


{
<Details Data="@person.Data" />
}

@code {
private Timer timer = new Timer(3000);

public List<Person> people =


new()
{
{ new Person { Data = "Person 1" } },
{ new Person { Data = "Person 2" } },
{ new Person { Data = "Person 3" } }
};

protected override void OnInitialized()


{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}

private void OnTimerCallback()


{
_ = InvokeAsync(() =>
{
people.Insert(0,
new Person
{
Data = $"INSERTED {DateTime.Now.ToString("hh:mm:ss
tt")}"
});
StateHasChanged();
});
}

public void Dispose() => timer.Dispose();

public class Person


{
public string? Data { get; set; }
}
}

Le contenu de la collection people change quand des entrées sont insérées, supprimées
ou réorganisées. La regénération du rendu peut entraîner des différences de
comportement visibles. Par exemple, chaque fois qu’une personne est insérée dans la
collection people , le focus de l’utilisateur est perdu.

Le processus de mappage d’éléments ou de composants à une collection peut être


contrôlé avec l’attribut de directive @key. L’utilisation de @key garantit la conservation
d’éléments ou de composants en fonction de la valeur de la clé. Si le composant
Details dans l’exemple précédent est indexé sur l’élément person , Blazor ignore le

nouveau rendu des composants Details qui n’ont pas changé.

Pour modifier le composant parent afin d’utiliser l’attribut de directive @key avec la
collection people , mettez à jour l’élément <Details> comme suit :

razor

<Details @key="person" Data="@person.Data" />

Quand la collection people change, l’association entre les instances de Details et de


person est conservée. Quand Person est inséré au début de la collection, une nouvelle

instance de Details est insérée à la position correspondante. Les autres instances sont
inchangées. Le focus de l’utilisateur n’est donc pas perdu à mesure que des personnes
sont ajoutées à la collection.
D’autres mises à jour de collection présentent le même comportement quand l’attribut
de directive @key est utilisé :

Si une instance est supprimée de la collection, seule l’instance de composant


correspondante est supprimée de l’interface utilisateur. Les autres instances sont
inchangées.
Si les entrées de collection sont réorganisées, les instances de composant
correspondantes sont conservées et réorganisées dans l’interface utilisateur.

) Important

Les clés sont locales à chaque composant ou élément conteneur. Les clés ne sont
pas comparées globalement dans le document.

Quand utiliser @key


En règle générale, il est judicieux d’utiliser @key chaque fois qu’une liste est rendue (par
exemple, dans un bloc foreach) et qu’une valeur appropriée existe pour définir @key.

Vous pouvez également utiliser @key pour conserver une sous-arborescence d’éléments
ou de composants quand un objet ne change pas, comme le montrent les exemples
suivants.

Exemple 1 :

razor

<li @key="person">
<input value="@person.Data" />
</li>

Exemple 2 :

razor

<div @key="person">
@* other HTML elements *@
</div>

Si une instance de person change, la directive d’attribut @key force Blazor à :

Ignorer l’intégralité de <li> ou de <div> et les descendants.


Regénérer la sous-arborescence au sein de l’interface utilisateur avec de nouveaux
éléments et composants.

Cela permet de garantir qu’aucun état de l’interface utilisateur n’est préservé quand la
collection change dans une sous-arborescence.

Étendue de @key
La directive d’attribut @key est délimitée à ses propres frères au sein de son parent.

Prenons l'exemple suivant. Les clés first et second sont comparées l’une à l’autre dans
la même étendue de l’élément <div> externe :

razor

<div>
<div @key="first">...</div>
<div @key="second">...</div>
</div>

L’exemple suivant illustre les clés first et second dans leurs propres étendues, sans
aucun rapport entre elles et sans aucune influence de l’une sur l’autre. Chaque étendue
@key s’applique uniquement à son élément <div> parent, et non à travers les éléments
<div> parents :

razor

<div>
<div @key="first">...</div>
</div>
<div>
<div @key="second">...</div>
</div>

Pour le composant Details présenté précédemment, les exemples suivants génèrent le


rendu des données person dans la même étendue @key et illustrent des cas d’usage
standard pour @key :

razor

<div>
@foreach (var person in people)
{
<Details @key="person" Data="@person.Data" />
}
</div>

razor

@foreach (var person in people)


{
<div @key="person">
<Details Data="@person.Data" />
</div>
}

razor

<ol>
@foreach (var person in people)
{
<li @key="person">
<Details Data="@person.Data" />
</li>
}
</ol>

Les exemples suivants étendent uniquement @key à l’élément <div> ou <li> qui
entoure chaque instance de composant Details . Les données person pour chaque
membre de la collection people ne sont donc pas indexées sur chaque instance de
person à travers les composants Details rendus. Évitez les modèles suivants quand

vous utilisez @key :

razor

@foreach (var person in people)


{
<div>
<Details @key="person" Data="@person.Data" />
</div>
}

razor

<ol>
@foreach (var person in people)
{
<li>
<Details @key="person" Data="@person.Data" />
</li>
}
</ol>

Quand ne pas utiliser @key


Le rendu avec @key entraîne un coût en termes de performances. Ce coût en termes de
performances n’est pas important, mais spécifiez uniquement @key si la conservation
de l’élément ou du composant profite à l’application.

Même si @key n’est pas utilisé, Blazor conserve autant que possible les instances des
composants et des éléments enfants. Le seul avantage à utiliser @key est de contrôler la
façon dont les instances de modèle sont mappées aux instances de composant
conservées afin d’éviter que Blazor ne sélectionne le mappage.

Valeurs à utiliser pour @key


En règle générale, il est judicieux de fournir l’une des valeurs suivantes pour @key :

Instances d’objet de modèle. Par exemple, l’instance Person ( person ) a été utilisée
dans l’exemple précédent. Cela garantit une conservation basée sur l’égalité des
références d’objet.
Identificateurs uniques. Par exemple, des identificateurs uniques peuvent être
basés sur des valeurs de clé primaire de type int , string ou Guid .

Vérifiez que les valeurs utilisées pour @key ne sont pas en conflit. Si des valeurs en
conflit sont détectées dans le même élément parent, Blazor lève une exception car il ne
peut pas mapper de manière déterministe les anciens éléments ou composants aux
nouveaux. Utilisez uniquement des valeurs distinctes, comme des instances d’objet ou
des valeurs de clé primaire.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Évitez d'écraser les paramètres dans
ASP.NET Core Blazor
Article • 09/02/2024

Cet article vous explique comment éviter le remplacement des paramètres dans les
applications Blazor lors d’un nouveau rendu.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Paramètres remplacés
Le framework Blazor impose généralement une affectation de paramètres parent-enfant
sans échec :

Les paramètres ne sont pas remplacés de manière inattendue.


Les effets secondaires sont minimisés. Par exemple, les rendus supplémentaires
sont évités car ils peuvent créer des boucles de rendu infinies.

Un composant enfant reçoit de nouvelles valeurs de paramètre qui remplacent


éventuellement les valeurs existantes lorsque le composant parent est rendu à nouveau.
Les valeurs de paramètre dans un composant enfant sont souvent remplacées
accidentellement lorsque le composant est développé avec un ou plusieurs paramètres
liés aux données et que le développeur écrit directement dans un paramètre de
l’enfant :

Le composant enfant est rendu avec une ou plusieurs valeurs de paramètre du


composant parent.
L’enfant écrit directement dans la valeur d’un paramètre.
Le composant parent est rendu à nouveau et remplace la valeur du paramètre
enfant.

Le risque de remplacement des valeurs de paramètre s’étend également aux accesseurs


set de propriété du composant enfant.

) Important

Notre conseil général est de ne pas créer de composants qui écrivent directement
dans leurs propres paramètres après le rendu initial du composant.

Considérez le composant Expander suivant qui :

Génère le rendu du contenu enfant.


Bascule l’affichage du contenu enfant avec un paramètre de composant
( Expanded ).
Après la démonstration d’un paramètre remplacé avec le composant Expander suivant,
un composant Expander modifié présente la bonne approche à suivre dans ce scénario.
Les exemples suivants peuvent être placés dans un exemple d’application local pour
découvrir les comportements décrits.

Expander.razor :

<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">


<div class="card-body">
<h2 class="card-title">Toggle (<code>Expanded</code> = @Expanded)
</h2>

@if (Expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>

@code {
[Parameter]
public bool Expanded { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }

private void Toggle()


{
Expanded = !Expanded;
}
}

Le composant Expander est ajouté au composant parent ExpanderExample suivant qui


peut appeler StateHasChanged :

L’appel de StateHasChanged dans le code du développeur notifie un composant


que son état a changé et déclenche généralement un nouveau rendu du
composant pour mettre à jour l’interface utilisateur. Vous trouverez plus de détails
sur StateHasChanged plus loin dans le cycle de vie de composant Razor
ASP.NET Core et le rendu de composants Razor ASP.NET Core.
L’attribut de directive @onclick du bouton attache un gestionnaire d’événements à
l’événement onclick du bouton. Vous trouverez plus de détails plus loin dans la
gestion des événements Blazor ASP.NET Core.

ExpanderExample.razor :
@page "/expander-example"

<PageTitle>Expander</PageTitle>

<h1>Expander Example</h1>

<Expander Expanded="true">
Expander 1 content
</Expander>

<Expander Expanded="true" />

<button @onclick="StateHasChanged">
Call StateHasChanged
</button>

Au départ, les composants Expander se comportent indépendamment lorsque leurs


propriétés Expanded sont basculées. Les composants enfants conservent leurs états
comme prévu.

Si StateHasChanged est appelé dans un composant parent, le framework Blazor


regénère le rendu des composants enfants si leurs paramètres ont changé :

Pour un groupe de types de paramètres que Blazor vérifie explicitement, Blazor


regénère le rendu d’un composant enfant s’il détecte que l’un des paramètres a
changé.
Pour les types de paramètres non vérifiés, Blazor regénère le rendu du composant
enfant que les paramètres aient changé ou non. Le contenu enfant appartient à
cette catégorie de types de paramètres car le contenu enfant est de type
RenderFragment, qui est un délégué faisant référence à d’autres objets mutables.

Pour le composant ExpanderExample :

Le premier composant Expander définit le contenu enfant dans un


RenderFragment potentiellement mutable. Un appel à StateHasChanged dans le
composant parent regénère donc automatiquement le rendu du composant et
remplace potentiellement la valeur de Expanded par sa valeur initiale de true .
Le deuxième composant Expander ne définit pas de contenu enfant. Par
conséquent, un RenderFragment potentiellement mutable n’existe pas. Un appel à
StateHasChanged dans le composant parent ne regénère pas automatiquement le
rendu du composant enfant. La valeur Expanded du composant n’est donc pas
remplacée.

Pour conserver l’état basculé dans le scénario précédent, utilisez un champ privé dans le
composant Expander .
Le composant Expander révisé suivant :

Accepte la valeur de paramètre de composant Expanded du parent.


Affecte la valeur de paramètre de composant à un champ privé ( expanded ) dans
l’événement OnInitialized.
Utilise le champ privé pour conserver son état de basculement interne, ce qui
montre comment éviter d’écrire directement dans un paramètre.

7 Notes

Les conseils de cette section s’appliquent également aux accesseurs set de


paramètre de composant, ce qui peut entraîner des effets secondaires indésirables
similaires.

Expander.razor :

<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">


<div class="card-body">
<h2 class="card-title">Toggle (<code>expanded</code> = @expanded)
</h2>

@if (expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>

@code {
private bool expanded;

[Parameter]
public bool Expanded { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }

protected override void OnInitialized()


{
expanded = Expanded;
}

private void Toggle()


{
expanded = !expanded;
}
}
Pour plus d'informations sur la liaison parent-enfant, voir les ressources suivantes :

Liaison à des paramètres de composant


Lier plus de deux composants
Blazor erreur de liaison bidirectionnelle (dotnet/aspnetcore #24599)

Pour plus d’informations sur la détection des modifications, notamment sur les types
exacts vérifiés par Blazor, consultez le rendu de composants Razor ASP.NET Core.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Projection d’attributs et paramètres
arbitraires ASP.NET Core Blazor
Article • 09/02/2024

Les composants peuvent capturer et générer le rendu d’attributs supplémentaires en


plus des paramètres déclarés du composant. Les attributs supplémentaires peuvent être
capturés dans un dictionnaire, puis projetés sur un élément lorsque le composant est
rendu avec l’attribut de directive Razor@attributes. Ce scénario est utile pour définir un
composant qui produit un élément de balisage prenant en charge des personnalisations
diverses. Par exemple, il peut être fastidieux de définir séparément des attributs pour un
<input> qui prend en charge de nombreux paramètres.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Quand vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.
Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Projection d’attributs
Dans le composant Splat suivant :

Le premier élément <input> ( id="useIndividualParams" ) utilise des paramètres de


composant individuels.
Le deuxième élément <input> ( id="useAttributesDict" ) utilise la projection
d’attributs.

Splat.razor :

razor

@page "/splat"

<PageTitle>SPLAT!</PageTitle>

<h1>Splat Parameters Example</h1>

<input id="useIndividualParams"
maxlength="@maxlength"
placeholder="@placeholder"
required="@required"
size="@size" />

<input id="useAttributesDict"
@attributes="InputAttributes" />

@code {
private string maxlength = "10";
private string placeholder = "Input placeholder text";
private string required = "required";
private string size = "50";
private Dictionary<string, object> InputAttributes { get; set; } =
new()
{
{ "maxlength", "10" },
{ "placeholder", "Input placeholder text" },
{ "required", "required" },
{ "size", "50" }
};
}

Les éléments <input> rendus dans la page web sont identiques :

HTML

<input id="useIndividualParams"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">

<input id="useAttributesDict"
maxlength="10"
placeholder="Input placeholder text"
required="required"
size="50">

Attributs arbitraires
Pour accepter des attributs arbitraires, définissez un paramètre de composant avec la
propriété CaptureUnmatchedValues définie sur true :

razor

@code {
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object>? InputAttributes { get; set; }
}

La propriété CaptureUnmatchedValues sur [Parameter] permet au paramètre de capturer


tous les attributs qui ne correspondent à aucun autre paramètre. Un composant ne peut
définir qu’un seul paramètre avec CaptureUnmatchedValues. Le type de propriété utilisé
avec CaptureUnmatchedValues doit pouvoir être affecté à partir de Dictionary<string,
object> avec des clés de chaîne. Il également possible d’utiliser
IEnumerable<KeyValuePair<string, object>> ou IReadOnlyDictionary<string, object>
dans ce scénario.
La position de @attributes par rapport à la position des attributs d’élément est
importante. Quand des @attributes sont projetés sur l’élément, les attributs sont traités
de droite à gauche (du dernier au premier). Prenez l’exemple suivant d’un composant
parent qui consomme un composant enfant :

AttributeOrderChild1.razor :

razor

<div @attributes="AdditionalAttributes" extra="5" />

@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}

AttributeOrder1.razor :

razor

@page "/attribute-order-1"

<PageTitle>Attribute Order 1</PageTitle>

<h1>Attribute Order Example 1</h1>

<AttributeOrderChild1 extra="10" />

<p>
View the HTML markup in your browser to inspect the attributes on
the AttributeOrderChild1 component.
</p>

L’attribut extra du composant AttributeOrderChild1 est défini à droite de @attributes.


Le <div> rendu du composant AttributeOrderParent1 contient extra="5" quand il est
passé par le biais de l’attribut supplémentaire, car les attributs sont traités de droite à
gauche (du dernier au premier) :

HTML

<div extra="5" />

Dans l’exemple suivant, l’ordre de extra et de @attributes est inversé dans le <div> du
composant enfant :

AttributeOrderChild2.razor :
razor

<div extra="5" @attributes="AdditionalAttributes" />

@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}

AttributeOrder2.razor :

razor

@page "/attribute-order-2"

<PageTitle>Attribute Order 2</PageTitle>

<h1>Attribute Order Example 2</h1>

<AttributeOrderChild2 extra="10" />

<p>
View the HTML markup in your browser to inspect the attributes on
the AttributeOrderChild2 component.
</p>

Le <div> dans la page web rendue du composant parent contient extra="10" quand il
est passé par le biais de l’attribut supplémentaire :

HTML

<div extra="10" />

6 Collaborer avec nous sur


GitHub
La source de ce contenu se
trouve sur GitHub, où vous
pouvez également créer et
examiner les problèmes et les
demandes de tirage. Pour plus
d’informations, consultez notre
guide du contributeur.
Commentaires sur ASP.NET
Core
ASP.NET Core est un projet open
source. Sélectionnez un lien pour
fournir des commentaires :

 Ouvrir un problème de
documentation

 Indiquer des commentaires sur


le produit
Dispositions ASP.NET Core Blazor
Article • 09/02/2024

Cet article explique comment créer des composants de disposition réutilisables pour les
applications Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir des exemples Blazor du référentiel
GitHub correspondant à la version .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Utilité des dispositions Blazor


Certains éléments d’application, comme les menus, les messages de copyright et les
logos d’entreprise, font généralement partie de la présentation globale de l’application.
Placer une copie du balisage de ces éléments dans tous les composants d’une
application n’est pas efficace. Chaque fois qu’un de ces éléments est mis à jour, chaque
composant qui utilise l’élément doit être mis à jour. Cette approche est coûteuse à gérer
et peut entraîner un contenu incohérent si une mise à jour est manquée. Les dispositions
résolvent ces problèmes.

Une disposition Blazor est un composant Razor qui partage le balisage avec les
composants qui le référencent. Les dispositions peuvent utiliser la liaison de données,
l’injection de dépendances et d’autres fonctionnalités des composants.

Composants de la disposition

Créer un composant de disposition


Pour créer un composant de disposition :

Créez un composant Razor défini par un modèle Razor ou du code C#. Les
composants de disposition basés sur un modèle Razor utilisent l’extension de
fichier .razor , comme les composants Razor ordinaires. Comme les composants
de disposition sont partagés entre les composants d’une application, ils sont
généralement placés dans le dossier partagé ou de disposition de l’application.
Toutefois, les dispositions peuvent être placées à n’importe quel emplacement
accessible aux composants qui les utilisent. Par exemple, une disposition peut être
placée dans le même dossier que les composants qui l’utilisent.
Héritez du composant de LayoutComponentBase. LayoutComponentBase définit
une propriété Body (type RenderFragment) pour le contenu rendu à l’intérieur de
la disposition.
Utilisez la syntaxe Razor @Body pour spécifier l’emplacement dans le balisage de
disposition où le contenu est rendu.

7 Notes
Pour plus d’informations sur RenderFragment, consultez Composants ASP.NET
Core Razor.

Le composant DoctorWhoLayout suivant montre le modèle Razor d’un composant de


disposition. La disposition hérite de LayoutComponentBase et définit @Body entre la
barre de navigation ( <nav>...</nav> ) et le pied de page ( <footer>...</footer> ).

DoctorWhoLayout.razor :

razor

@inherits LayoutComponentBase

<PageTitle>Doctor Who® Database</PageTitle>

<header>
<h1>Doctor Who® Database</h1>
</header>

<nav>
<a href="main-list">Main Episode List</a>
<a href="search">Search</a>
<a href="new">Add Episode</a>
</nav>

@Body

<footer>
@TrademarkMessage
</footer>

@code {
public string TrademarkMessage { get; set; } =
"Doctor Who is a registered trademark of the BBC. " +
"https://www.doctorwho.tv/ https://www.bbc.com";
}

MainLayout (composant)

Dans une application créée à partir d’un modèle de projet Blazor, le composant
MainLayout est la disposition par défaut de l’application. La disposition de Blazor adopte

Flexbox layout model (MDN documentation) (spécification W3C ).

La fonctionnalité d’isolation CSS de Blazor applique des styles CSS isolés au composant
MainLayout . Par convention, les styles sont fournis par la feuille de style associée du

même nom, MainLayout.razor.css . L’implémentation du framework ASP.NET Core de la


feuille de style est disponible pour inspection dans la source de référence ASP.NET Core
(dépôt GitHub dotnet/aspnetcore ) :

Application web BlazorMainLayout.razor.css


Blazor WebAssembly MainLayout.razor.css

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Appliquer une disposition

Rendre l’espace de noms de disposition disponible


Emplacements et espaces de noms des fichiers de disposition modifiés au fil du temps
pour l’infrastructure Blazor. Selon la version de Blazor et le type de l’application Blazor
que vous générez, il est possible que vous deviez indiquer l’espace de noms de la
disposition lorsque vous l’utilisez. Lorsque vous référencez une implémentation de
disposition et que la disposition est introuvable sans indiquer l’espace de noms de la
disposition, suivez l’une des approches suivantes :

Ajoutez une directive @using au fichier _Imports.razor pour l’emplacement des


dispositions. Dans l’exemple suivant, un dossier de dispositions portant le nom
Layout se trouve à l’intérieur d’un dossier Components et l’espace de noms de

l’application est BlazorSample :

razor

@using BlazorSample.Components.Layout

Ajoutez une directive @using en haut de la définition du composant où la


disposition est utilisée :

razor
@using BlazorSample.Components.Layout
@layout DoctorWhoLayout

Qualifiez entièrement l’espace de noms de la disposition où elle est utilisée :

razor

@layout BlazorSample.Components.Layout.DoctorWhoLayout

Appliquer une disposition à un composant


Utilisez la directive @layoutRazor pour appliquer une disposition à un composant Razor
routable qui a une directive @page. Le compilateur convertit @layout en
LayoutAttribute et applique l’attribut à la classe de composant.

Le contenu du composant Episodes suivant est inséré dans DoctorWhoLayout à la


position de @Body .

Episodes.razor :

razor

@page "/episodes"
@layout DoctorWhoLayout

<h2>Doctor Who® Episodes</h2>

<ul>
<li>
<a href="https://www.bbc.co.uk/programmes/p00vfknq">
<em>The Ribos Operation</em>
</a>
</li>
<li>
<a href="https://www.bbc.co.uk/programmes/p00vfdsb">
<em>The Sunmakers</em>
</a>
</li>
<li>
<a href="https://www.bbc.co.uk/programmes/p00vhc26">
<em>Nightmare of Eden</em>
</a>
</li>
</ul>
Le balisage HTML rendu suivant est produit par les composants DoctorWhoLayout et
Episodes précédents. Le balisage superflu n’apparaît pas pour vous permettre de vous

concentrer sur le contenu fourni par les deux composants impliqués :

L’en-tête « base de données » H1 ( <h1>...</h1> ) dans l’en-tête ( <header>...


</header> ), la barre de navigation ( <nav>...</nav> ) et les informations de marque

déposée contenues dans le pied de page ( <footer>...</footer> ) proviennent du


composant DoctorWhoLayout .
L’en-tête « épisodes » H2 ( <h2>...</h2> ) et la liste d’épisodes ( <ul>...</ul> )
proviennent du composant Episodes .

HTML

<header>
<h1 ...>...</h1>
</header>

<nav>
...
</nav>

<h2>...</h2>

<ul>
<li>...</li>
<li>...</li>
<li>...</li>
</ul>

<footer>
...
</footer>

La spécification de la disposition directement dans un composant remplace une


disposition par défaut :

Définie par une directive @layout importée à partir d’un composant _Imports
( _Imports.razor ), comme décrit dans la section suivante, Appliquer une disposition
à un dossier de composants.
Définie comme disposition par défaut de l’application, comme décrit dans la
section Appliquer une disposition par défaut à une application plus loin dans cet
article.

Appliquer une disposition à un dossier de composants


Chaque dossier d’une application peut éventuellement contenir un fichier de modèle
nommé _Imports.razor . Le compilateur inclut les directives spécifiées dans le fichier
d’importation dans tous les modèles Razor dans le même dossier et de manière
récursive dans tous ses sous-dossiers. Par conséquent, un fichier _Imports.razor
contenant @layout DoctorWhoLayout garantit que tous les composants d’un dossier
utilisent le composant DoctorWhoLayout . Il n’est pas nécessaire d’ajouter à plusieurs
reprises @layout DoctorWhoLayout à tous les composants Razor ( .razor ) dans le dossier
et les sous-dossiers.

_Imports.razor :

razor

@layout DoctorWhoLayout
...

Le fichier _Imports.razor est similaire au fichier _ViewImports.cshtml pour les vues et


pages Razor, mais appliqué spécifiquement aux fichiers de composants Razor.

La spécification d’une disposition dans _Imports.razor remplace une disposition


spécifiée comme disposition d’application par défaut du routeur, qui est décrite dans la
section suivante.

2 Avertissement

N’ajoutez pas de directive Razor @layout au fichier racine _Imports.razor , ce qui


entraîne une boucle infinie de dispositions. Pour contrôler la disposition
d’application par défaut, spécifiez la disposition dans le composant Router. Pour
plus d’informations, consultez la section suivante, Appliquer une disposition par
défaut à une application.

7 Notes

La directive @layoutRazor applique uniquement une disposition aux composants


Razor routables avec une directive @page.

Appliquer une disposition par défaut à une application


Spécifiez la disposition d’application par défaut dans le composant RouteView du
composant Router. Utilisez le paramètre DefaultLayout pour définir le type de
disposition :

razor

<RouteView RouteData="routeData" DefaultLayout="typeof({LAYOUT})" />

Dans l’exemple précédent, l’espace réservé {LAYOUT} est la disposition (par exemple,
DoctorWhoLayout si le nom de fichier de la disposition est DoctorWhoLayout.razor ). Il est

possible que vous deviez identifier l’espace de noms de la disposition en fonction de la


version .NET et du type d’application Blazor. Pour obtenir plus d’informations, consultez
la section Rendre l’espace de noms de disposition disponible.

La spécification de la disposition en tant que disposition par défaut dans le Router du


composant RouteView est une pratique utile, car vous pouvez remplacer la disposition
par composant ou par dossier, comme décrit dans les sections précédentes de cet
article. Nous vous recommandons d’utiliser le composant Router pour définir la
disposition par défaut de l’application, car il s’agit de l’approche la plus générale et
flexible pour l’utilisation de dispositions.

Appliquer une disposition à un contenu arbitraire


(composant LayoutView )
Pour définir une disposition pour un contenu de modèle Razor arbitraire, spécifiez la
disposition avec un composant LayoutView. Vous pouvez utiliser LayoutView dans
n’importe quel composant Razor. L’exemple suivant définit un composant de disposition
nommé ErrorLayout pour le modèle de NotFound du composant MainLayout
( <NotFound>...</NotFound> ).

7 Notes

L’exemple suivant concerne spécifiquement une application Blazor WebAssembly,


car les applications web Blazor n’utilisent pas le modèle NotFound ( <NotFound>...
</NotFound> ). Cependant, le modèle est pris en charge pour la compatibilité

descendante afin d’éviter un changement cassant dans le framework. Les


applications web Blazor traitent généralement les demandes d’URL incorrectes en
affichant l’interface utilisateur intégrée 404 du navigateur ou en retournant une
page 404 personnalisée à partir du serveur ASP.NET Core via l’intergiciel ASP.NET
Core (par exemple UseStatusCodePagesWithRedirects / Documentation de l’API).

razor
<Router ...>
<Found ...>
...
</Found>
<NotFound>
<LayoutView Layout="typeof(ErrorLayout)">
<h1>Page not found</h1>
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

Il est possible que vous deviez identifier l’espace de noms de la disposition en fonction
de la version .NET et du type d’application Blazor. Pour obtenir plus d’informations,
consultez la section Rendre l’espace de noms de disposition disponible.

Dispositions imbriquées
Un composant peut référencer une disposition qui à son tour fait référence à une autre
disposition. Par exemple, les dispositions imbriquées sont utilisées pour créer des
structures de menu à plusieurs niveaux.

L’exemple suivant montre comment utiliser des dispositions imbriquées. Le composant


Episodes indiqué dans la section Appliquer une disposition à un composant est le

composant à afficher. Le composant fait référence au composant DoctorWhoLayout .

Le composant DoctorWhoLayout suivant est une version modifiée de l’exemple présenté


plus haut dans cet article. Les éléments d’en-tête et de pied de page sont supprimés, et
la disposition fait référence à une autre disposition, ProductionsLayout . Le composant
Episodes est rendu où @Body apparaît dans DoctorWhoLayout .

DoctorWhoLayout.razor :

razor

@inherits LayoutComponentBase
@layout ProductionsLayout

<PageTitle>Doctor Who® Database</PageTitle>

<h1>Doctor Who® Database</h1>

<nav>
<a href="main-episode-list">Main Episode List</a>
<a href="episode-search">Search</a>
<a href="new-episode">Add Episode</a>
</nav>

@Body

<div>
@TrademarkMessage
</div>

@code {
public string TrademarkMessage { get; set; } =
"Doctor Who is a registered trademark of the BBC. " +
"https://www.doctorwho.tv/ https://www.bbc.com";
}

Le composant ProductionsLayout contient les éléments de disposition de niveau


supérieur, où résident désormais les éléments d’en-tête ( <header>...</header> ) et de
pied de page ( <footer>...</footer> ). La disposition DoctorWhoLayout avec le
composant Episodes est rendue où @Body apparaît.

ProductionsLayout.razor :

razor

@inherits LayoutComponentBase

<header>
<h1>Productions</h1>
</header>

<nav>
<a href="main-production-list">Main Production List</a>
<a href="production-search">Search</a>
<a href="new-production">Add Production</a>
</nav>

@Body

<footer>
Footer of Productions Layout
</footer>

Le balisage HTML rendu suivant est généré par la disposition imbriquée précédente. Le
balisage superflu n’apparaît pas pour vous permettre de vous concentrer sur le contenu
imbriqué fourni par les trois composants impliqués :

L’en-tête ( <header>...</header> ), la barre de navigation de production ( <nav>...


</nav> ) et les éléments de pied de page ( <footer>...</footer> ) et leur contenu
proviennent du composant ProductionsLayout .
L’en-tête « base de données » H1 ( <h1>...</h1> ), la barre de navigation d’épisode
( <nav>...</nav> ) et les informations de marque déposée ( <div>...</div> )
proviennent du composant DoctorWhoLayout .
L’en-tête « épisodes » H2 ( <h2>...</h2> ) et la liste d’épisodes ( <ul>...</ul> )
proviennent du composant Episodes .

HTML

<header>
...
</header>

<nav>
<a href="main-production-list">Main Production List</a>
<a href="production-search">Search</a>
<a href="new-production">Add Production</a>
</nav>

<h1>...</h1>

<nav>
<a href="main-episode-list">Main Episode List</a>
<a href="episode-search">Search</a>
<a href="new-episode">Add Episode</a>
</nav>

<h2>...</h2>

<ul>
<li>...</li>
<li>...</li>
<li>...</li>
</ul>

<div>
...
</div>

<footer>
...
</footer>

Partager une disposition Razor Pages avec des


composants intégrés
Lorsque les composants routables sont intégrés à une application Razor Pages, la
disposition partagée de l’application peut être utilisée avec les composants. Pour plus
d’informations, consultez Intégrer des composants Razor ASP.NET Core aux applications
ASP.NET Core.

Sections
Pour contrôler le contenu d’une disposition Quand depuis un composant Razor enfant,
consultez ASP.NET Core – Sections Blazor.

Ressources supplémentaires
Disposition dans ASP.NET Core
Blazor exemples de référentiel GitHub (dotnet/blazor-samples)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Sections ASP.NET Core Blazor
Article • 09/02/2024

Cet article explique comment contrôler le contenu d’un composant Razor à partir d’un
composant Razor enfant.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : dans une application web Blazor, rendu côté serveur
interactif (SSR interactif) qui fonctionne sur une connexion SignalR avec le client ou
rendu côté serveur statique (SSR statique).
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un composant doit avoir un mode de rendu
interactif appliqué pour l’interactivité sur une connexion SignalR avec le client,
dans le fichier de définition du composant ou hérité d’un composant parent. Les
composants qui ne définissent pas de mode de rendu ou qui n’en héritent pas
sont rendus avec un SSR statique sur le serveur. Aucune connexion SignalR n’est
établie pour les composants rendus statiquement. Pour plus d’informations,
consultez Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.
Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples
d’applications BlazorSample_{PROJECT TYPE} à partir du dépôt GitHub d’exemples
Blazor qui correspond à la version de .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Blazor sections
Pour contrôler le contenu d’un composant Razor à partir d’un composant Razor enfant,
Blazor prend en charge les sections à l’aide des composants intégrés suivants :

SectionOutlet : affiche le contenu fourni par les composants SectionContent avec


des arguments SectionName ou SectionId correspondants. Deux composants
SectionOutlet ou plus ne peuvent pas avoir le même SectionName ou SectionId.

SectionContent : fournit le contenu en tant que RenderFragment aux composants


SectionOutlet avec un argument SectionName ou SectionId correspondant. Si
plusieurs composants SectionContent ont le même argument SectionName ou
SectionId, le composant SectionOutlet correspondant affiche le contenu du dernier
composant SectionContent rendu.

Les sections peuvent être utilisées dans les dispositions et entre les composants parent-
enfant imbriqués.

Bien que l’argument transmis à SectionName puisse utiliser n’importe quel type de
casse, la documentation adopte la casse kebab (par exemple, top-bar ), qui est un choix
de casse courant pour les ID d’élément HTML. SectionId reçoit un champ object
statique et nous recommandons toujours la casse Pascal pour les noms de champs C#
(par exemple, TopbarSection ).

Dans l’exemple suivant, le composant de disposition principale de l’application


implémente un bouton de compteur d’incréments pour le composant Counter de
l’application.

Si l’espace de noms des sections n’est pas dans le fichier _Imports.razor , ajoutez-le :

razor

@using Microsoft.AspNetCore.Components.Sections
Dans le composant MainLayout ( MainLayout.razor ), placez un composant SectionOutlet
et transmettez une chaîne au paramètre SectionName pour indiquer le nom de la
section. L’exemple suivant utilise le nom de section top-bar :

razor

<SectionOutlet SectionName="top-bar" />

Dans le composant Counter ( Counter.razor ), créez un composant SectionContent et


transmettez la chaîne correspondante ( top-bar ) à son paramètre SectionName :

razor

<SectionContent SectionName="top-bar">
<button class="btn btn-primary" @onclick="IncrementCount">Click
me</button>
</SectionContent>

Quand le composant Counter est accessible à /counter , le composant MainLayout


affiche le bouton de nombre d’incréments à partir du composant Counter où le
composant SectionOutlet est placé. Quand un autre composant est accessible, le bouton
de nombre d’incréments n’est pas affiché.

Au lieu d’utiliser une section nommée, vous pouvez transmettre un champg object
statique avec le paramètre SectionId pour identifier la section. L’exemple suivant
implémente également un bouton de compteur d’incréments pour le composant
Counter de l’application dans la disposition principale de l’application.

Si vous ne souhaitez pas que d’autres composants SectionContent correspondent


accidentellement au nom d’un composant SectionOutlet, transmettez un paramètre
d’objet SectionId pour identifier la section. Cela peut être utile lors de la conception
d’une bibliothèque de classes Razor (RCL). Quand un composant SectionOutlet dans la
bibliothèque RCL utilise une référence d’objet avec SectionId et que le consommateur
place un composant SectionContent avec un objet SectionId correspondant, une
correspondance accidentelle par nom n’est pas possible quand les consommateurs de la
bibliothèque RCL implémentent d’autres composants SectionContent.

L’exemple suivant implémente également un bouton de compteur d’incréments pour le


composant Counter de l’application dans la disposition principale de l’application à
l’aide d’une référence d’objet au lieu d’un nom de section.

Ajoutez un champ object statique TopbarSection au composant MainLayout dans un


bloc @code :
razor

@code {
internal static object TopbarSection = new();
}

Dans le balise Razor du composant MainLayout , placez un composant SectionOutlet et


transmettez TopbarSection au paramètre SectionId pour indiquer la section :

razor

<SectionOutlet SectionId="TopbarSection" />

Ajoutez un composant SectionContent au composant Counter de l’application qui


affiche un bouton de nombre d’incréments. Utilisez le champ statique object de section
TopbarSection du composant MainLayout comme SectionId ( MainLayout.TopbarSection ).

Dans Counter.razor :

razor

<SectionContent SectionId="MainLayout.TopbarSection">
<button class="btn btn-primary" @onclick="IncrementCount">Click
me</button>
</SectionContent>

Quand le composant Counter est accessible, le composant MainLayout , affiche le


bouton de nombre d’incréments où le composant SectionOutlet est placé.

7 Notes

Les composants SectionOutlet et SectionContent ne peuvent définir que SectionId


ou SectionName, et non les deux.

Interaction de section avec d’autres


fonctionnalités Blazor
Une section interagit avec d’autres fonctionnalités Blazor des manières suivantes :

Les valeurs en cascade arrivent dans le contenu de section à partir duquel le


contenu est défini par le composant SectionContent.
Les exceptions non prises en charge sont gérées par des limites d’erreur définies
pour un composant SectionContent.
Un composant Razor configuré pour le rendu en streaming configure également le
contenu de section fourni par un composant SectionContent pour utiliser le rendu
en streaming.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Contrôlez le contenu <head> dans les
applications ASP.NET Core Blazor
Article • 28/12/2023

Les composants Razor peuvent modifier le contenu de l’élément HTML <head> d’une
page, notamment la définition du titre de la page (élément <title> ) et la modification
des métadonnées (éléments <meta> ).

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.
La meilleure façon d’exécuter le code de démonstration consiste à télécharger
les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Contrôlez le contenu <head> d’un composant


Razor
Spécifiez le titre de la page avec le composant PageTitle, ce qui permet de rendre un
élément HTML <title> dans un HeadOutlet composant.

Spécifiez le contenu d’élément <head> avec le composant HeadContent, qui fournit du


contenu à un HeadOutlet composant.

L’exemple suivant définit le titre et la description de la page à l’aide de Razor.

ControlHeadContent.razor :

razor

@page "/control-head-content"

<PageTitle>@title</PageTitle>

<h1>Control <head> Content Example</h1>

<p>
Title: @title
</p>

<p>
Description: @description
</p>

<HeadContent>
<meta name="description" content="@description">
</HeadContent>

@code {
private string description = "This description is set by the
component.";
private string title = "Control <head> Content";
}
HeadOutlet (composant)
Le composant HeadOutlet affiche le contenu fourni par les composants PageTitle et
HeadContent.

Dans une application web Blazor créée à partir du modèle de projet, le composant
HeadOutlet dans App.razor affiche le contenu <head> :

razor

<head>
...
<HeadOutlet />
</head>

Dans une application créée à partir du modèle de projet Blazor WebAssembly, le


composant HeadOutlet est ajouté à la collection RootComponents de
WebAssemblyHostBuilder dans le Program côté client :

C#

builder.RootComponents.Add<HeadOutlet>("head::after");

Lorsque le ::after pseudo-sélecteur est spécifié, le contenu du composant racine est


ajouté au contenu de l’en-tête existant au lieu de remplacer le contenu. Cela permet à
l’application de conserver le contenu de l’en-tête statique dans wwwroot/index.html sans
avoir à répéter le contenu dans les composants Razor de l’application.

Titre de la page introuvable


Cette section s’applique uniquement aux applications Blazor WebAssembly.

Dans les applications Blazor créées à partir d’un modèle de projet Application autonome
Blazor WebAssembly, le modèle de composant NotFound dans le composant App
( App.razor ) définit le titre de la page sur Not found .

App.razor :

razor

<PageTitle>Not found</PageTitle>
Ressources supplémentaires
Contrôle des en-têtes dans le code C# au démarrage
Blazor exemples de référentiel GitHub (dotnet/blazor-samples)

Documentation de Mozilla MDN Web Docs :

Que contient l’en-tête ? Métadonnées en HTML


<head> : L’élément Métadonnées du document (en-tête)
<title> : L’élément Titre du document
<meta> : L’élément de métadonnées

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Valeurs et paramètres en cascade
ASP.NET Core Blazor
Article • 21/12/2023

Cet article explique comment faire passer le flux de données d’un composant Razor
ancêtre aux composants descendants.

Les valeurs et paramètres en cascade offrent un moyen pratique de faire passer le flux de
données vers le bas d’une hiérarchie de composants, d’un composant ancêtre vers un
nombre quelconque de composants descendants. Contrairement aux paramètres de
composant, les valeurs et paramètres en cascade ne nécessitent pas d’affectation
d’attribut pour chaque composant descendant où les données sont consommées. Les
valeurs et paramètres en cascade permettent également aux composants de se
coordonner entre eux au sein d’une hiérarchie de composants.

7 Notes

Les exemples de code de cet article adoptent les références null (NRT) et l'analyse
statique de l'état null du compilateur .NET, qui sont pris en charge dans ASP.NET
Core 6.0 ou une version ultérieure. Lorsque vous ciblez ASP.NET Core 5.0 ou version
antérieure, supprimez la désignation de type Null ( ? ) des types CascadingType? ,
@ActiveTab? , RenderFragment? , ITab? , TabSet? et string? dans les exemples de

l’article.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode de rendu interactif avec une directive @rendermode dans le fichier de définition du
composant ( .razor ) :

Dans une application web Blazor, le composant doit avoir un mode de rendu
interactif appliqué, soit dans le fichier de définition du composant, soit hérité d’un
composant parent. Pour plus d’informations, consultez Modes de rendu ASP.NET
Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode de rendu, car
les composants s’exécutent toujours de manière interactive sur WebAssembly dans
une application Blazor WebAssembly.

Lors de l’utilisation des modes WebAssembly interactif ou Rendu automatique interactif,


le code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas
de code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration est de télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne sont pas inclus dans les exemples
d’applications, mais nous nous efforçons de transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Valeurs en cascade du niveau racine


Les valeurs en cascade au niveau racine peuvent être inscrites pour la hiérarchie
complète des composants. Les valeurs et abonnements nommés en cascade pour les
notifications de mise à jour sont pris en charge.

La classe suivante est utilisée dans les exemples de cette section.

Daleks.cs :

C#

// "Dalek" ©Terry Nation https://www.imdb.com/name/nm0622334/


// "Doctor Who" ©BBC https://www.bbc.co.uk/programmes/b006q2x0

namespace BlazorSample;

public class Daleks


{
public int Units { get; set; }
}

Les inscriptions suivantes sont effectuées dans le fichier Program de l’application :

Avec une valeur de propriété pour Units , Daleks est inscrit comme valeur en
cascade fixe.
Une deuxième inscription Daleks avec une valeur de propriété différente pour
Units est nommée « AlphaGroup ».

C#

builder.Services.AddCascadingValue(sp => new Daleks { Units = 123 });


builder.Services.AddCascadingValue("AlphaGroup", sp => new Daleks { Units =
456 });

Le composant Daleks suivant affiche les valeurs en cascade.

Daleks.razor :

razor

@page "/daleks"

<h1>Root-level cascading value registration example</h1>

<ul>
<li>Dalek Units: @Daleks?.Units</li>
<li>Alpha Group Dalek Units: @AlphaGroupDaleks?.Units</li>
</ul>

<p>
Dalek© <a href="https://www.imdb.com/name/nm0622334/">Terry Nation</a>
<br>
Doctor Who© <a href="https://www.bbc.co.uk/programmes/b006q2x0">BBC</a>
</p>

@code {
[CascadingParameter]
public Daleks? Daleks { get; set; }

[CascadingParameter(Name = "AlphaGroup")]
public Daleks? AlphaGroupDaleks { get; set; }
}

Dans l’exemple suivant, Daleks est inscrit comme valeur en cascade à l’aide de
CascadingValueSource<T>, avec <T> comme type. L’indicateur isFixed signale si la
valeur est corrigée. Si la valeur est false, tous les destinataires sont abonnés aux
notifications de mise à jour émises en appelant NotifyChangedAsync. Les abonnements
créent des surcharges et réduisent les performances. Définissez donc isFixed sur true ,
si la valeur ne change pas.

C#

builder.Services.AddCascadingValue(sp =>
{
var daleks = new Daleks { Units = 789 };
var source = new CascadingValueSource<Daleks>(daleks, isFixed: false);
return source;
});

Composant CascadingValue
Un composant ancêtre fournit une valeur en cascade à l’aide du composant
CascadingValue du framework Blazor, qui inclut dans un wrapper une sous-arborescence
d’une hiérarchie de composants, et fournit une seule valeur à tous les composants de sa
sous-arborescence.

L’exemple suivant montre le flux des informations de thème vers le bas de la hiérarchie
des composants pour fournir une classe de style CSS aux boutons des composants
enfants.

La classe C# ThemeInfo suivante spécifie les informations relatives au thème.

7 Notes

Pour les exemples de cette section, l’espace de noms de l’application est


BlazorSample . Quand vous testez le code dans votre propre exemple d’application,

remplacez l’espace de noms de l’application par l’espace de noms de votre


exemple d’application.

ThemeInfo.cs :

C#

namespace BlazorSample;

public class ThemeInfo


{
public string? ButtonClass { get; set; }
}
Le composant de disposition suivant spécifie les informations de thème ( ThemeInfo )
sous la forme d’une valeur en cascade pour tous les composants qui constituent le corps
de disposition de la propriété Body. ButtonClass se voit affecter la valeur btn-success ,
qui est un style de bouton Bootstrap. Tout composant descendant dans la hiérarchie de
composants peut utiliser la propriété ButtonClass via la valeur en cascade ThemeInfo .

MainLayout.razor :

razor

@inherits LayoutComponentBase

<div class="page">
<div class="sidebar">
<NavMenu />
</div>

<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/"
target="_blank">About</a>
</div>

<CascadingValue Value="@theme">
<article class="content px-4">
@Body
</article>
</CascadingValue>
</main>
</div>

<div id="blazor-error-ui" data-nosnippet>


An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}

Blazor Web Apps propose des approches alternatives pour les valeurs en cascade qui
s’appliquent plus largement à l’application que de les fournir via une layout :

Encapsulez le balisage du composant Routes dans un composant CascadingValue


pour spécifier les données comme valeur en cascade pour tous les composants de
l’application.

L’exemple suivant cascade les données ThemeInfo du composant Routes .


Routes.razor :

razor

<CascadingValue Value="@theme">
<Router ...>
<Found ...>
...
</Found>
</Router>
</CascadingValue>

@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}

Dans le composant App ( Components/App.razor ), adoptez un mode de rendu


interactif pour l’ensemble de l’application. L’exemple suivant adopte le rendu
interactif côté serveur (SSR interactif) :

razor

<Routes @rendermode="InteractiveServer" />

Spécifiez une valeur en cascade du niveau racine en tant que service en appelant la
méthode d’extension AddCascadingValue sur le générateur de collection de
services.

L’exemple suivant cascade les données ThemeInfo du fichier Program .

Program.cs

C#

builder.Services.AddCascadingValue(sp =>
new ThemeInfo() { ButtonClass = "btn-primary" });

Pour plus d’informations, consultez les sections suivantes de cette article :

Valeurs en cascade du niveau racine


Valeurs/paramètres en cascade et limites du mode de rendu

Attribut [CascadingParameter]
Pour utiliser des valeurs en cascade, les composants descendants déclarent les
paramètres en cascade à l’aide de l’attribut [CascadingParameter]. Les valeurs en
cascade sont liées aux paramètres en cascade par type. La création d’une cascade de
plusieurs valeurs du même type est traitée dans la section Créer une cascade de
plusieurs valeurs plus loin dans cet article.

Le composant suivant lie la valeur en cascade ThemeInfo à un paramètre en cascade, en


utilisant éventuellement le même nom que ThemeInfo . Le paramètre permet de définir la
classe CSS du bouton Increment Counter (Themed) .

ThemedCounter.razor :

razor

@page "/themed-counter"

<PageTitle>Themed Counter</PageTitle>

<h1>Themed Counter Example</h1>

<p>Current count: @currentCount</p>

<p>
<button @onclick="IncrementCount">
Increment Counter (Unthemed)
</button>
</p>

<p>
<button
class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass :
string.Empty)"
@onclick="IncrementCount">
Increment Counter (Themed)
</button>
</p>

@code {
private int currentCount = 0;

[CascadingParameter]
protected ThemeInfo? ThemeInfo { get; set; }

private void IncrementCount()


{
currentCount++;
}
}
À l’image d’un paramètre de composant classique, les composants qui acceptent un
paramètre en cascade sont réaffichés quand la valeur en cascade change. Par exemple,
la configuration d’une autre instance de thème entraîne un nouveau rendu du
composant ThemedCounter décrit dans la section Composant CascadingValue.

MainLayout.razor :

razor

<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/"
target="_blank">About</a>
</div>

<CascadingValue Value="@theme">
<article class="content px-4">
@Body
</article>
</CascadingValue>
<button @onclick="ChangeToDarkTheme">Dark mode</button>
</main>

@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };

private void ChangeToDarkTheme()


{
theme = new() { ButtonClass = "btn-darkmode-success" };
}
}

CascadingValue<TValue>.IsFixed peut être utilisé pour indiquer qu’un paramètre en


cascade ne change pas après son initialisation.

Valeurs/paramètres en cascade et limites du


mode de rendu
Les paramètres en cascade ne passent pas de données entre les limites du mode de
rendu :

Les sessions interactives s’exécutent dans un contexte différent de celui des pages
qui utilisent un rendu statique côté serveur (SSR statique). Il n’est pas nécessaire
que le serveur produisant la page soit la même machine que celle qui héberge une
session ultérieure du serveur interactif, y compris pour les composants
WebAssembly où le serveur est une machine différente pour le client. L’avantage
du rendu statique côté serveur (SSR statique) est de bénéficier de tous les niveaux
de performance du rendu HTML sans état.

L’état qui traverse la limite entre le rendu statique et le rendu interactif doit être
sérialisable. Les composants sont des objets arbitraires qui référencent une vaste
chaîne d’autres objets, y compris le renderer, le conteneur d’injection de
dépendances et chaque instance du service d’injection de dépendances. Vous
devez explicitement faire en sorte que l’état soit sérialisé à partir d’un SSR statique
pour qu’il soit disponible dans les composants interactifs suivants. Deux approches
sont adoptées :
Grâce au cadre Blazor, les paramètres transmis d’un SSR statique à une frontière
de rendu interactive sont sérialisés automatiquement s’ils sont JSsérialisables,
sinon une erreur est générée.
L’état stocké dans PersistentComponentState est sérialisé et récupéré
automatiquement s’il est sérialisable en JSON ; sinon, une erreur est levée.

Les paramètres en cascade ne sont pas sérialisés en JSON, car les modèles d’utilisation
classiques pour les paramètres en cascade sont un peu comme les services d’injection
de dépendances. Il existe souvent des variantes des paramètres en cascade qui sont
spécifiques à la plateforme : il serait donc inutile pour les développeurs que le
framework les empêche d’avoir des versions spécifiques au serveur interactif ou des
versions spécifiques au WebAssembly. En outre, de nombreuses valeurs de paramètres
en cascade ne sont généralement pas sérialisables : il serait donc impraticable de mettre
à jour des applications existantes si vous deviez arrêter d’utiliser toutes les valeurs de
paramètre en cascade non sérialisables.

Recommandations :

Si vous devez rendre l’état disponible pour tous les composants interactifs sous
forme de paramètre en cascade, nous vous recommandons d’utiliser des valeurs en
cascade au niveau racine. Un modèle de fabrique est disponible et l’application
peut émettre des valeurs mises à jour après son démarrage. Les valeurs en cascade
au niveau racine sont disponibles pour tous les composants, y compris les
composants interactifs, car elles sont traitées en tant que services d’injection de
dépendances.

Pour les créateurs de bibliothèques de composants, vous pouvez créer une


méthode d’extension pour les consommateurs de bibliothèques, similaire à ceci :

C#

builder.Services.AddLibraryCascadingParameters();
Demandez aux développeurs d’appeler votre méthode d’extension. C’est une
bonne alternative que de leur demander d’ajouter un composant <RootComponent>
dans leur composant MainLayout .

Créer une cascade de plusieurs valeurs


Pour créer une cascade de plusieurs valeurs du même type dans la même sous-
arborescence, fournissez une seule chaîne Name à chaque composant CascadingValue
ainsi qu’à leurs attributs [CascadingParameter] correspondants.

Dans l’exemple suivant, deux composants CascadingValue font passer en cascade


différentes instances de CascadingType :

razor

<CascadingValue Value="@parentCascadeParameter1" Name="CascadeParam1">


<CascadingValue Value="@ParentCascadeParameter2" Name="CascadeParam2">
...
</CascadingValue>
</CascadingValue>

@code {
private CascadingType? parentCascadeParameter1;

[Parameter]
public CascadingType? ParentCascadeParameter2 { get; set; }
}

Dans un composant descendant, les paramètres en cascade reçoivent leurs valeurs en


cascade du composant ancêtre par Name :

razor

@code {
[CascadingParameter(Name = "CascadeParam1")]
protected CascadingType? ChildCascadeParameter1 { get; set; }

[CascadingParameter(Name = "CascadeParam2")]
protected CascadingType? ChildCascadeParameter2 { get; set; }
}

Passer des données dans une hiérarchie de


composants
Les paramètres en cascade permettent également aux composants de passer des
données au sein d’une hiérarchie de composants. Prenons l’exemple suivant d’un
ensemble d’onglets d’IU, où un composant d’ensemble d’onglets gère une série
d’onglets individuels.

7 Notes

Pour les exemples de cette section, l’espace de noms de l’application est


BlazorSample . Quand vous testez le code dans votre propre exemple d’application,

remplacez l’espace de noms par l’espace de noms de votre exemple d’application.

Créez une interface ITab que les onglets implémentent dans un dossier nommé
UIInterfaces .

UIInterfaces/ITab.cs :

C#

using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces;

public interface ITab


{
RenderFragment ChildContent { get; }
}

7 Notes

Pour plus d’informations sur RenderFragment, consultez Composants ASP.NET


Core Razor.

Le composant TabSet suivant gère un ensemble d’onglets. Les composants Tab de


l’ensemble d’onglets, créés plus loin dans cette section, fournissent les éléments de liste
( <li>...</li> ) de la liste ( <ul>...</ul> ).

Les composants Tab enfants ne sont pas explicitement passés en tant que paramètres à
TabSet . À la place, les composants Tab enfants font partie du contenu enfant de

TabSet . Toutefois, le TabSet a toujours besoin d’une référence pour chaque composant
Tab afin qu’il puisse afficher les en-têtes et l’onglet actif. Pour activer cette coordination

sans nécessiter de code supplémentaire, le TabSet composant peut servir lui-même de


valeur en cascade, qui est ensuite récupérée par les composants Tab descendants.
TabSet.razor :

razor

@using BlazorSample.UIInterfaces

<!-- Display the tab headers -->

<CascadingValue Value="this">
<ul class="nav nav-tabs">
@ChildContent
</ul>
</CascadingValue>

<!-- Display body for only the active tab -->

<div class="nav-tabs-body p-4">


@ActiveTab?.ChildContent
</div>

@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }

public ITab? ActiveTab { get; private set; }

public void AddTab(ITab tab)


{
if (ActiveTab is null)
{
SetActiveTab(tab);
}
}

public void SetActiveTab(ITab tab)


{
if (ActiveTab != tab)
{
ActiveTab = tab;
StateHasChanged();
}
}
}

Les composants Tab descendants capturent le TabSet conteneur en tant que paramètre
en cascade. Les composants Tab s’ajoutent à TabSet , et se coordonnent pour définir
l’onglet actif.

Tab.razor :

razor
@using BlazorSample.UIInterfaces
@implements ITab

<li>
<a @onclick="ActivateTab" class="nav-link @TitleCssClass" role="button">
@Title
</a>
</li>

@code {
[CascadingParameter]
public TabSet? ContainerTabSet { get; set; }

[Parameter]
public string? Title { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }

private string? TitleCssClass =>


ContainerTabSet?.ActiveTab == this ? "active" : null;

protected override void OnInitialized()


{
ContainerTabSet?.AddTab(this);
}

private void ActivateTab()


{
ContainerTabSet?.SetActiveTab(this);
}
}

Le composant ExampleTabSet suivant utilise le composant TabSet , qui contient trois


composants Tab .

ExampleTabSet.razor :

razor

@page "/example-tab-set"

<TabSet>
<Tab Title="First tab">
<h4>Greetings from the first tab!</h4>

<label>
<input type="checkbox" @bind="showThirdTab" />
Toggle third tab
</label>
</Tab>
<Tab Title="Second tab">
<h4>Hello from the second tab!</h4>
</Tab>

@if (showThirdTab)
{
<Tab Title="Third tab">
<h4>Welcome to the disappearing third tab!</h4>
<p>Toggle this tab from the first tab.</p>
</Tab>
}
</TabSet>

@code {
private bool showThirdTab;
}

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Gestion des événements ASP.NET Core
Blazor
Article • 09/02/2024

Cet article explique les fonctionnalités de gestion des événements de Blazor,


notamment les types d’arguments d’événement, les rappels d’événements et la gestion
des événements de navigateur par défaut.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.
Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples
d’applications BlazorSample_{PROJECT TYPE} à partir du dépôt GitHub d’exemples
Blazor qui correspond à la version de .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Déléguer des gestionnaires d’événements


Spécifiez les gestionnaires d’événements délégués dans le balisage de composant Razor
avec la syntaxe @on{DOM EVENT}="{DELEGATE}"Razor :

L’espace réservé {DOM EVENT} est un événement DOM (par exemple, click ).
L’espace réservé {DELEGATE} est le gestionnaire d’événements délégué C#.

Pour la gestion des événements :

Les gestionnaires d’événements délégués asynchrones qui retournent Task sont


pris en charge.
Les gestionnaires d’événements délégués déclenchent automatiquement un rendu
d’interface utilisateur. Il n’est donc pas nécessaire d’appeler manuellement
StateHasChanged.
Les exceptions sont consignées.

L'exemple de code suivant :

Appelle la méthode UpdateHeading lorsque le bouton est sélectionné dans


l’interface utilisateur.
Appelle la méthode CheckChanged lorsque la case à cocher est modifiée dans
l’interface utilisateur.

EventHandler1.razor :

razor

@page "/event-handler-1"

<PageTitle>Event Handler 1</PageTitle>

<h1>Event Handler Example 1</h1>

<h2>@headingValue</h2>

<p>
<button @onclick="UpdateHeading">
Update heading
</button>
</p>

<p>
<label>
<input type="checkbox" @onchange="CheckChanged" />
@checkedMessage
</label>
</p>

@code {
private string headingValue = "Initial heading";
private string checkedMessage = "Not changed yet";

private void UpdateHeading()


{
headingValue = $"New heading ({DateTime.Now})";
}

private void CheckChanged()


{
checkedMessage = $"Last changed at {DateTime.Now}";
}
}

Dans l’exemple suivant, UpdateHeading :

Est appelé de manière asynchrone lorsque le bouton est sélectionné.


Attend deux secondes avant de mettre à jour le titre.

EventHandler2.razor :

razor

@page "/event-handler-2"

<PageTitle>Event Handler 2</PageTitle>

<h1>Event Handler Example 2</h1>

<h2>@headingValue</h2>

<p>
<button @onclick="UpdateHeading">
Update heading
</button>
</p>

@code {
private string headingValue = "Initial heading";
private async Task UpdateHeading()
{
await Task.Delay(2000);

headingValue = $"New heading ({DateTime.Now})";


}
}

Arguments d’événement intégrés


Pour les événements qui prennent en charge un type d’argument d’événement, la
spécification d’un paramètre d’événement dans la définition de méthode d’événement
n’est nécessaire que si le type d’événement est utilisé dans la méthode. Dans l’exemple
suivant, MouseEventArgs est utilisé dans la méthode ReportPointerLocation pour définir
le texte du message qui signale les coordonnées de la souris lorsque l’utilisateur
sélectionne un bouton dans l’interface utilisateur.

EventHandler3.razor :

razor

@page "/event-handler-3"

<PageTitle>Event Handler 3</PageTitle>

<h1>Event Handler Example 3</h1>

@for (var i = 0; i < 4; i++)


{
<p>
<button @onclick="ReportPointerLocation">
Where's my mouse pointer for this button?
</button>
</p>
}

<p>@mousePointerMessage</p>

@code {
private string? mousePointerMessage;

private void ReportPointerLocation(MouseEventArgs e)


{
mousePointerMessage = $"Mouse coordinates: {e.ScreenX}:{e.ScreenY}";
}
}
Les EventArgs pris en charge sont indiqués dans le tableau suivant.

ノ Agrandir le tableau

événement Classe Notes DOM

Presse-papiers ClipboardEventArgs

Glisser DragEventArgs DataTransfer et DataTransferItem contiennent les données


de l’élément déplacé.

Implémentez le glisser-déplacer dans les applications


Blazor à l’aide de l’interopérabilité JS avec l’API glisser-
déplacer HTML .

Error ErrorEventArgs

événement EventArgs EventHandlers contient des attributs pour configurer les


mappages entre les noms d’événements et les types
d’arguments d’événement.

Focus FocusEventArgs N’inclut pas la prise en charge de relatedTarget .

Entrée ChangeEventArgs

Clavier KeyboardEventArgs

Souris MouseEventArgs

Pointeur de PointerEventArgs
souris

Roulette de la WheelEventArgs
souris

Progression ProgressEventArgs

Entrées TouchEventArgs TouchPoint représente un point de contact unique sur un


tactiles appareil tactile.

Pour plus d'informations, reportez-vous aux ressources suivantes :

Classes EventArgs dans la source de référence ASP.NET Core (branche


dotnet/aspnetcore main)

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

EventHandlers contient des attributs pour configurer les mappages entre les noms
d’événements et les types d’arguments d’événement.

Arguments d’événement personnalisés


Blazor prend en charge les arguments d’événements personnalisés, ce qui vous permet
de transmettre des données arbitraires aux gestionnaires d’événements .NET avec des
événements personnalisés.

Configuration générale
Les événements personnalisés avec des arguments d’événement personnalisés sont
généralement activés en procédant comme suit.

En JavaScript, définissez une fonction pour générer l’objet d’argument d’événement


personnalisé à partir de l’événement source :

JavaScript

function eventArgsCreator(event) {
return {
customProperty1: 'any value for property 1',
customProperty2: event.srcElement.id
};
}

Le event paramètre est un événement DOM (documentation MDN).

Inscrivez l’événement personnalisé avec le gestionnaire précédent dans un initialiseur


JavaScript. Indiquez le nom d’événement de navigateur approprié à browserEventName ,
qui, pour l’exemple indiqué dans cette section, correspond à une sélection de bouton
click dans l’interface utilisateur.

wwwroot/{PACKAGE ID/ASSEMBLY NAME}.lib.module.js (l’ {PACKAGE ID/ASSEMBLY

NAME} espace réservé est l’ID du package ou le nom de l’assembly de l’application) :

Pour une application web Blazor :


JavaScript

export function afterWebStarted(blazor) {


blazor.registerCustomEventType('customevent', {
browserEventName: 'click',
createEventArgs: eventArgsCreator
});
}

Pour une application Blazor Server ou Blazor WebAssembly :

JavaScript

export function afterStarted(blazor) {


blazor.registerCustomEventType('customevent', {
browserEventName: 'click',
createEventArgs: eventArgsCreator
});
}

7 Notes

L’appel à registerCustomEventType n’est effectué dans un script qu’une seule fois


par événement.

Pour l’appel à registerCustomEventType , utilisez le paramètre blazor ( b (en)


minuscules) fourni par l’événement de démarrage Blazor. Bien que l’inscription soit
valide lors de l’utilisation de l’objet Blazor ( B majuscule), l’approche recommandée
consiste à utiliser le paramètre.

Définissez une classe pour les arguments d’événement :

C#

namespace BlazorSample.CustomEvents;

public class CustomEventArgs : EventArgs


{
public string? CustomProperty1 {get; set;}
public string? CustomProperty2 {get; set;}
}

Reliez l’événement personnalisé avec les arguments de l’événement en ajoutant une


annotation d’attribut [EventHandler] pour l’événement personnalisé :
Pour que le compilateur recherche la classe [EventHandler] , celle-ci doit être
placée dans un fichier de classe C# ( .cs ), ce qui en fait une classe de niveau
supérieur normale.
Marquez la classe comme public .
La classe ne nécessite pas de membres.
La classe doit être appelée « EventHandlers » pour être trouvée par le compilateur
Razor.
Placez la classe sous un espace de noms spécifique à votre application.
Importez l’espace de noms dans le composant Razor ( .razor ) où l’événement est
utilisé.

C#

using Microsoft.AspNetCore.Components;

namespace BlazorSample.CustomEvents;

[EventHandler("oncustomevent", typeof(CustomEventArgs),
enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}

Inscrivez le gestionnaire d’événements sur un ou plusieurs éléments HTML. Accédez aux


données qui ont été transmises à partir de JavaScript dans la méthode de gestionnaire
de délégués :

razor

@using BlazorSample.CustomEvents

<button id="buttonId" @oncustomevent="HandleCustomEvent">Handle</button>

@if (!string.IsNullOrEmpty(propVal1) && !string.IsNullOrEmpty(propVal2))


{
<ul>
<li>propVal1: @propVal1</li>
<li>propVal2: @propVal2</li>
</ul>
}

@code
{
private string? propVal1;
private string? propVal2;

private void HandleCustomEvent(CustomEventArgs eventArgs)


{
propVal1 = eventArgs.CustomProperty1;
propVal2 = eventArgs.CustomProperty2;
}
}

Si l’attribut @oncustomevent n’est pas reconnu par IntelliSense, vérifiez que le composant
ou le fichier _Imports.razor contient une instruction @using pour l’espace de noms
contenant la classe EventHandler .

Chaque fois que l’événement personnalisé est déclenché sur le DOM, le gestionnaire
d’événements est appelé avec les données transmises à partir du JavaScript.

Si vous tentez de déclencher un événement personnalisé, bubbles doit être activé en


définissant sa valeur sur true . Sinon, l’événement n’atteint pas le gestionnaire Blazor
pour le traitement dans la classe d’attributs [EventHandler] personnalisée de C#. Pour
plus d’informations, consultez Documentation web MDN : Propagation des
événements .

Exemple d’événement de collage de presse-papiers


personnalisé
L’exemple suivant reçoit un événement de collage de presse-papiers personnalisé qui
inclut l’heure du collage et le texte collé de l’utilisateur.

Déclarez un nom personnalisé ( oncustompaste ) pour l’événement et une classe .NET


( CustomPasteEventArgs ) pour contenir les arguments de l’événement pour cet
événement :

CustomEvents.cs :

C#

using Microsoft.AspNetCore.Components;

namespace BlazorSample.CustomEvents;

[EventHandler("oncustompaste", typeof(CustomPasteEventArgs),
enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}

public class CustomPasteEventArgs : EventArgs


{
public DateTime EventTimestamp { get; set; }
public string? PastedData { get; set; }
}

Ajoutez du code JavaScript pour fournir des données pour la sous-classe EventArgs avec
le gestionnaire précédent dans un initialiseur JavaScript. L’exemple suivant gère
uniquement le collage de texte, mais vous pouvez utiliser des API JavaScript arbitraires
pour traiter les utilisateurs collant d’autres types de données, comme des images.

wwwroot/{PACKAGE ID/ASSEMBLY NAME}.lib.module.js :

Pour une application web Blazor :

JavaScript

export function afterWebStarted(blazor) {


blazor.registerCustomEventType('custompaste', {
browserEventName: 'paste',
createEventArgs: event => {
return {
eventTimestamp: new Date(),
pastedData: event.clipboardData.getData('text')
};
}
});
}

Pour une application Blazor Server ou Blazor WebAssembly :

JavaScript

export function afterStarted(blazor) {


blazor.registerCustomEventType('custompaste', {
browserEventName: 'paste',
createEventArgs: event => {
return {
eventTimestamp: new Date(),
pastedData: event.clipboardData.getData('text')
};
}
});
}

Dans l’exemple précédent, l’espace réservé {PACKAGE ID/ASSEMBLY NAME} du nom de


fichier représente l’ID de package ou le nom d’assembly de l’application.

7 Notes
Pour l’appel à registerCustomEventType , utilisez le paramètre blazor ( b (en)
minuscules) fourni par l’événement de démarrage Blazor. Bien que l’inscription soit
valide lors de l’utilisation de l’objet Blazor ( B majuscule), l’approche recommandée
consiste à utiliser le paramètre.

Le code précédent indique au navigateur que lorsqu’un événement natif paste se


produit, il doit :

Déclencher un événement custompaste .


Fournir les données des arguments d’événement à l’aide de la logique
personnalisée indiquée :
Pour eventTimestamp , créer une date.
Pour pastedData , obtenir les données du presse-papiers sous forme de texte.
Pour plus d’informations, consultez Documentation web MDN :
ClipboardEvent.clipboardData .

Les conventions de nom d’événement diffèrent entre .NET et JavaScript :

Dans .NET, les noms d’événement sont précédés de « on ».


En JavaScript, les noms d’événement n’ont pas de préfixe.

Dans un composant Razor, attachez le gestionnaire personnalisé à un élément.

CustomPasteArguments.razor :

razor

@page "/custom-paste-arguments"
@using BlazorSample.CustomEvents

<label>
Try pasting into the following text box:
<input @oncustompaste="HandleCustomPaste" />
</label>

<p>
@message
</p>

@code {
private string? message;

private void HandleCustomPaste(CustomPasteEventArgs eventArgs)


{
message = $"At {eventArgs.EventTimestamp.ToShortTimeString()}, " +
$"you pasted: {eventArgs.PastedData}";
}
}

Expressions lambda
Les expressions lambda sont prises en charge en tant que gestionnaire d’événements
délégués.

EventHandler4.razor :

razor

@page "/event-handler-4"

<PageTitle>Event Handler 4</PageTitle>

<h1>Event Handler Example 4</h1>

<h2>@heading</h2>

<p>
<button @onclick="@(e => heading = "New heading!!!")">
Update heading
</button>
</p>

@code {
private string heading = "Initial heading";
}

Il est souvent pratique de fermer avec des valeurs supplémentaires à l’aide de


paramètres de méthode C#, par exemple lors de l’itération sur un ensemble d’éléments.
L’exemple suivant crée trois boutons ; chacun d’entre eux appelle UpdateHeading et
transmet les données suivantes :

Argument d’événement (MouseEventArgs) dans e .


Numéro de bouton dans buttonNumber .

EventHandler5.razor :

razor

@page "/event-handler-5"

<PageTitle>Event Handler 5</PageTitle>

<h1>Event Handler Example 5</h1>


<h2>@heading</h2>

@for (var i = 1; i < 4; i++)


{
var buttonNumber = i;

<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
</p>
}

@code {
private string heading = "Select a button to learn its position";

private void UpdateHeading(MouseEventArgs e, int buttonNumber)


{
heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
}
}

La création d’un grand nombre de délégués d’événements dans une boucle peut
entraîner des performances de rendu médiocres. Pour plus d’informations, consultez
Bonnes pratiques relatives aux performances d’ASP.NET Core Blazor.

Évitez d’utiliser une variable de boucle directement dans une expression lambda,
comme i dans l’exemple de boucle for précédent. Sinon, la même variable est utilisée
par toutes les expressions lambda, ce qui entraîne l’utilisation de la même valeur dans
toutes les expressions lambda. Capturez la valeur de la variable dans une variable locale.
Dans l'exemple précédent :

La variable de boucle i est affectée à buttonNumber .


buttonNumber est utilisé dans l’expression lambda.

Vous pouvez également utiliser une boucle foreach avec Enumerable.Range, qui ne
souffre pas du problème précédent :

razor

@foreach (var buttonNumber in Enumerable.Range(1,3))


{
<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@buttonNumber
</button>
</p>
}

EventCallback
Un scénario courant avec les composants imbriqués exécute une méthode dans un
composant parent lorsqu’un événement de composant enfant se produit. Un événement
onclick se produisant dans le composant enfant est un cas d’usage courant. Pour

exposer des événements entre les composants, utilisez un EventCallback. Un composant


parent peut affecter une méthode de rappel au EventCallback d’un composant enfant.

Le composant Child suivant montre comment le gestionnaire de onclick d’un bouton


est configuré pour recevoir un délégué EventCallback du ParentComponent de l’exemple.
Le EventCallback est typé avec MouseEventArgs, qui convient à un événement onclick
à partir d’un périphérique.

Child.razor :

razor

<p>
<button @onclick="OnClickCallback">
Trigger a Parent component method
</button>
</p>

@code {
[Parameter]
public string? Title { get; set; }

[Parameter]
public RenderFragment? ChildContent { get; set; }

[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

Le composant Parent définit le EventCallback<TValue> ( OnClickCallback ) de l’enfant


sur sa méthode ShowMessage .

ParentChild.razor :

razor

@page "/parent-child"
<PageTitle>Parent Child</PageTitle>

<h1>Parent Child Example</h1>

<Child Title="Panel Title from Parent" OnClickCallback="ShowMessage">


Content of the child component is supplied by the parent component.
</Child>

<p>@message</p>

@code {
private string? message;

private void ShowMessage(MouseEventArgs e)


{
message = $"Blaze a new trail with Blazor! ({e.ScreenX}:
{e.ScreenY})";
}
}

Lorsque le bouton est sélectionné dans le ChildComponent :

La méthode ShowMessage du composant Parent est appelée. message est mis à


jour et affiché dans le composant Parent .
Un appel à StateHasChanged n’est pas requis dans la méthode du rappel
( ShowMessage ). StateHasChanged est appelé automatiquement pour renvoyer le
composant Parent , tout comme les événements enfants déclenchent une nouvelle
génération de composant dans les gestionnaires d’événements qui s’exécutent
dans l’enfant. Pour plus d’informations, consultez le rendu de composants Razor
ASP.NET Core.

Utilisez EventCallback et EventCallback<TValue> pour la gestion des événements et les


paramètres de composant de liaison.

Préférez le EventCallback<TValue> fortement typé à EventCallback.


EventCallback<TValue> fournit des retours d’erreur améliorés aux utilisateurs du
composant. Comme pour d’autres gestionnaires d’événements d’interface utilisateur, la
spécification du paramètre d’événement est facultative. Utilisez EventCallback
lorsqu’aucune valeur n’est passée au rappel.

EventCallback et EventCallback<TValue> autorisent les délégués asynchrones.


EventCallback est faiblement typé et permet de passer n’importe quel type d’argument
dans InvokeAsync(Object) . EventCallback<TValue> est fortement typé et nécessite de
passer un argument T dans InvokeAsync(T) qui est assignable à TValue .
Appelez un EventCallback ou EventCallback<TValue> avec InvokeAsync et attendez
Task :

C#

await OnClickCallback.InvokeAsync();

L’exemple parent-enfant suivant montre la technique.

Child2.razor :

razor

<h3>Child2 Component</h3>

<button @onclick="TriggerEvent">Click Me</button>

@code {
[Parameter]
public EventCallback<string> OnClickCallback { get; set; }

private async Task TriggerEvent()


{
await OnClickCallback.InvokeAsync("Blaze It!");
}
}

ParentChild2.razor :

razor

@page "/parent-child-2"

<Child2 OnClickCallback="@(async (value) => { await Task.Yield();


messageText = value; })" />

<p>
@messageText
</p>

@code {
private string messageText = string.Empty;
}

Empêcher les actions par défaut


Utilisez l’attribut de directive @on{DOM EVENT}:preventDefault pour empêcher l’action
par défaut d’un événement, où l’espace réservé {DOM EVENT} est un événement DOM
(Document Object Model) .

Lorsqu’une touche est sélectionnée sur un périphérique d’entrée et que le focus


d’élément se trouve sur une zone de texte, un navigateur affiche normalement le
caractère de la touche dans la zone de texte. Dans l’exemple suivant, le comportement
par défaut est empêché en spécifiant l’attribut de directive @onkeydown:preventDefault .
Lorsque le focus se trouve sur l’élément <input> , le compteur s’incrémente avec la
séquence de touches Maj + + . Le caractère + n’est pas affecté à la valeur de l’élément
<input> . Pour plus d’informations sur keydown , consultez l’événement MDN Web Docs:

Document: keydown .

EventHandler6.razor :

razor

@page "/event-handler-6"

<PageTitle>Event Handler 6</PageTitle>

<h1>Event Handler Example 6</h1>

<p>For this example, give the <code><input></code> focus.</p>

<p>
<label>
Count of '+' key presses:
<input value="@count" @onkeydown="KeyHandler"
@onkeydown:preventDefault />
</label>
</p>

@code {
private int count = 0;

private void KeyHandler(KeyboardEventArgs e)


{
if (e.Key == "+")
{
count++;
}
}
}

La spécification de l’attribut @on{DOM EVENT}:preventDefault sans valeur équivaut à


@on{DOM EVENT}:preventDefault="true" .
Une expression est également une valeur autorisée de l’attribut. Dans l’exemple suivant,
shouldPreventDefault est un champ bool défini sur true ou false :

razor

<input @onkeydown:preventDefault="shouldPreventDefault" />

...

@code {
private bool shouldPreventDefault = true;
}

Arrêter la propagation d’événements


Utilisez l’attribut de directive @on{DOM EVENT}:stopPropagation pour arrêter la
propagation d’événements dans l’étendue Blazor. {DOM EVENT} est un espace réservé
pour un événement DOM .

L’effet de l’attribut de directive stopPropagation est limité à l’étendue Blazor et ne


s’étend pas au DOM HTML. Les événements doivent se propager à la racine DOM HTML
avant que Blazor puisse agir dessus. Pour qu’un mécanisme empêche la propagation
d’événements DOM HTML, envisagez l’approche suivante :

Obtenez le chemin d’accès de l’événement en appelant Event.composedPath() .


Filtrez les événements en fonction des cibles d’événement composées
(EventTarget) .

Dans l’exemple suivant, la case à cocher empêche les événements de clic du deuxième
<div> enfant de se propager au <div> parent. Étant donné que les événements de clic

propagés déclenchent normalement la méthode OnSelectParentDiv , la sélection du


deuxième <div> enfant entraîne l’affichage du message parent <div> , sauf si la case est
cochée.

EventHandler7.razor :

razor

@page "/event-handler-7"

<PageTitle>Event Handler 7</PageTitle>

<h1>Event Handler Example 7</h1>

<div>
<b>stopPropagation</b>: @stopPropagation
</div>

<div>
<button @onclick="StopPropagation">
Stop Propagation (stopPropagation = true)
</button>
<button @onclick="EnablePropagation">
Enable Propagation (stopPropagation = false)
</button>
</div>

<div class="m-1 p-1 border border-primary" @onclick="OnSelectParentDiv">


<h3>Parent div</h3>

<div class="m-1 p-1 border" @onclick="OnSelectChildDiv">


Child div that never stops propagation to the parent div when
selected.
</div>

<div class="m-1 p-1 border" @onclick="OnSelectChildDiv"


@onclick:stopPropagation="stopPropagation">
Child div that stops propagation when selected if
<b>stopPropagation</b> is <b>true</b>.
</div>
</div>

<p>
@message
</p>

@code {
private bool stopPropagation = false;
private string? message;

private void StopPropagation() => stopPropagation = true;

private void EnablePropagation() => stopPropagation = false;

private void OnSelectParentDiv() =>


message = $"The parent div was selected. {DateTime.Now}";

private void OnSelectChildDiv() =>


message = $"The child div was selected. {DateTime.Now}";
}

Focus sur un élément


Appelez FocusAsync sur une référence d’élément pour placer le focus sur un élément
dans le code. Dans l’exemple suivant, sélectionnez le bouton pour placer le focus sur
l’élément <input> .
EventHandler8.razor :

razor

@page "/event-handler-8"

<PageTitle>Event Handler 8</PageTitle>

<h1>Event Handler Example 8</h1>

<p>Select the button to give the <code><input></code> focus.</p>

<p>
<label>
Input:
<input @ref="exampleInput" />
</label>

</p>

<button @onclick="ChangeFocus">
Focus the Input Element
</button>

@code {
private ElementReference exampleInput;

private async Task ChangeFocus()


{
await exampleInput.FocusAsync();
}
}

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Liaison de données ASP.NET Core Blazor
Article • 16/01/2024

Cet article décrit les fonctionnalités de liaison de données pour les composants Razor et
les éléments DOM (Document Object Model) dans les applications Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Fonctionnalités de liaison
Les composants Razor fournissent des fonctionnalités de liaison de données via l’attribut
de directive @bindRazor avec un champ, une propriété ou une valeur d’expression
Razor.

L’exemple suivant lie :

Une valeur d’élément <input> au champ C# inputValue .


Une deuxième valeur d’élément <input> à la propriété C# InputValue .

Quand un élément <input> perd le focus, son champ ou sa propriété lié est mis à jour.

Bind.razor :

razor

@page "/bind"

<PageTitle>Bind</PageTitle>

<h1>Bind Example</h1>

<p>
<label>
inputValue:
<input @bind="inputValue" />
</label>
</p>

<p>
<label>
InputValue:
<input @bind="InputValue" />
</label>
</p>

<ul>
<li><code>inputValue</code>: @inputValue</li>
<li><code>InputValue</code>: @InputValue</li>
</ul>

@code {
private string? inputValue;
private string? InputValue { get; set; }
}

La zone de texte est mise à jour dans l’IU uniquement au moment du rendu du
composant, et non en réponse au changement de valeur du champ ou de la propriété.
Dans la mesure où les composants s’affichent après l’exécution du code du gestionnaire
d’événements, les mises à jour de champs et de propriétés sont généralement visibles
dans l’IU immédiatement après le déclenchement d’un gestionnaire d’événements.

Pour illustrer la composition de la liaison de données en HTML, l’exemple suivant lie la


propriété InputValue aux attributs value et onchange (change ) du deuxième élément
<input> . Le deuxième élément <input> dans l’exemple suivant est une démonstration de

concept. Il n’est pas destiné à suggérer la façon dont vous devez lier les données dans les
composants Razor.

BindTheory.razor :

razor

@page "/bind-theory"

<PageTitle>Bind Theory</PageTitle>

<h1>Bind Theory Example</h1>

<p>
<label>
Normal Blazor binding:
<input @bind="InputValue" />
</label>
</p>

<p>
<label>
Demonstration of equivalent HTML binding:
<input value="@InputValue"
@onchange="@((ChangeEventArgs __e) => InputValue =
__e?.Value?.ToString())" />
</label>
</p>

<p>
<code>InputValue</code>: @InputValue
</p>

@code {
private string? InputValue { get; set; }
}
Quand le composant BindTheory est affiché, le value de l’élément de démonstration
<input> HTML provient de la propriété InputValue . Quand l’utilisateur entre une valeur

dans la zone de texte et qu’il change le focus de l’élément, l’événement onchange se


déclenche, et la propriété InputValue est définie en fonction de la valeur changée. En
réalité, l’exécution de code est plus complexe, car @bind gère les cas où des
conversions de types sont effectuées. En règle générale, @bind associe la valeur actuelle
d’une expression à l’attribut value de <input> , et gère les changements à l’aide du
gestionnaire inscrit.

Liez une propriété ou un champ à d’autres événements DOM (Document Object Model)
en incluant un attribut @bind:event="{EVENT}" avec un événement DOM pour l’espace
réservé {EVENT} . L’exemple suivant lie la propriété InputValue à la valeur de l’élément
<input> quand l’événement oninput (input ) de l’élément se déclenche. Contrairement
à l’événement onchange (change ), qui se déclenche quand l’élément perd le focus,
oninput (input ) se déclenche quand la valeur de la zone de texte change.

Page/BindEvent.razor :

razor

@page "/bind-event"

<PageTitle>Bind Event</PageTitle>

<h1>Bind Event Example</h1>

<p>
<label>
InputValue:
<input @bind="InputValue" @bind:event="oninput" />
</label>

</p>

<p>
<code>InputValue</code>: @InputValue
</p>

@code {
private string? InputValue { get; set; }
}

Pour exécuter la logique asynchrone après la liaison, utilisez @bind:after="{EVENT}" avec


un événement DOM pour l’espace réservé {EVENT} . Une méthode C# affectée n’est pas
exécutée tant que la valeur liée n’est pas affectée de manière synchrone.
L’utilisation d’un paramètre de rappel d’événement (EventCallback/EventCallback<T>)
avec @bind:after n’est pas prise en charge. À la place, passez une méthode qui
retourne Action ou Task à @bind:after .

Dans l’exemple suivant :

Le value de l’élément <input> est lié à la valeur de searchText de manière


synchrone.
Après chaque frappe (événement onchange ) dans le champ, la méthode
PerformSearch s’exécute de manière asynchrone.
PerformSearch appelle un service avec une méthode asynchrone ( FetchAsync ) pour

retourner les résultats de la recherche.

razor

@inject ISearchService SearchService

<input @bind="searchText" @bind:after="PerformSearch" />

@code {
private string? searchText;
private string[]? searchResult;

private async Task PerformSearch()


{
searchResult = await SearchService.FetchAsync(searchText);
}
}

Exemples supplémentaires

BindAfter.razor :

razor

@page "/bind-after"
@using Microsoft.AspNetCore.Components.Forms

<h1>Bind After Examples</h1>

<h2>Elements</h2>

<input type="text" @bind="text" @bind:after="() => { }" />

<input type="text" @bind="text" @bind:after="After" />

<input type="text" @bind="text" @bind:after="AfterAsync" />

<h2>Components</h2>
<InputText @bind-Value="text" @bind-Value:after="() => { }" />

<InputText @bind-Value="text" @bind-Value:after="After" />

<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />

@code {
private string text = "";

private void After() {}


private Task AfterAsync() { return Task.CompletedTask; }
}

Pour plus d’informations sur le composant InputText , consultez Composants d’entrée


Blazor ASP.NET Core.

Les composants prennent en charge la liaison de données bidirectionnelle en définissant


une paire de paramètres :

@bind:get : spécifie la valeur à lier.


@bind:set : spécifie un rappel au moment où la valeur change.

Les modificateurs @bind:get et @bind:set sont toujours utilisés ensemble.

L’utilisation d’un paramètre de rappel d’événement avec @bind:set ( [Parameter] public


EventCallback<string> ValueChanged { get; set; } ) n’est pas prise en charge. À la

place, passez une méthode qui retourne Action ou Task à @bind:set .

Exemples

BindGetSet.razor :

razor

@page "/bind-get-set"
@using Microsoft.AspNetCore.Components.Forms

<h1>Bind Get Set Examples</h1>

<h2>Elements</h2>

<input type="text" @bind:get="text" @bind:set="(value) => { text = value; }"


/>
<input type="text" @bind:get="text" @bind:set="Set" />
<input type="text" @bind:get="text" @bind:set="SetAsync" />

<h2>Components</h2>
<InputText @bind-Value:get="text" @bind-Value:set="(value) => { text =
value; }" />
<InputText @bind-Value:get="text" @bind-Value:set="Set" />
<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />

@code {
private string text = "";

private void Set(string value)


{
text = value;
}

private Task SetAsync(string value)


{
text = value;
return Task.CompletedTask;
}
}

Pour plus d’informations sur le composant InputText , consultez Composants d’entrée


Blazor ASP.NET Core.

Pour obtenir un autre exemple d’utilisation de @bind:get et de @bind:set , consultez la


section Liaison sur plus de deux composants plus loin dans cet article.

La liaison d’attribut Razor respecte la casse :

@bind , @bind:event et @bind:after sont valides.

@Bind / @bind:Event / @bind:aftEr (en majuscules) ou

@BIND / @BIND:EVENT / @BIND:AFTER (tout en majuscules) ne sont pas valides.

Utilisez les modificateurs @bind:get / @bind:set ,


et évitez les gestionnaires d’événements pour
la liaison de données bidirectionnelle
La liaison de données bidirectionnelle ne peut pas être implémentée avec un
gestionnaire d’événements. Utilisez les modificateurs @bind:get / @bind:set pour une
liaison de données bidirectionnelle.

❌ Tenez compte de l’approche dysfonctionnelle suivante pour une liaison de données


bidirectionnelle à l’aide d’un gestionnaire d’événements :

razor
<p>
<input value="@inputValue" @oninput="OnInput" />
</p>

<p>
<code>inputValue</code>: @inputValue
</p>

@code {
private string? inputValue;

private void OnInput(ChangeEventArgs args)


{
var newValue = args.Value?.ToString() ?? string.Empty;

inputValue = newValue.Length > 4 ? "Long!" : newValue;


}
}

Le gestionnaire d’événements OnInput met à jour la valeur de inputValue à Long! , une


fois qu’un quatrième caractère a été fourni. Toutefois, l’utilisateur peut continuer à
ajouter des caractères à la valeur d’élément dans l’IU. La valeur de inputValue n’est pas
liée à la valeur de l’élément à chaque frappe. L’exemple précédent est uniquement
capable d’établir une liaison de données unidirectionnelle.

Ce comportement est dû au fait que Blazor ne sait pas que votre code a pour but de
modifier la valeur de inputValue dans le gestionnaire d’événements. Blazor n’essaie pas
de forcer la correspondance des valeurs des éléments DOM et des valeurs des variables
.NET, sauf si elles sont liées par la syntaxe @bind . Dans les versions antérieures de Blazor,
la liaison de données bidirectionnelle est implémentée en liant l’élément à une propriété
et en contrôlant la valeur de la propriété avec sa méthode setter. Dans ASP.NET Core 7.0
ou version ultérieure, la syntaxe du modificateur @bind:get / @bind:set est utilisée pour
implémenter la liaison de données bidirectionnelle, comme le montre l’exemple suivant.

✔️Tenez compte de l’approche appropriée suivante en utilisant @bind:get / @bind:set

pour une liaison de données bidirectionnelle :

razor

<p>
<input @bind:event="oninput" @bind:get="inputValue" @bind:set="OnInput"
/>
</p>

<p>
<code>inputValue</code>: @inputValue
</p>
@code {
private string? inputValue;

private void OnInput(string value)


{
var newValue = value ?? string.Empty;

inputValue = newValue.Length > 4 ? "Long!" : newValue;


}
}

L’utilisation des modificateurs @bind:get / @bind:set contrôle la valeur sous-jacente de


inputValue via @bind:set , et lie la valeur de inputValue à la valeur de l’élément via

@bind:get . L’exemple précédent illustre l’approche appropriée pour implémenter la

liaison de données bidirectionnelle.

Liaison à une propriété avec les accesseurs C#


get et set
Les accesseurs C# get et set peuvent être utilisés pour créer un comportement de
format de liaison personnalisé, comme le montre le composant DecimalBinding suivant.
Le composant lie une décimale positive ou négative ayant jusqu’à trois décimales à un
élément <input> à l’aide d’une propriété string ( DecimalValue ).

DecimalBinding.razor :

razor

@page "/decimal-binding"
@using System.Globalization

<PageTitle>Decimal Binding</PageTitle>

<h1>Decimal Binding Example</h1>

<p>
<label>
Decimal value (±0.000 format):
<input @bind="DecimalValue" />
</label>
</p>

<p>
<code>decimalValue</code>: @decimalValue
</p>
@code {
private decimal decimalValue = 1.1M;
private NumberStyles style =
NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
private CultureInfo culture = CultureInfo.CreateSpecificCulture("en-
US");

private string DecimalValue


{
get => decimalValue.ToString("0.000", culture);
set
{
if (Decimal.TryParse(value, style, culture, out var number))
{
decimalValue = Math.Round(number, 3);
}
}
}
}

7 Notes

La liaison bidirectionnelle à une propriété avec des accesseurs get / set nécessite
l’abandon du Task retourné par EventCallback.InvokeAsync. Pour la liaison de
données bidirectionnelle, nous vous recommandons d’utiliser des modificateurs
@bind:get / @bind:set . Pour plus d’informations, consultez les instructions
@bind:get / @bind:set décrites plus haut dans cet article.

Sélection d’options multiples avec des éléments


<select>
La liaison prend en charge la sélection d’options multiples via l’attribut multiple avec
les éléments <select> . L’événement @onchange fournit un tableau des éléments
sélectionnés via des arguments d’événement (ChangeEventArgs). La valeur doit être liée
à un type tableau.

BindMultipleInput.razor :

razor

@page "/bind-multiple-input"

<h1>Bind Multiple <code>input</code>Example</h1>

<p>
<label>
Select one or more cars:
<select @onchange="SelectedCarsChanged" multiple>
<option value="audi">Audi</option>
<option value="jeep">Jeep</option>
<option value="opel">Opel</option>
<option value="saab">Saab</option>
<option value="volvo">Volvo</option>
</select>
</label>
</p>

<p>
Selected Cars: @string.Join(", ", SelectedCars)
</p>

<p>
<label>
Select one or more cities:
<select @bind="SelectedCities" multiple>
<option value="bal">Baltimore</option>
<option value="la">Los Angeles</option>
<option value="pdx">Portland</option>
<option value="sf">San Francisco</option>
<option value="sea">Seattle</option>
</select>
</label>
</p>

<span>
Selected Cities: @string.Join(", ", SelectedCities)
</span>

@code {
public string[] SelectedCars { get; set; } = new string[] { };
public string[] SelectedCities { get; set; } = new[] { "bal", "sea" };

private void SelectedCarsChanged(ChangeEventArgs e)


{
if (e.Value is not null)
{
SelectedCars = (string[])e.Value;
}
}
}

Pour plus d’informations sur la façon dont les chaînes vides et les valeurs null sont
gérées dans la liaison de données, consultez la section Liaison des options de l’élément
<select> aux valeurs null d’un objet C#.
Liaison des options de l’élément <select> aux
valeurs null d’un objet C#
Il n’existe aucun moyen judicieux de représenter une valeur d’option d’élément
<select> en tant que valeur null d’un objet C#, car :

Les attributs HTML ne peuvent pas avoir de valeurs null . L’équivalent le plus
proche de null en HTML est l’absence de l’attribut HTML value dans l’élément
<option> .

Quand vous sélectionnez un <option> sans attribut value , le navigateur traite la


valeur en tant que contenu textuel de l’élément de <option> .

Le framework Blazor ne tente pas de supprimer le comportement par défaut, car cela
impliquerait les effets suivants :

Création d’une chaîne de solutions de contournement pour les cas particuliers


dans le framework.
Changements cassants apportés au comportement actuel du framework.

L’équivalent le plus plausible de null en HTML est une chaîne vide value . Le framework
Blazor gère les conversions de null en chaînes vides pour une liaison bidirectionnelle
avec une valeur de <select> .

Valeurs non analysables


Quand un utilisateur fournit une valeur non analysable à un élément lié aux données, la
valeur précédente de la valeur non analysable est automatiquement rétablie au moment
où l’événement de liaison se déclenche.

Prenons le cas du composant suivant, où un élément <input> est lié à un type int avec
la valeur initiale 123 .

UnparsableValues.razor :

razor

@page "/unparsable-values"

<PageTitle>Unparsable Values</PageTitle>

<h1>Unparsable Values Example</h1>

<p>
<label>
inputValue:
<input @bind="inputValue" />
</label>

</p>

<p>
<code>inputValue</code>: @inputValue
</p>

@code {
private int inputValue = 123;
}

Par défaut, la liaison s’applique à l’événement onchange de l’élément. Si l’utilisateur met


à jour la valeur de l’entrée de la zone de texte en la remplaçant par 123.45 , puis change
le focus, la valeur de l’élément est rétablie à 123 au moment où onchange se déclenche.
Quand la valeur 123.45 est rejetée au profit de la valeur d’origine de 123 , l’utilisateur
comprend que sa valeur n’a pas été acceptée.

Pour l’événement oninput ( @bind:event="oninput" ), un rétablissement de valeur se


produit après toute séquence de touches qui introduit une valeur non analysable. En cas
de ciblage de l’événement oninput avec un type lié à int , l’utilisateur ne peut pas taper
de point ( . ). Le point ( . ) est instantanément supprimé, et l’utilisateur reçoit
immédiatement un commentaire indiquant que seuls les nombres entiers sont autorisés.
Il existe des scénarios où le rétablissement de la valeur au moment où l’événement
oninput se produit n’est pas idéal, par exemple quand l’utilisateur doit être autorisé à

effacer une valeur <input> non analysable. Il existe des alternatives :

N’utilisez pas l’événement oninput . Utilisez l’événement onchange par défaut, où


une valeur non valide n’est pas rétablie tant que l’élément n’a pas perdu le focus.
Effectuez une liaison à un type Nullable, par exemple int? ou string , et utilisez
les modificateurs @bind:get / @bind:set (décrits plus haut dans cet article), ou
effectuez une liaison à une propriété avec une logique d’accesseurs get et set
personnalisée pour gérer les entrées non valides.
Utilisez un composant de validation de formulaire, par exemple
InputNumber<TValue> ou InputDate<TValue>. Les composants de validation de
formulaire fournissent une prise en charge intégrée de la gestion des entrées non
valides. Composants de validation de formulaire :
Permettez à l’utilisateur de fournir des entrées non valides, et de recevoir des
erreurs de validation sur le EditContext associé.
Affichez les erreurs de validation dans l’IU sans interférer avec l’utilisateur qui
entre des données de formulaire web supplémentaires.
Chaînes de format
La liaison de données fonctionne avec une seule chaîne de format DateTime à l’aide de
@bind:format="{FORMAT STRING}" , où l’espace réservé {FORMAT STRING} correspond à la

chaîne de format. Les autres expressions de format, par exemple pour les formats
monétaires ou numériques, ne sont pas disponibles, mais elles seront peut-être ajoutées
dans une prochaine version.

DateBinding.razor :

razor

@page "/date-binding"

<PageTitle>Date Binding</PageTitle>

<h1>Date Binding Example</h1>

<p>
<label>
<code>yyyy-MM-dd</code> format:
<input @bind="startDate" @bind:format="yyyy-MM-dd" />
</label>
</p>

<p>
<code>startDate</code>: @startDate
</p>

@code {
private DateTime startDate = new(2020, 1, 1);
}

Dans le code précédent, le type de champ de l’élément <input> (attribut type ) a la


valeur par défaut text .

Les types Nullable System.DateTime et System.DateTimeOffset sont pris en charge :

C#

private DateTime? date;


private DateTimeOffset? dateOffset;

La spécification d’un format pour le type de champ date n’est pas recommandée, car
Blazor offre une prise en charge intégrée du format des dates. Malgré la
recommandation, utilisez uniquement le format de date yyyy-MM-dd pour que la liaison
fonctionne correctement si un format est fourni avec le type de champ date :
razor

<input type="date" @bind="startDate" @bind:format="yyyy-MM-dd">

Liaison à des paramètres de composant


La liaison d’une propriété d’un composant enfant à une propriété de son composant
parent est un scénario courant. Ce scénario est appelé liaison chaînée, car plusieurs
niveaux de liaison sont impliqués simultanément.

Les paramètres de composant permettent la liaison des propriétés d’un composant


parent avec la syntaxe @bind-{PROPERTY} , où l’espace réservé {PROPERTY} correspond à
la propriété à lier.

Vous ne pouvez pas implémenter de liaisons chaînées avec la syntaxe @bind dans le
composant enfant. Vous devez spécifier séparément un gestionnaire d’événements et
une valeur pour permettre la prise en charge de la mise à jour de la propriété dans le
parent à partir du composant enfant.

Le composant parent tire tout de même parti de la syntaxe @bind pour configurer la
liaison de données avec le composant enfant.

Le composant ChildBind suivant a un paramètre de composant Year et un


EventCallback<TValue>. Par convention, le EventCallback<TValue> du paramètre doit
avoir un nom qui correspond au nom de paramètre du composant suivi du suffixe
« Changed ». La syntaxe de nommage est {PARAMETER NAME}Changed , où l’espace réservé
{PARAMETER NAME} correspond au nom de paramètre. Dans l’exemple suivant,

EventCallback<TValue> est nommé YearChanged .

EventCallback.InvokeAsync appelle le délégué associé à la liaison avec l’argument fourni,


et distribue une notification d’événement pour la propriété changée.

ChildBind.razor :

razor

<div class="card bg-light mt-3" style="width:18rem ">


<div class="card-body">
<h3 class="card-title">ChildBind Component</h3>
<p class="card-text">
Child <code>Year</code>: @Year
</p>
<button @onclick="UpdateYearFromChild">Update Year from
Child</button>
</div>
</div>

@code {
private Random r = new();

[Parameter]
public int Year { get; set; }

[Parameter]
public EventCallback<int> YearChanged { get; set; }

private async Task UpdateYearFromChild()


{
await YearChanged.InvokeAsync(r.Next(1950, 2021));
}
}

Pour plus d’informations sur les événements et EventCallback<TValue>, consultez la


section EventCallback de l’article Gestion des événements ASP.NET Core Blazor.

Dans le composant Parent1 suivant, le champ year est lié au paramètre Year du
composant enfant. Le paramètre Year peut être lié, car il a un événement compagnon
YearChanged qui correspond au type du paramètre Year .

Parent1.razor :

razor

@page "/parent-1"

<PageTitle>Parent 1</PageTitle>

<h1>Parent Example 1</h1>

<p>Parent <code>year</code>: @year</p>

<button @onclick="UpdateYear">Update Parent <code>year</code></button>

<ChildBind @bind-Year="year" />

@code {
private Random r = new();
private int year = 1979;

private void UpdateYear()


{
year = r.Next(1950, 2021);
}
}
La liaison d’un paramètre de composant peut également déclencher des événements
@bind:after . Dans l’exemple suivant, la méthode YearUpdated s’exécute de manière

asynchrone après la liaison du paramètre de composant Year .

razor

<ChildBind @bind-Year="year" @bind-Year:after="YearUpdated" />

@code {
...

private async Task YearUpdated()


{
... = await ...;
}
}

Par convention, vous pouvez lier une propriété à un gestionnaire d’événements


correspondant en incluant un attribut @bind-{PROPERTY}:event affecté au gestionnaire,
où l’espace réservé {PROPERTY} représente la propriété. <ChildBind @bind-Year="year"
/> équivaut à écrire :

razor

<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" />

Dans un exemple plus sophistiqué et plus réaliste, le composant PasswordEntry suivant :

Affecte un champ password à la valeur d’un élément <input> .


Expose les changements d’une propriété Password à un composant parent avec un
EventCallback qui passe la valeur actuelle du champ password de l’enfant en tant
qu’argument.
Utilise l’événement onclick pour déclencher la méthode ToggleShowPassword . Pour
plus d’informations, consultez Gestion des événements ASP.NET Core Blazor.

PasswordEntry.razor :

razor

<div class="card bg-light mt-3" style="width:22rem ">


<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged"
required
type="@(showPassword ? "text" : "password")"
value="@password" />
</label>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>

@code {
private bool showPassword;
private string? password;

[Parameter]
public string? Password { get; set; }

[Parameter]
public EventCallback<string> PasswordChanged { get; set; }

private async Task OnPasswordChanged(ChangeEventArgs e)


{
password = e?.Value?.ToString();

await PasswordChanged.InvokeAsync(password);
}

private void ToggleShowPassword()


{
showPassword = !showPassword;
}
}

Le composant PasswordEntry est utilisé dans un autre composant, à l’image de


l’exemple de composant PasswordBinding suivant.

PasswordBinding.razor :

razor

@page "/password-binding"

<PageTitle>Password Binding</PageTitle>

<h1>Password Binding Example</h1>

<PasswordEntry @bind-Password="password" />

<p>
<code>password</code>: @password
</p>
@code {
private string password = "Not set";
}

Quand le composant PasswordBinding est affiché initialement, la valeur password de Not


set s’affiche dans l’IU. Après le rendu initial, la valeur de password reflète les

changements apportés à la valeur du paramètre de composant Password dans le


composant PasswordEntry .

7 Notes

L’exemple précédent lie le mot de passe unidirectionnel du composant enfant


PasswordEntry au composant parent PasswordBinding . La liaison bidirectionnelle

n’est pas obligatoire dans ce scénario si l’objectif est que l’application dispose d’un
composant d’entrée de mot de passe partagé réutilisable dans l’application, qui
transmet simplement le mot de passe au parent. Pour une approche qui permet
d’effectuer une liaison bidirectionnelle sans écrire directement dans le paramètre
du composant enfant, consultez l’exemple de composant NestedChild dans la
section Lier plus de deux composants de cet article.

Effectuez des vérifications ou interceptez les erreurs dans le gestionnaire. Le composant


PasswordEntry révisé suivant fournit une indication immédiate à l’utilisateur si un espace

est utilisé dans la valeur du mot de passe.

PasswordEntry.razor :

razor

<div class="card bg-light mt-3" style="width:22rem ">


<div class="card-body">
<h3 class="card-title">Password Component</h3>
<p class="card-text">
<label>
Password:
<input @oninput="OnPasswordChanged"
required
type="@(showPassword ? "text" : "password")"
value="@password" />
</label>
<span class="text-danger">@validationMessage</span>
</p>
<button class="btn btn-primary" @onclick="ToggleShowPassword">
Show password
</button>
</div>
</div>

@code {
private bool showPassword;
private string? password;
private string? validationMessage;

[Parameter]
public string? Password { get; set; }

[Parameter]
public EventCallback<string> PasswordChanged { get; set; }

private Task OnPasswordChanged(ChangeEventArgs e)


{
password = e?.Value?.ToString();

if (password != null && password.Contains(' '))


{
validationMessage = "Spaces not allowed!";

return Task.CompletedTask;
}
else
{
validationMessage = string.Empty;

return PasswordChanged.InvokeAsync(password);
}
}

private void ToggleShowPassword()


{
showPassword = !showPassword;
}
}

Lier plus de deux composants


Vous pouvez lier des paramètres via un nombre illimité de composants imbriqués, mais
vous devez respecter le flux de données unidirectionnel :

Le flux des notifications de changement va vers le haut de la hiérarchie.


Le flux des nouvelles valeurs de paramètre va vers le bas de la hiérarchie.

Une approche courante et recommandée consiste à stocker uniquement les données


sous-jacentes dans le composant parent pour éviter toute confusion sur l’état à mettre à
jour, comme le montre l’exemple suivant.

Parent2.razor :
razor

@page "/parent-2"

<PageTitle>Parent 2</PageTitle>

<h1>Parent Example 2</h1>

<p>Parent Message: <b>@parentMessage</b></p>

<p>
<button @onclick="ChangeValue">Change from Parent</button>
</p>

<NestedChild @bind-ChildMessage="parentMessage" />

@code {
private string parentMessage = "Initial value set in Parent";

private void ChangeValue()


{
parentMessage = $"Set in Parent {DateTime.Now}";
}
}

Dans le composant NestedChild suivant, le composant NestedGrandchild :

Affecte la valeur de ChildMessage à GrandchildMessage avec la syntaxe @bind:get .


Met à jour GrandchildMessage quand ChildMessageChanged s’exécute avec la
syntaxe @bind:set .

NestedChild.razor :

razor

<div class="border rounded m-1 p-1">


<h2>Child Component</h2>

<p>Child Message: <b>@ChildMessage</b></p>

<p>
<button @onclick="ChangeValue">Change from Child</button>
</p>

<NestedGrandchild @bind-GrandchildMessage:get="ChildMessage"
@bind-GrandchildMessage:set="ChildMessageChanged" />
</div>

@code {
[Parameter]
public string? ChildMessage { get; set; }
[Parameter]
public EventCallback<string?> ChildMessageChanged { get; set; }

private async Task ChangeValue()


{
await ChildMessageChanged.InvokeAsync(
$"Set in Child {DateTime.Now}");
}
}

NestedGrandchild.razor :

razor

<div class="border rounded m-1 p-1">


<h3>Grandchild Component</h3>

<p>Grandchild Message: <b>@GrandchildMessage</b></p>

<p>
<button @onclick="ChangeValue">Change from Grandchild</button>
</p>
</div>

@code {
[Parameter]
public string? GrandchildMessage { get; set; }

[Parameter]
public EventCallback<string> GrandchildMessageChanged { get; set; }

private async Task ChangeValue()


{
await GrandchildMessageChanged.InvokeAsync(
$"Set in Grandchild {DateTime.Now}");
}
}

Pour une autre approche adaptée au partage des données en mémoire et entre des
composants qui ne sont pas nécessairement imbriqués, consultez Gestion d’état
ASP.NET Core Blazor.

Ressources supplémentaires
Détection des changements de paramètres et conseils d’aide supplémentaires sur
le rendu des composants Razor
Vue d’ensemble des formulaires Blazor ASP.NET Core
Liaison à des cases d’option dans un formulaire
Liaison des options InputSelect aux valeurs null d’un objet C#
Gestion des événements ASP.NET Core Blazor : section EventCallback
Blazor exemples de dépôt GitHub (dotnet/blazor-samples)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Cycle de vie des composants Razor
ASP.NET Core
Article • 09/02/2024

Cet article explique le cycle de vie des composants Razor ASP.NET Core et comment
utiliser des événements de cycle de vie.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Événements de cycle de vie


Le composant Razor traite les événements de cycle de vie des composants Razor dans
un ensemble de méthodes de cycle de vie synchrones et asynchrones. Les méthodes de
cycle de vie peuvent être remplacées pour effectuer des opérations supplémentaires
dans les composants lors de l’initialisation et du rendu des composants.

Cet article simplifie le traitement des événements de cycle de vie des composants afin
de clarifier la logique d’infrastructure complexe. Vous devrez peut-être accéder à la
source de référence ComponentBase pour intégrer le traitement des événements
personnalisés avec le traitement des événements de cycle de vie Blazor. Les
commentaires de code dans la source de référence incluent des remarques
supplémentaires sur le traitement des événements de cycle de vie qui n’apparaissent
pas dans cet article ou dans la documentation de l’API. Le traitement des événements de
cycle de vie Blazor a changé au fil du temps et est susceptible d’être modifié sans
préavis à chaque version.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Les diagrammes simplifiés suivants illustrent le traitement des événements de cycle de


vie des composants Razor. Les méthodes C# associées aux événements de cycle de vie
sont définies avec des exemples dans les sections suivantes de cet article.

Événements de cycle de vie des composants :

1. Si le composant effectue un rendu pour la première fois sur une requête :


Créez l’instance du composant.
Effectuez l’injection de propriétés. Exécutez SetParametersAsync.
Appelez OnInitialized{Async}. Si une Task incomplète est retournée, la Task est
attendue, puis le composant est renvoyé. La méthode synchrone est appelée
avant la méthode asynchrone.
2. Appelez OnParametersSet{Async}. Si une Task incomplète est retournée, la Task est
attendue, puis le composant est renvoyé. La méthode synchrone est appelée avant
la méthode asynchrone.
3. Effectuez le rendu de tous les travaux synchrones et terminez les Tasks.

7 Notes

Les actions asynchrones effectuées dans les événements de cycle de vie ne se sont
peut-être pas terminées avant qu’un composant ne soit rendu. Pour plus
d’informations, consultez la section Gérer les actions asynchrones incomplètes au
rendu plus loin dans cet article.

Un composant parent effectue le rendu avant ses composants enfants, car le rendu est
ce qui détermine les enfants présents. Si l’initialisation du composant parent synchrone
est utilisée, l’initialisation parente est garantie pour se terminer en premier. Si
l’initialisation asynchrone du composant parent est utilisée, l’ordre d’achèvement de
l’initialisation des composants parent et enfant ne peut pas être déterminé, car il
dépend du code d’initialisation en cours d’exécution.
Traitement d’un événement DOM :

1. Le gestionnaire d’événements est exécuté.


2. Si une Task incomplète est retournée, la Task est attendue, puis le composant est
renvoyé.
3. Effectuez le rendu de tous les travaux synchrones et terminez les Tasks.
Cycle de vie du Render :

1. Évitez d’autres opérations de rendu sur le composant :

Après le premier rendu.


Quand ShouldRender est false .

2. Générez l’arborescence de rendu diff (différence) et affichez le composant.


3. Attendez la mise à jour du DOM.
4. Appelez OnAfterRender{Async}. La méthode synchrone est appelée avant la
méthode asynchrone.
Les appels de développeur à StateHasChanged entraînent un rendu. Pour plus
d’informations, consultez le rendu de composants Razor ASP.NET Core.

Quand les paramètres sont définis


( SetParametersAsync )
SetParametersAsync définit les paramètres fournis par le parent du composant dans
l’arborescence de rendu ou à partir des paramètres de routage.

Le paramètre ParameterView de la méthode contient l’ensemble de valeurs de


paramètre de composant pour le composant chaque fois que SetParametersAsync est
appelée. En substituant la méthode SetParametersAsync, le code du développeur peut
interagir directement avec les paramètres de ParameterView.
L’implémentation par défaut de SetParametersAsync définit la valeur de chaque
propriété avec l’attribut [Parameter] ou [CascadingParameter] qui a une valeur
correspondante dans le ParameterView. Les paramètres qui n’ont pas de valeur
correspondante dans ParameterView restent inchangés.

Si base.SetParametersAsync n’est pas appelée, le code du développeur peut interpréter


les valeurs des paramètres entrants de quelque manière que ce soit nécessaire. Par
exemple, il n’est pas nécessaire d’affecter les paramètres entrants aux propriétés de la
classe.

Si des gestionnaires d’événements sont fournis dans le code du développeur, décochez-


les lors de la suppression. Pour plus d’informations, consultez la section Suppression des
composants avec IDisposableIAsyncDisposable.

Dans l’exemple suivant, ParameterView.TryGetValue affecte la valeur du paramètre Param


à value si l’analyse d’un paramètre de route pour Param est réussie. Lorsque value n’est
pas null , la valeur est affichée par le composant.

Bien que la correspondance des paramètres de route ne respecte pas la casse,


TryGetValue ne correspond qu’aux noms de paramètres respectant la casse dans le
modèle d’itinéraire. L’exemple suivant nécessite l’utilisation de dans le modèle
d’itinéraire /{Param?} pour obtenir la valeur avec TryGetValue, et non /{param?} . Si
/{param?} est utilisé dans ce scénario, TryGetValue retourne false et message n’est

défini sur aucune des message chaînes.

SetParamsAsync.razor :

razor

@page "/set-params-async/{Param?}"

<PageTitle>Set Parameters Async</PageTitle>

<h1>Set Parameters Async Example</h1>

<p>@message</p>

@code {
private string message = "Not set";

[Parameter]
public string? Param { get; set; }

public override async Task SetParametersAsync(ParameterView parameters)


{
if (parameters.TryGetValue<string>(nameof(Param), out var value))
{
if (value is null)
{
message = "The value of 'Param' is null.";
}
else
{
message = $"The value of 'Param' is {value}.";
}
}

await base.SetParametersAsync(parameters);
}
}

Initialisation des composants


( OnInitialized{Async} )
OnInitialized et OnInitializedAsync sont appelés lorsque le composant est initialisé après
avoir reçu ses paramètres initiaux dans SetParametersAsync. La méthode synchrone est
appelée avant la méthode asynchrone.

Si l’initialisation de composant parent synchrone est utilisée, l’initialisation parente est


garantie avant l’initialisation du composant enfant. Si l’initialisation asynchrone du
composant parent est utilisée, l’ordre d’achèvement de l’initialisation des composants
parent et enfant ne peut pas être déterminé, car il dépend du code d’initialisation en
cours d’exécution.

Pour une opération synchrone, remplacez OnInitialized :

OnInit.razor :

razor

@page "/on-init"

<PageTitle>On Initialized</PageTitle>

<h1>On Initialized Example</h1>

<p>@message</p>

@code {
private string? message;

protected override void OnInitialized()


{
message = $"Initialized at {DateTime.Now}";
}
}

Pour effectuer une opération asynchrone, remplacez OnInitializedAsync et utilisez


l’opérateur await :

C#

protected override async Task OnInitializedAsync()


{
await ...
}

Si une classe de base personnalisée est utilisée avec une logique d’initialisation
personnalisée, appelez OnInitializedAsync sur la classe de base :

C#

protected override async Task OnInitializedAsync()


{
await ...

await base.OnInitializedAsync();
}

Il n’est pas nécessaire d’appeler ComponentBase.OnInitializedAsync, sauf si une classe


de base personnalisée est utilisée avec une logique personnalisée. Pour plus
d’informations, consultez la section Méthodes de cycle de vie de classe de base.

Les applications Blazor qui prérendent leur contenu sur le serveur appellent
OnInitializedAsyncdeux fois :

Une fois lorsque le composant est initialement rendu statiquement dans le cadre
de la page.
Une deuxième fois lorsque le navigateur affiche le composant.

Pour empêcher le code du développeur dans OnInitializedAsync de s’exécuter deux fois


lors du prerendering, consultez la section Reconnexion avec état après le prerendering.
Le contenu de la section se concentre sur web Apps Blazor et la SignalRreconnexion
avec état. Pour conserver l’état pendant l’exécution du code d’initialisation lors du
prérendu, consultez Prérendu des composants ASP.NET Core Razor.

Bien qu’une application Blazor soit en prerendering, certaines actions, telles que l’appel
à JavaScript (JS interop), ne sont pas possibles. Les composants peuvent avoir besoin de
s’afficher différemment lorsqu’ils sont prérendus. Pour plus d’informations, consultez la
section Prerendering avec interopérabilité JavaScript.

Si des gestionnaires d’événements sont fournis dans le code du développeur, décochez-


les lors de la suppression. Pour plus d’informations, consultez la section Suppression des
composants avec IDisposableIAsyncDisposable.

Utilisez le rendu de streaming avec les composants de serveur interactif pour améliorer
l’expérience de l’utilisateur afin que les composants qui exécutent des tâches
asynchrones de longue durée dans OnInitializedAsync soient rendus entièrement. Pour
plus d’informations, consultez le rendu de composants Razor ASP.NET Core.

Une fois les paramètres définis


( OnParametersSet{Async} )
OnParametersSet ou OnParametersSetAsync sont appelés :

Une fois le composant initialisé dans OnInitialized ou OnInitializedAsync.

Lorsque le composant parent est remis à l'état initial et fourni :


Des types immuables connus ou primitifs quand au moins un paramètre a
changé.
Paramètres de type complexe. L’infrastructure ne peut pas savoir si les valeurs
d’un paramètre de type complexe ont muté en interne, de sorte que
l’infrastructure traite toujours le jeu de paramètres comme modifié lorsqu’un ou
plusieurs paramètres de type complexe sont présents.

Pour plus d’informations sur les conventions de rendu, consultez le rendu de


composants Razor ASP.NET Core.

La méthode synchrone est appelée avant la méthode asynchrone.

Pour l’exemple de composant suivant, accédez à la page du composant à une URL :

Avec une date de début reçue par StartDate : /on-parameters-set/2021-03-19


Sans date de début, où StartDate est attribuée une valeur de l’heure locale
actuelle : /on-parameters-set

7 Notes

Dans un itinéraire de composant, il n’est pas possible de limiter à la fois un


paramètre DateTime avec la contrainte de routage datetime et rendre le
paramètre facultatif. Par conséquent, le composant OnParamsSet suivant utilise
deux directives @page pour gérer le routage avec et sans segment de date fourni
dans l’URL.

OnParamsSet.razor :

razor

@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<PageTitle>On Parameters Set</PageTitle>

<h1>On Parameters Set Example</h1>

<p>
Pass a datetime in the URI of the browser's address bar.
For example, add <code>/1-1-2024</code> to the address.
</p>

<p>@message</p>

@code {
private string? message;

[Parameter]
public DateTime StartDate { get; set; }

protected override void OnParametersSet()


{
if (StartDate == default)
{
StartDate = DateTime.Now;

message = $"No start date in URL. Default value applied " +


$"(StartDate: {StartDate}).";
}
else
{
message = $"The start date in the URL was used " +
$"(StartDate: {StartDate}).";
}
}
}

Le travail asynchrone lors de l’application de paramètres et de valeurs de propriété doit


se produire pendant l’événement de cycle de vie OnParametersSetAsync :

C#
protected override async Task OnParametersSetAsync()
{
await ...
}

Si une classe de base personnalisée est utilisée avec une logique d’initialisation
personnalisée, appelez OnParametersSetAsync sur la classe de base :

C#

protected override async Task OnParametersSetAsync()


{
await ...

await base.OnParametersSetAsync();
}

Il n’est pas nécessaire d’appeler ComponentBase.OnParametersSetAsync, sauf si une


classe de base personnalisée est utilisée avec une logique personnalisée. Pour plus
d’informations, consultez la section Méthodes de cycle de vie de classe de base.

Si des gestionnaires d’événements sont fournis dans le code du développeur, décochez-


les lors de la suppression. Pour plus d’informations, consultez la section Suppression des
composants avec IDisposableIAsyncDisposable.

Pour plus d’informations sur les contraintes et paramètres de routage, consultez


ASP.NET Core Blazor routage et navigation.

Pour obtenir un exemple d’implémentation manuelle de SetParametersAsync pour


améliorer les performances dans certains scénarios, consultez ASP.NET Core Blazor
meilleures pratiques en matière de performances.

Après le rendu du composant


( OnAfterRender{Async} )
OnAfterRender et OnAfterRenderAsync sont appelés une fois qu’un composant a
produit un rendu de manière interactive et que l’interface utilisateur a terminé la mise à
jour (par exemple, une fois les éléments ajoutés au DOM du navigateur). Les références
d’élément et de composant sont renseignées à ce stade. Utilisez cette étape pour
effectuer des étapes d’initialisation supplémentaires avec le contenu rendu, comme les
appels d’interopérabilité JS qui interagissent avec les éléments DOM rendus. La
méthode synchrone est appelée avant la méthode asynchrone.
Ces méthodes ne sont pas invoquées pendant le pré-rendu ou le rendu sur le serveur,
car ces processus ne sont pas liés au DOM du navigateur et sont déjà terminés avant
que le DOM ne soit mis à jour.

Pour OnAfterRenderAsync, le composant ne produit pas automatiquement un rendu


après l’achèvement de tout Task retourné pour éviter une boucle de rendu infinie.

Paramètre firstRender pour OnAfterRender et OnAfterRenderAsync :

Est défini sur true la première fois que l’instance du composant est rendue.
Peut être utilisé pour garantir que le travail d’initialisation n’est effectué qu’une
seule fois.

AfterRender.razor :

razor

@page "/after-render"
@inject ILogger<AfterRender> Logger

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>
<button @onclick="LogInformation">Log information (and trigger a render)
</button>
</p>

<p>Study logged messages in the console.</p>

@code {
private string message = "Initial assigned message.";

protected override void OnAfterRender(bool firstRender)


{
Logger.LogInformation("OnAfterRender(1): firstRender: " +
"{FirstRender}, message: {Message}", firstRender, message);

if (firstRender)
{
message = "Executed for the first render.";
}
else
{
message = "Executed after the first render.";
}

Logger.LogInformation("OnAfterRender(2): firstRender: " +


"{FirstRender}, message: {Message}", firstRender, message);
}
private void LogInformation()
{
Logger.LogInformation("LogInformation called");
}
}

Le travail asynchrone immédiatement après le rendu doit se produire pendant


l’événement de cycle de vie OnAfterRenderAsync :

C#

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
await ...
}
}

Si une classe de base personnalisée est utilisée avec une logique d’initialisation
personnalisée, appelez OnAfterRenderAsync sur la classe de base :

C#

protected override async Task OnAfterRenderAsync(bool firstRender)


{
...

await base.OnAfterRenderAsync(firstRender);
}

Il n’est pas nécessaire d’appeler ComponentBase.OnAfterRenderAsync, sauf si une classe


de base personnalisée est utilisée avec une logique personnalisée. Pour plus
d’informations, consultez la section Méthodes de cycle de vie de classe de base.

Même si vous retournez un Task à partir de OnAfterRenderAsync, l’infrastructure ne


planifie pas de cycle de rendu supplémentaire pour votre composant une fois cette
tâche terminée. Cela permet d’éviter une boucle de rendu infinie. Cela diffère des autres
méthodes de cycle de vie, qui planifient un autre cycle de rendu une fois qu’une Task
retournée est terminée.

OnAfterRender et OnAfterRenderAsyncne sont pas appelés pendant le processus de


prerendering sur le serveur. Les méthodes sont appelées lorsque le composant est rendu
de manière interactive après le prerendering. Lorsque l'application se prépare :
1. Le composant s’exécute sur le serveur pour produire un balisage HTML statique
dans la réponse HTTP. Pendant cette phase, OnAfterRender et
OnAfterRenderAsync ne sont pas appelées.
2. Lorsque le Blazor script ( blazor.{server|webassembly|web}.js ) démarre dans le
navigateur, le composant est redémarré en mode de rendu interactif. Une fois
qu’un composant est redémarré, OnAfterRender et OnAfterRenderAsyncsont
appelées, car l’application n’est plus dans la phase de prerendering.

Si des gestionnaires d’événements sont fournis dans le code du développeur, décochez-


les lors de la suppression. Pour plus d’informations, consultez la section Suppression des
composants avec IDisposableIAsyncDisposable.

Méthodes de cycle de vie de classe de base


En cas de substitution des méthodes de cycle de vie de Blazor, il n’est pas nécessaire
d’appeler des méthodes de cycle de vie de classe de base pour ComponentBase.
Toutefois, un composant doit appeler une méthode de cycle de vie de classe de base
substituée si la méthode de classe de base contient une logique qui doit être exécutée.
Les consommateurs de bibliothèque appellent généralement des méthodes de cycle de
vie de classe de base lors de l’héritage d’une classe de base, car les classes de base de
bibliothèque ont souvent une logique de cycle de vie personnalisée à exécuter. Si
l’application utilise une classe de base à partir d’une bibliothèque, consultez la
documentation de la bibliothèque pour obtenir des conseils.

Dans l’exemple suivant, base.OnInitialized(); est appelé pour s’assurer que la


méthode OnInitialized de la classe de base est exécutée. Sans l’appel,
BlazorRocksBase2.OnInitialized ne s’exécute pas.

BlazorRocks2.razor :

razor

@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
@BlazorRocksText
</p>

@code {
protected override void OnInitialized()
{
Logger.LogInformation("Initialization code of BlazorRocks2
executed!");

base.OnInitialized();
}
}

BlazorRocksBase2.cs :

C#

using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase


{
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

public string BlazorRocksText { get; set; } =


"Blazor rocks the browser!";

protected override void OnInitialized()


{
Logger.LogInformation("Initialization code of BlazorRocksBase2
executed!");
}
}

Changements d’état ( StateHasChanged )


StateHasChanged avertit le composant que son état a changé. Le cas échéant, l’appel de
StateHasChanged entraîne la remise à l’état du composant.

StateHasChanged est appelée automatiquement pour les méthodes EventCallback. Pour


plus d’informations sur les rappels d’événements, consultez ASP.NET Core Blazor
gestion des événements.

Pour plus d’informations sur le rendu des composants et le moment d’appeler


StateHasChanged, notamment quand l’appeler avec ComponentBase.InvokeAsync,
consultez ASP.NET Core Razor rendu de composant.
Gérer les actions asynchrones incomplètes au
rendu
Les actions asynchrones effectuées dans les événements de cycle de vie ne se sont peut-
être pas terminées avant que le composant ne soit rendu. Les objets peuvent être null
ou être renseignés de manière incomplète avec des données pendant l’exécution de la
méthode de cycle de vie. Fournissez une logique de rendu pour confirmer que les objets
sont initialisés. Affiche les éléments d’interface utilisateur d’espace réservé (par exemple,
un message de chargement) alors que les objets sont null .

Dans le composant suivant, OnInitializedAsync est remplacé pour fournir de manière


asynchrone les données d’évaluation des films ( movies ). Lorsque movies est null , un
message de chargement s’affiche à l’utilisateur. Une fois le Task retourné par
OnInitializedAsync terminé, le composant est remangé avec l’état mis à jour.

razor

<h1>Sci-Fi Movie Ratings</h1>

@if (movies == null)


{
<p><em>Loading...</em></p>
}
else
{
<ul>
@foreach (var movie in movies)
{
<li>@movie.Title &mdash; @movie.Rating</li>
}
</ul>
}

@code {
private Movies[]? movies;

protected override async Task OnInitializedAsync()


{
movies = await GetMovieRatings(DateTime.Now);
}
}

Gérer les erreurs


Pour plus d’informations sur la gestion des erreurs pendant l’exécution de la méthode
de cycle de vie, consultez Gérer les erreurs dans les Blazorapplications ASP.NET Core.
Reconnexion avec état après le prerendering
Lors du prérendu sur le serveur, un composant est initialement rendu statiquement dans
le cadre de la page. Une fois que le navigateur établit une connexion SignalR au serveur,
le composant est rendu à nouveau et interactif. Si la méthode de cycle de vie
OnInitialized{Async} pour initialiser le composant est présente, la méthode est exécutée
deux fois :

Lorsque le composant est prérendu statiquement.


Une fois la connexion au serveur établie.

Cela peut entraîner une modification notable des données affichées dans l’interface
utilisateur lorsque le composant est finalement rendu. Pour éviter ce comportement,
transmettez un identificateur pour mettre en cache l’état lors du prérendu et pour
récupérer l’état après le prérendu.

Le code suivant illustre un élément WeatherForecastService qui évite la modification de


l’affichage des données en raison d’un prérendu. L’élément Delay attendu ( await
Task.Delay(...) ) simule un court délai avant de retourner des données à partir de la

méthode GetForecastAsync .

Ajoutez des services IMemoryCache avec AddMemoryCache sur la collection de services


dans le fichier Program de l’application :

C#

builder.Services.AddMemoryCache();

WeatherForecastService.cs :

C#

using Microsoft.Extensions.Caching.Memory;

namespace BlazorSample;

public class WeatherForecastService(IMemoryCache memoryCache)


{
private static readonly string[] summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];

public IMemoryCache MemoryCache { get; } = memoryCache;


public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
{
return MemoryCache.GetOrCreateAsync(startDate, async e =>
{
e.SetOptions(new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
});

var rng = new Random();

await Task.Delay(TimeSpan.FromSeconds(10));

return Enumerable.Range(1, 5).Select(index => new


WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
});
}
}

Pour plus d’informations sur le RenderMode, consultez conseils BlazorSignalR ASP.NET


Core.

Le contenu de cette section se concentre sur web Apps Blazor et la


SignalRreconnexionavec état. Pour conserver l’état pendant l’exécution du code
d’initialisation lors du prérendu, consultez Prérendu des composants ASP.NET Core
Razor.

Prerendering avec l’interopérabilité JavaScript


Cette section s’applique aux applications côté serveur et hébergées qui effectuent un pré-
rendu des composants Razor. Le prérendu est couvert dans Prérendu des composants
ASP.NET Core Razor.

7 Notes

La navigation interne pour le routage interactif dans les applications web Blazor
n’implique pas de demander le nouveau contenu de la page auprès du serveur. Par
conséquent, un prérendu n’est pas effectué pour les demandes de pages internes.
Si l’application adopte le routage interactif, effectuez un rechargement de la page
complète pour les exemples de composants qui illustrent le comportement de
prérendu. Pour plus d’informations, consultez Prévisualiser les composants
ASP.NET Core Razor.

Lorsqu’une application est en cours de prérendu, certaines actions, comme l’appel à


JavaScript (JS), ne sont pas possibles.

Dans l’exemple suivant, la fonction setElementText1 est appelée avec


JSRuntimeExtensions.InvokeVoidAsync et ne retourne pas de valeur.

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

HTML

<script>
window.setElementText1 = (element, text) => element.innerText = text;
</script>

2 Avertissement

L’exemple précédent modifie le modèle DOM directement à des fins de


démonstration uniquement. La modification directe du DOM avec JS n’est pas
recommandée dans la plupart des scénarios, car JS peut interférer avec le suivi des
modifications de Blazor. Pour plus d’informations, consultez Interopérabilité
JavaScript et ASP.NET Core Blazor (interopérabilité JS).

L’événement de cycle de vie OnAfterRender{Async} n’est pas appelé pendant le


processus de prérendu sur le serveur. Remplacez la méthode OnAfterRender{Async}
pour retarder les appels d’interopérabilité JS jusqu’à ce que le composant soit rendu et
interactif sur le client après le prérendu.

PrerenderedInterop1.razor :

razor

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS
<PageTitle>Prerendered Interop 1</PageTitle>

<h1>Prerendered Interop Example 1</h1>

<div @ref="divElement">Text during render</div>

@code {
private ElementReference divElement;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
await JS.InvokeVoidAsync(
"setElementText1", divElement, "Text after render");
}
}
}

7 Notes

L’exemple précédent pollue le client avec des fonctions globales. Pour une
meilleure approche dans les applications de production, consultez Isolation
JavaScript dans les modules JavaScript.

Exemple :

JavaScript

export setElementText1 = (element, text) => element.innerText = text;

Le composant suivant montre comment utiliser l’interopérabilité JS dans le cadre de la


logique d’initialisation d’un composant d’une manière compatible avec le prérendu. Le
composant indique qu’il est possible de déclencher une mise à jour de rendu à partir de
OnAfterRenderAsync. Le développeur doit veiller à éviter de créer une boucle infinie
dans ce scénario.

Dans l’exemple suivant, la fonction setElementText2 est appelée avec


IJSRuntime.InvokeAsync et retourne une valeur.

7 Notes
Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations
pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

HTML

<script>
window.setElementText2 = (element, text) => {
element.innerText = text;
return text;
};
</script>

2 Avertissement

L’exemple précédent modifie le modèle DOM directement à des fins de


démonstration uniquement. La modification directe du DOM avec JS n’est pas
recommandée dans la plupart des scénarios, car JS peut interférer avec le suivi des
modifications de Blazor. Pour plus d’informations, consultez Interopérabilité
JavaScript et ASP.NET Core Blazor (interopérabilité JS).

Là où JSRuntime.InvokeAsync est appelé, le ElementReference est utilisé uniquement


dans OnAfterRenderAsync et non dans une méthode de cycle de vie antérieure, car il n’y
a aucun élément JS avant le rendu du composant.

StateHasChanged est appelé pour renvoyer le composant avec le nouvel état obtenu à
partir de l’appel d’interopérabilité JS (pour plus d’informations, consultez Rendu de
composants ASP.NET Core Razor). Le code ne crée pas de boucle infinie, car
StateHasChanged est appelé uniquement lorsque data est null .

PrerenderedInterop2.razor :

razor

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<PageTitle>Prerendered Interop 2</PageTitle>

<h1>Prerendered Interop Example 2</h1>

<p>
Get value via JS interop call:
<strong id="val-get-by-interop">@(infoFromJs ?? "No value yet")</strong>
</p>

<p>
Set value via JS interop call:
<strong id="val-set-by-interop" @ref="divElement"></strong>
</p>

@code {
private string? infoFromJs;
private ElementReference divElement;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender && infoFromJs == null)
{
infoFromJs = await JS.InvokeAsync<string>(
"setElementText2", divElement, "Hello from interop call!");

StateHasChanged();
}
}
}

7 Notes

L’exemple précédent pollue le client avec des fonctions globales. Pour une
meilleure approche dans les applications de production, consultez Isolation
JavaScript dans les modules JavaScript.

Exemple :

JavaScript

export setElementText2 = (element, text) => {


element.innerText = text;
return text;
};

Suppression des composants avec IDisposable


et IAsyncDisposable
Si un composant implémente IDisposable, IAsyncDisposable, ou les deux, l’infrastructure
appelle à la suppression des ressources non managées lorsque le composant est
supprimé de l’interface utilisateur. La suppression peut se produire à tout moment, y
compris lors de l’initialisation des composants.

Les composants ne doivent pas avoir besoin d’implémenter IDisposable et


IAsyncDisposable simultanément. Si les deux sont implémentés, l’infrastructure exécute
uniquement la surcharge asynchrone.

Le code du développeur doit s’assurer que les implémentations IAsyncDisposable ne


prennent pas beaucoup de temps.

Suppression des références d’objets d’interopérabilité


JavaScript
Des exemples dans les articles d’interopérabilité JavaScript (JS) illustrent les modèles de
suppression d’objets classiques :

Lors de l’appel JS à partir de .NET, comme décrit dans Appeler des fonctions
JavaScript à partir de méthodes .NET dans ASP.NET Core Blazor, éliminez les
fonctions créées
IJSObjectReference/IJSInProcessObjectReference/JSObjectReferenceà partir de
.NET ou de JS pour éviter toute fuite de JS mémoire.

Lors de l’appel de .NET à partir de JS, comme décrit dans Appeler des méthodes
.NET à partir de fonctions JavaScript dans ASP.NET Core Blazor, supprimez un
DotNetObjectReference créé à partir de .NET ou de JS pour éviter la fuite de
mémoire .NET.

Les références d’objet d’interopérabilité JS sont implémentées en tant que carte avec
pour clé un identificateur sur le côté de l’appel d’interopérabilité JS qui crée la référence.
Lorsque l’élimination de l’objet est lancée du côté .NET ou JS, Blazor supprime l’entrée
de la carte, et l’objet peut être récupéré en mémoire tant qu’aucune autre référence
forte à l’objet n’est présente.

Au minimum, éliminez toujours les objets créés côté .NET pour éviter les fuites de
mémoire managée .NET.

Tâches de nettoyage de modèle DOM lors de la


suppression des composants
Pour plus d’informations, consultez Interopérabilité JavaScript et ASP.NET Core Blazor
(interopérabilité JS).
Pour obtenir de l’aide sur JSDisconnectedException quand un circuit est déconnecté,
consultez l’interopérabilité ASP.NET Blazor JavaScript (interopérabilité JS). Pour obtenir
des conseils généraux sur la gestion des erreurs d’interopérabilité JavaScript, consultez
la section JavaScript Interop dans Gérer les erreurs dans les applications Blazor ASP.NET
Core.

IDisposable synchrone

Pour les tâches de suppression synchrone, utilisez IDisposable.Dispose.

Le composant suivant :

Implémente IDisposable avec la directive @implementsRazor.


Supprime obj , qui est un type non managé qui implémente IDisposable.
Une vérification null est effectuée, car obj est créée dans une méthode de cycle de
vie (non affichée).

razor

@implements IDisposable

...

@code {
...

public void Dispose()


{
obj?.Dispose();
}
}

Si un objet unique nécessite une suppression, une lambda peut être utilisée pour
supprimer l’objet lorsque Dispose est appelé. L’exemple suivant apparaît dans l’article
de rendu du composantRazor ASP.NET Core et illustre l’utilisation d’une expression
lambda pour la suppression d’un Timer.

TimerDisposal1.razor :

razor

@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 1</PageTitle>


<h1>Timer Disposal Example 1</h1>

<p>Current count: @currentCount</p>

@code {
private int currentCount = 0;
private Timer timer = new(1000);

protected override void OnInitialized()


{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}

private void OnTimerCallback()


{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}

public void Dispose() => timer.Dispose();


}

7 Notes

Dans l’exemple précédent, l’appel à StateHasChanged est encapsulé par un appel à


ComponentBase.InvokeAsync, car le rappel est appelé en dehors du contexte de
synchronisation de Blazor. Pour plus d’informations, consultez le rendu de
composants Razor ASP.NET Core.

Si l’objet est créé dans une méthode de cycle de vie, telle que OnInitialized{Async},
vérifiez null avant d’appeler Dispose .

TimerDisposal2.razor :

razor

@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 2</PageTitle>

<h1>Timer Disposal Example 2</h1>

<p>Current count: @currentCount</p>


@code {
private int currentCount = 0;
private Timer? timer;

protected override void OnInitialized()


{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}

private void OnTimerCallback()


{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}

public void Dispose() => timer?.Dispose();


}

Pour plus d'informations, voir :

Nettoyage des ressources non managées (documentation.NET)


Opérateurs conditionnels Null ?. et ?[]

IAsyncDisposable asynchrone

Pour les tâches d’élimination asynchrones, utilisez IAsyncDisposable.DisposeAsync.

Le composant suivant :

Implémente IAsyncDisposable avec la directive @implementsRazor.


Supprime obj , qui est un type non managé qui implémente IAsyncDisposable.
Une vérification null est effectuée, car obj est créée dans une méthode de cycle de
vie (non affichée).

razor

@implements IAsyncDisposable

...

@code {
...
public async ValueTask DisposeAsync()
{
if (obj is not null)
{
await obj.DisposeAsync();
}
}
}

Pour plus d'informations, voir :

Nettoyage des ressources non managées (documentation.NET)


Opérateurs conditionnels Null ?. et ?[]

Affectation de null aux objets supprimés


En règle générale, il n’est pas nécessaire d’affecter null à des objets supprimés après
avoir appelé Dispose/DisposeAsync. Les cas rares pour l’affectation de null incluent les
éléments suivants :

Si le type de l’objet est mal implémenté et ne tolère pas les appels répétés à
Dispose/DisposeAsync, affectez null après suppression pour ignorer correctement
d’autres appels à Dispose/DisposeAsync.
Si un processus de longue durée continue de contenir une référence à un objet
supprimé, l’affectation null permet au récupérateur de mémoire de libérer l’objet
malgré le processus de longue durée contenant une référence à celui-ci.

Il s’agit de scénarios inhabituels. Pour les objets qui sont implémentés correctement et
qui se comportent normalement, il est inutile d’affecter null à des objets supprimés.
Dans les rares cas où un objet doit être affecté null , nous vous recommandons de
documenter la raison et de rechercher une solution qui évite d’avoir à affecter null .

StateHasChanged

7 Notes

L’appel de StateHasChanged dans Dispose n’est pas pris en charge.


StateHasChanged peut être appelée dans le cadre de la suppression du
convertisseur, de sorte que la demande de mises à jour de l’interface utilisateur à
ce stade n’est pas prise en charge.
Gestionnaires d’événements
Toujours annuler les gestionnaires d’événements des événements .NET. Les exemples de
formulaires Blazor suivants montrent comment se désinscrire d'un gestionnaire
d'événements dans la méthode Dispose :

Champ privé et approche lambda

razor

@implements IDisposable

<EditForm EditContext="editContext">
...
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>

@code {
...

private EventHandler<FieldChangedEventArgs>? fieldChanged;

protected override void OnInitialized()


{
editContext = new(model);

fieldChanged = (_, __) =>


{
...
};

editContext.OnFieldChanged += fieldChanged;
}

public void Dispose()


{
editContext.OnFieldChanged -= fieldChanged;
}
}

Approche de méthode privée

razor

@implements IDisposable

<EditForm EditContext="editContext">
...
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>
@code {
...

protected override void OnInitialized()


{
editContext = new(model);
editContext.OnFieldChanged += HandleFieldChanged;
}

private void HandleFieldChanged(object sender,


FieldChangedEventArgs e)
{
...
}

public void Dispose()


{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}

Pour plus d’informations, consultez la section Suppression des composants avec


IDisposable etIAsyncDisposable.

Pour plus d’informations sur le composant EditForm et les formulaires, consultez Vue
d’ensemble des formulaires Blazor ASP.NET Core et les autres articles sur les formulaires
dans le nœud Formulaires.

Fonctions, méthodes et expressions anonymes


Lorsque des fonctions, des méthodes ou des expressions anonymes sont utilisées, il
n’est pas nécessaire d’implémenter IDisposable et de se désabonner des délégués.
Toutefois, l’échec de l’annulation d’un délégué est un problème lorsque l’objet qui
expose l’événement dépasse la durée de vie du composant qui inscrit le délégué.
Lorsque cela se produit, une fuite de mémoire se produit, car le délégué inscrit
maintient l’objet d’origine actif. Par conséquent, utilisez uniquement les approches
suivantes lorsque vous savez que le délégué d’événement se supprime rapidement. En
cas de doute quant à la durée de vie des objets qui nécessitent une suppression,
abonnez-vous à une méthode de délégué et éliminez correctement le délégué, comme
le montrent les exemples précédents.

Approche de méthode lambda anonyme (suppression explicite non requise) :

C#

private void HandleFieldChanged(object sender, FieldChangedEventArgs e)


{
formInvalid = !editContext.Validate();
StateHasChanged();
}

protected override void OnInitialized()


{
editContext = new(starship);
editContext.OnFieldChanged += (s, e) =>
HandleFieldChanged((editContext)s, e);
}

Approche d’expression lambda anonyme (suppression explicite non requise) :

C#

private ValidationMessageStore? messageStore;

[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }

protected override void OnInitialized()


{
...

messageStore = new(CurrentEditContext);

CurrentEditContext.OnValidationRequested += (s, e) =>


messageStore.Clear();
CurrentEditContext.OnFieldChanged += (s, e) =>
messageStore.Clear(e.FieldIdentifier);
}

L’exemple complet du code précédent avec des expressions lambda anonymes


apparaît dans l’article Validation de formulaires Blazor ASP.NET Core.

Pour plus d’informations, consultez Nettoyage des ressources non managées et les
rubriques qui le suivent sur l’implémentation des méthodes Dispose et DisposeAsync .

Travail en arrière-plan annulable


Les composants effectuent souvent un travail en arrière-plan de longue durée, comme
effectuer des appels réseau (HttpClient) et interagir avec des bases de données. Il est
souhaitable d’arrêter le travail en arrière-plan pour conserver les ressources système
dans plusieurs situations. Par exemple, les opérations asynchrones en arrière-plan ne
s’arrêtent pas automatiquement lorsqu’un utilisateur quitte un composant.
Voici d’autres raisons pour lesquelles les éléments de travail en arrière-plan peuvent
nécessiter une annulation :

Une tâche en arrière-plan d’exécution a été démarrée avec des données d’entrée
ou des paramètres de traitement défectueux.
L’ensemble actuel d’éléments de travail en arrière-plan en cours d’exécution doit
être remplacé par un nouvel ensemble d’éléments de travail.
La priorité des tâches en cours d’exécution doit être modifiée.
L’application doit être arrêtée pour le redéploiement du serveur.
Les ressources du serveur deviennent limitées, ce qui nécessite la replanification
des éléments de travail en arrière-plan.

Pour implémenter un modèle de travail en arrière-plan annulable dans un composant :

Utilisez un CancellationTokenSource et CancellationToken.


Lors de l’élimination du composant et à tout moment l’annulation est souhaitée en
annulant manuellement le jeton, appelez CancellationTokenSource.Cancel pour
signaler que le travail en arrière-plan doit être annulé.
Une fois l’appel asynchrone retourné, appelez ThrowIfCancellationRequested sur le
jeton.

Dans l’exemple suivant :

await Task.Delay(5000, cts.Token); représente un travail en arrière-plan

asynchrone de longue durée.


BackgroundResourceMethod représente une méthode d’arrière-plan de longue durée
qui ne doit pas démarrer si le Resource est supprimé avant l’appel de la méthode.

BackgroundWork.razor :

razor

@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<PageTitle>Background Work</PageTitle>

<h1>Background Work Example</h1>

<p>
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
If you trigger disposal within 10 seconds of page load, the
<code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
If disposal occurs after <code>BackgroundResourceMethod</code> is called
but before action
is taken on the resource, an <code>ObjectDisposedException</code> is
thrown by
<code>BackgroundResourceMethod</code>, and the resource isn't processed.
</p>

@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
private IList<string> messages = [];

protected async Task LongRunningWork()


{
Logger.LogInformation("Long running work started");

await Task.Delay(10000, cts.Token);

cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}

public void Dispose()


{
Logger.LogInformation("Executing Dispose");

if (!cts.IsCancellationRequested)
{
cts.Cancel();
}

cts?.Dispose();
resource?.Dispose();
}

private class Resource : IDisposable


{
private bool disposed;

public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)


{
logger.LogInformation("BackgroundResourceMethod: Start method");

if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}

// Take action on the Resource

logger.LogInformation("BackgroundResourceMethod: Action on
Resource");
}

public void Dispose() => disposed = true;


}
}

Blazor Server événements de reconnexion


Les événements de cycle de vie des composants abordés dans cet article fonctionnent
séparément des gestionnaires d’événements de reconnexion côté serveur. En cas de
perte de la connexion SignalR au client, seules les mises à jour de l’interface utilisateur
sont interrompues. Les mises à jour de l’interface utilisateur reprennent lorsque la
connexion est rétablie. Pour plus d’informations sur les événements et la configuration
du gestionnaire de circuit, consultez les conseils ASP.NET Core BlazorSignalR.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Virtualisation de composants ASP.NET
Core Razor
Article • 22/12/2023

Cet article explique comment utiliser la virtualisation de composants dans les


applications ASP.NET Core Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lors de l’utilisation des modes d’affichage WebAssembly interactif ou Auto interactif, le


code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Virtualization
Améliorez les performances perçues d’un rendu des composants en utilisant la prise en
charge intégrée de la virtualisation de l’infrastructure Blazor avec le composant
Virtualize<TItem>. La virtualisation est une technique permettant de limiter le rendu de
l’interface utilisateur aux parties actuellement visibles. Par exemple, la virtualisation est
utile lorsque l’application doit afficher une longue liste d’éléments et que seul un sous-
ensemble d’éléments doit être visible à un moment donné.

Utilisez le composant Virtualize<TItem> quand :

Rendu d’un ensemble d’éléments de données dans une boucle.


La plupart des éléments ne sont pas visibles en raison du défilement.
Les éléments rendus ont la même taille.

Lorsque l’utilisateur fait défiler jusqu’à un point arbitraire dans la liste des éléments du
composant Virtualize<TItem>, le composant calcule les éléments visibles à afficher. Les
éléments invisibles ne sont pas rendus.

Sans virtualisation, une liste classique peut utiliser une boucle foreach C# pour afficher
chaque élément d’une liste. Dans l’exemple suivant :

allFlights est une collection de vols d’avion.

Le composant FlightSummary affiche des détails sur chaque vol.


L’attribut de directive @key conserve la relation de chaque composant
FlightSummary à son vol rendu par le FlightId du vol.

razor

<div style="height:500px;overflow-y:scroll">
@foreach (var flight in allFlights)
{
<FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
}
</div>
Si la collection contient des milliers de vols, le rendu des vols prend beaucoup de temps,
et les utilisateurs subissent un ralentissement notable de l’interface utilisateur. La plupart
des vols ne sont pas visibles, car ils se trouvent en dehors de la hauteur de l’élément
<div> .

Au lieu d’afficher la liste complète des vols en une fois, remplacez la boucle foreach
dans l’exemple précédent par le composant Virtualize<TItem> :

Spécifiez allFlights comme source d’élément fixe pour Virtualize<TItem>.Items.


Seuls les vols actuellement visibles sont rendus par le composant
Virtualize<TItem>.
Spécifiez un contexte pour chaque vol avec le paramètre Context . Dans l’exemple
suivant, flight est utilisé comme contexte, qui fournit l’accès aux membres de
chaque vol.

razor

<div style="height:500px;overflow-y:scroll">
<Virtualize Items="@allFlights" Context="flight">
<FlightSummary @key="flight.FlightId" Details="@flight.Summary" />
</Virtualize>
</div>

Si un contexte n’est pas spécifié avec le paramètre Context , utilisez la valeur de context
dans le modèle de contenu d’élément pour accéder aux membres de chaque vol :

razor

<div style="height:500px;overflow-y:scroll">
<Virtualize Items="@allFlights">
<FlightSummary @key="context.FlightId" Details="@context.Summary" />
</Virtualize>
</div>

Le composant Virtualize<TItem> :

Calcule le nombre d’éléments à restituer en fonction de la hauteur du conteneur et


de la taille des éléments rendus.
Recalcule et reformule les éléments à mesure que l’utilisateur défile.
Extrait uniquement la tranche d’enregistrements d’une API externe qui correspond
à la région visible actuelle, au lieu de télécharger toutes les données de la
collection.
Reçoit un ICollection<T> générique pour Virtualize<TItem>.Items. Si une
collection non générique fournit les éléments (par exemple, une collection de
DataRow), suivez les instructions de la section Délégué du fournisseur d’éléments
pour fournir les éléments.

Le contenu de l’élément pour le composant Virtualize<TItem> peut inclure :

Code HTML et code Razor bruts, comme le montre l’exemple précédent.


Un ou plusieurs composants Razor.
Combinaison de composants HTML/Razor et Razor.

Délégué du fournisseur d’éléments


Si vous ne souhaitez pas charger tous les éléments en mémoire ou si la collection n’est
pas un ICollection<T> générique, vous pouvez spécifier une méthode de délégué du
fournisseur d’éléments au paramètre Virtualize<TItem>.ItemsProvider du composant
qui récupère de manière asynchrone les éléments requis à la demande. Dans l’exemple
suivant, la méthode LoadEmployees fournit les éléments au composant
Virtualize<TItem> :

razor

<Virtualize Context="employee" ItemsProvider="@LoadEmployees">


<p>
@employee.FirstName @employee.LastName has the
job title of @employee.JobTitle.
</p>
</Virtualize>

Le fournisseur d’éléments reçoit un ItemsProviderRequest, qui spécifie le nombre


d’éléments requis à partir d’un index de début spécifique. Le fournisseur d’éléments
récupère ensuite les éléments demandés à partir d’une base de données ou d’un autre
service et les retourne en tant que ItemsProviderResult<TItem> avec le nombre total
d’éléments. Le fournisseur d’éléments peut choisir de récupérer les éléments avec
chaque requête ou de les mettre en cache afin qu’ils soient facilement disponibles.

Un composant Virtualize<TItem> ne peut accepter qu’une seule source d’élément à


partir de ses paramètres. N’essayez donc pas simultanément d’utiliser un fournisseur
d’éléments et d’affecter une collection à Items . Si les deux sont affectés, une
InvalidOperationException est levée lorsque les paramètres du composant sont définis
au moment de l’exécution.

L’exemple suivant charge des employés à partir d’un EmployeeService (non illustré) :

C#
private async ValueTask<ItemsProviderResult<Employee>> LoadEmployees(
ItemsProviderRequest request)
{
var numEmployees = Math.Min(request.Count, totalEmployees -
request.StartIndex);
var employees = await
EmployeesService.GetEmployeesAsync(request.StartIndex,
numEmployees, request.CancellationToken);

return new ItemsProviderResult<Employee>(employees, totalEmployees);


}

Dans l’exemple suivant, une collection de DataRow est une collection non générique, de
sorte qu’un délégué de fournisseur d’éléments est utilisé pour la virtualisation :

razor

<Virtualize Context="row" ItemsProvider="@GetRows">


...
</Virtualize>

@code{
...

private ValueTask<ItemsProviderResult<DataRow>>
GetRows(ItemsProviderRequest request)
{
return new(new ItemsProviderResult<DataRow>(
dataTable.Rows.OfType<DataRow>
().Skip(request.StartIndex).Take(request.Count),
dataTable.Rows.Count));
}
}

Virtualize<TItem>.RefreshDataAsync indique au composant de re-demander les


données à partir de son ItemsProvider. Cela est utile lorsque des données externes
changent. Il n’est généralement pas nécessaire d’appeler RefreshDataAsync lors de
l’utilisation de Items.

RefreshDataAsync met à jour les données d’un composant Virtualize<TItem> sans


provoquer de nouvelle mise à jour. Si RefreshDataAsync est appelé à partir d’un
gestionnaire d’événements Blazor ou d’une méthode de cycle de vie de composant, le
déclenchement d’un rendu n’est pas obligatoire, car un rendu est automatiquement
déclenché à la fin du gestionnaire d’événements ou de la méthode de cycle de vie. Si
RefreshDataAsync est déclenché séparément d’une tâche ou d’un événement en arrière-
plan, comme dans le délégué ForecastUpdated suivant, appelez StateHasChanged pour
mettre à jour l’interface utilisateur à la fin de la tâche ou de l’événement en arrière-plan :
C#

<Virtualize ... @ref="virtualizeComponent">


...
</Virtualize>

...

private Virtualize<FetchData>? virtualizeComponent;

protected override void OnInitialized()


{
WeatherForecastSource.ForecastUpdated += async () =>
{
await InvokeAsync(async () =>
{
await virtualizeComponent?.RefreshDataAsync();
StateHasChanged();
});
});
}

Dans l'exemple précédent :

RefreshDataAsync est appelé en premier pour obtenir de nouvelles données pour


le composant Virtualize<TItem>.
StateHasChanged est appelé pour effectuer à nouveau le rendu du composant.

Espace réservé
Étant donné que la demande d’éléments à partir d’une source de données distante peut
prendre un certain temps, vous avez la possibilité de restituer un espace réservé avec le
contenu de l’élément :

Utilisez Placeholder ( <Placeholder>...</Placeholder> ) pour afficher le contenu


jusqu’à ce que les données d’élément soient disponibles.
Utilisez Virtualize<TItem>.ItemContent pour définir le modèle d’élément pour la
liste.

razor

<Virtualize Context="employee" ItemsProvider="@LoadEmployees">


<ItemContent>
<p>
@employee.FirstName @employee.LastName has the
job title of @employee.JobTitle.
</p>
</ItemContent>
<Placeholder>
<p>
Loading&hellip;
</p>
</Placeholder>
</Virtualize>

Contenu vide
Utilisez le paramètre EmptyContent pour fournir du contenu lorsque le composant a été
chargé et que Items est vide ou ItemsProviderResult<TItem>.TotalItemCount égal à
zéro.

EmptyContent.razor :

razor

@page "/empty-content"

<h1>Empty Content Example</h1>

<Virtualize Items="@stringList">
<ItemContent>
<p>
@context
</p>
</ItemContent>
<EmptyContent>
<p>
There are no strings to display.
</p>
</EmptyContent>
</Virtualize>

@code {
private List<string>? stringList;

protected override void OnInitialized() => stringList ??= new();


}

Modifiez la méthode OnInitialized lambda pour consulter les chaînes d’affichage du


composant :

C#

protected override void OnInitialized() =>


stringList ??= new() { "Here's a string!", "Here's another string!" };
Taille de l’article
La hauteur de chaque élément en pixels peut être définie avec
Virtualize<TItem>.ItemSize (valeur par défaut : 50). L’exemple suivant modifie la hauteur
de chaque élément des 50 pixels par défaut à 25 pixels :

razor

<Virtualize Context="employee" Items="@employees" ItemSize="25">


...
</Virtualize>

Par défaut, le composant Virtualize<TItem> mesure la taille de rendu (hauteur) des


éléments individuels après le rendu initial. Utilisez ItemSize pour fournir une taille
d’élément exacte à l’avance pour faciliter des performances de rendu initiales précises et
garantir une position de défilement correcte pour les rechargements de page. Si la
valeur par défaut ItemSize entraîne le rendu de certains éléments en dehors de
l’affichage actuellement visible, un deuxième rendu est déclenché. Pour maintenir
correctement la position de défilement du navigateur dans une liste virtualisée, le rendu
initial doit être correct. Si ce n’est pas le cas, les utilisateurs pourraient voir les mauvais
éléments.

Nombre de suranalyses
Virtualize<TItem>.OverscanCount détermine le nombre d’éléments supplémentaires
rendus avant et après la région visible. Ce paramètre permet de réduire la fréquence de
rendu pendant le défilement. Toutefois, des valeurs plus élevées entraînent le rendu d’un
plus grand nombre d’éléments dans la page (valeur par défaut : 3). L’exemple suivant
modifie le nombre de suranalyses de la valeur par défaut de trois éléments à quatre
éléments :

razor

<Virtualize Context="employee" Items="@employees" OverscanCount="4">


...
</Virtualize>

Modification de l'état
Lorsque vous apportez des modifications aux éléments affichés par le composant
Virtualize<TItem>, appelez StateHasChanged pour forcer la réévaluation et la
réapprobation du composant. Pour plus d’informations, consultez le rendu de
composants Razor ASP.NET Core.

Prise en charge du défilement au clavier


Pour permettre aux utilisateurs de faire défiler du contenu virtualisé à l’aide de leur
clavier, assurez-vous que les éléments virtualisés ou le conteneur de défilement lui-
même peuvent faire l’objet du focus. Si vous ne parvenez pas à effectuer cette étape, le
défilement au clavier ne fonctionne pas dans les navigateurs basés sur Chromium.

Par exemple, vous pouvez utiliser un attribut tabindex sur le conteneur de défilement :

razor

<div style="height:500px; overflow-y:scroll" tabindex="-1">


<Virtualize Items="@allFlights">
<div class="flight-info">...</div>
</Virtualize>
</div>

Pour en savoir plus sur la signification de la valeur tabindex -1 , 0 ou d’autres valeurs,


consultez tabindex (documentation MDN) .

Styles avancés et détection de défilement


Le composant Virtualize<TItem> est conçu uniquement pour prendre en charge des
mécanismes de disposition d’éléments spécifiques. Pour comprendre les dispositions
d’éléments qui fonctionnent correctement, l’article suivant explique comment
Virtualize détecte les éléments qui doivent être visibles pour être affichés à

l’emplacement approprié.

Si votre code source ressemble à ce qui suit :

razor

<div style="height:500px; overflow-y:scroll" tabindex="-1">


<Virtualize Items="@allFlights" ItemSize="100">
<div class="flight-info">Flight @context.Id</div>
</Virtualize>
</div>

Au moment de l’exécution, le composant Virtualize<TItem> restitue une structure DOM


similaire à ce qui suit :
HTML

<div style="height:500px; overflow-y:scroll" tabindex="-1">


<div style="height:1100px"></div>
<div class="flight-info">Flight 12</div>
<div class="flight-info">Flight 13</div>
<div class="flight-info">Flight 14</div>
<div class="flight-info">Flight 15</div>
<div class="flight-info">Flight 16</div>
<div style="height:3400px"></div>
</div>

Le nombre réel de lignes rendues et la taille des interlignes varient en fonction de votre
style et de la taille de la collection Items . Toutefois, notez qu’il existe des éléments
d’espacement div injectés avant et après votre contenu. Ils remplissent deux fonctions :

Fournir un décalage avant et après votre contenu, ce qui entraîne l’affichage des
éléments actuellement visibles à l’emplacement approprié dans la plage de
défilement et la plage de défilement elle-même pour représenter la taille totale de
tout le contenu.
Détecter quand l’utilisateur fait défiler au-delà de la plage visible actuelle, ce qui
signifie que du contenu différent doit être rendu.

7 Notes

Pour savoir comment contrôler la balise d’élément HTML d’espaceur, consultez la


section Contrôle du nom de balise de l’élément d’espacement plus loin dans cet
article.

Les éléments d’espacement utilisent un observateur d’intersection en interne pour


recevoir une notification lorsqu’ils deviennent visibles. Virtualize dépend de la
réception de ces événements. Virtualize fonctionne dans les conditions suivantes :

Tous les éléments de contenu ont une hauteur identique. Cela permet de calculer
le contenu correspondant à une position de défilement donnée sans extraire au
préalable chaque élément de données et rendre les données dans un élément
DOM.

Les interlignes et les lignes de contenu sont rendus dans une seule pile verticale,
chaque élément remplissant toute la largeur horizontale. Il s’agit généralement
du comportement par défaut. Dans les cas classiques avec des éléments div ,
Virtualize fonctionne par défaut. Si vous utilisez CSS pour créer une disposition

plus avancée, gardez à l’esprit les exigences suivantes :


Le style de conteneur de défilement nécessite un display avec l’une des valeurs
suivantes :
block (par défaut pour div ).
table-row-group (par défaut pour tbody ).

flex avec flex-direction défini sur column . Assurez-vous que les enfants

immédiats du composant Virtualize<TItem> ne sont pas réduits selon des


règles flexibles. Par exemple, ajoutez .mycontainer > div { flex-shrink: 0
}.

Le style de ligne de contenu nécessite un display avec l’une des valeurs


suivantes :
block (par défaut pour div ).
table-row (par défaut pour tr ).

N’utilisez pas CSS pour interférer avec la disposition des éléments


d’espacement. Par défaut, les éléments de l’espaceur ont une valeur display de
block , sauf si le parent est un groupe de lignes de table, auquel cas ils ont la

valeur par défaut table-row . N’essayez pas d’influencer la largeur ou la hauteur


des éléments d’espacement, notamment en leur faisant avoir une bordure ou
des pseudo-éléments content .

Toute approche qui empêche les espacements et les éléments de contenu de s’afficher
sous la forme d’une pile verticale unique, ou qui fait varier les éléments de contenu en
hauteur, empêche le bon fonctionnement du composant Virtualize<TItem>.

Virtualisation au niveau racine


Le composant Virtualize<TItem> prend en charge l’utilisation du document lui-même
comme racine de défilement, comme alternative à l’utilisation d’un autre élément avec
overflow-y: scroll . Dans l’exemple suivant, les éléments <html> ou <body> sont mis en

forme dans un composant avec overflow-y: scroll :

razor

<HeadContent>
<style>
html, body { overflow-y: scroll }
</style>
</HeadContent>
Contrôle du nom de balise de l’élément
d’espacement
Si le composant Virtualize<TItem> est placé à l’intérieur d’un élément qui nécessite un
nom de balise enfant spécifique, SpacerElement vous permet d’obtenir ou de définir le
nom de la balise d’espaceur de virtualisation. La valeur par défaut est div . Pour
l’exemple suivant, le composant Virtualize<TItem> s’affiche à l’intérieur d’un élément de
corps de table (tbody ), de sorte que l’élément enfant approprié pour une ligne de
table (tr ) est défini comme espaceur.

VirtualizedTable.razor :

razor

@page "/virtualized-table"

<HeadContent>
<style>
html, body { overflow-y: scroll }
</style>
</HeadContent>

<h1>Virtualized Table Example</h1>

<table id="virtualized-table">
<thead style="position: sticky; top: 0; background-color: silver">
<tr>
<th>Item</th>
<th>Another column</th>
</tr>
</thead>
<tbody>
<Virtualize Items="@fixedItems" ItemSize="30" SpacerElement="tr">
<tr @key="context" style="height: 30px;" id="row-@context">
<td>Item @context</td>
<td>Another value</td>
</tr>
</Virtualize>
</tbody>
</table>

@code {
private List<int> fixedItems = Enumerable.Range(0, 1000).ToList();
}

Dans l’exemple précédent, la racine du document est utilisée comme conteneur de


défilement, de sorte que les éléments html et body sont mis en forme avec overflow-y:
scroll . Pour plus d'informations, reportez-vous aux ressources suivantes :
Section Virtualisation au niveau racine
Contrôler le contenu head dans les applications ASP.NET Core Blazor

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Rendu de composants ASP.NET Core
Razor
Article • 21/12/2023

Cet article explique le rendu des composants Razor dans les applications ASP.NET Core
Blazor, notamment quand appeler StateHasChanged pour déclencher manuellement un
composant à afficher.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode de rendu interactif avec une directive @rendermode dans le fichier de définition du
composant ( .razor ) :

Dans une application web Blazor, le composant doit avoir un mode de rendu
interactif appliqué, soit dans le fichier de définition du composant, soit hérité d’un
composant parent. Pour plus d’informations, consultez Modes de rendu ASP.NET
Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode de rendu, car
les composants s’exécutent toujours de manière interactive sur WebAssembly dans
une application Blazor WebAssembly.

Lors de l’utilisation des modes WebAssembly interactif ou Rendu automatique interactif,


le code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas
de code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.
La meilleure façon d’exécuter le code de démonstration est de télécharger
les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne sont pas inclus dans les exemples
d’applications, mais nous nous efforçons de transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Conventions de rendu pour ComponentBase


Les composants doivent être rendus lorsqu’ils sont ajoutés pour la première fois à la
hiérarchie des composants par un composant parent. Il s’agit du seul moment où un
composant doit obligatoirement être rendu. Les composants peuvent être rendus à
d’autres moments selon leur propre logique et leurs propres conventions.

Par défaut, les composants Razor héritent de la classe de base ComponentBase, qui
contient la logique pour déclencher une nouvelle génération aux moments suivants :

Après avoir appliqué un ensemble mis à jour de paramètres à partir d’un


composant parent.
Après avoir appliqué une valeur mise à jour pour un paramètre en cascade.
Après notification d’un événement et appel de l’un de ses propres gestionnaires
d’événements.
Après un appel à sa propre méthode StateHasChanged (consultez Cycle de vie des
composants ASP.NET Core Razor). Pour obtenir des conseils sur la façon
d’empêcher le remplacement des paramètres de composant enfant lorsque
StateHasChanged est appelé dans un composant parent, consultez Éviter le
remplacement de paramètres dans ASP.NET Core Blazor.

Les composants hérités de ComponentBase ignorent les nouveaux rendus en raison des
mises à jour de paramètres si l’une des valeurs suivantes est true :

Tous les paramètres proviennent d’un ensemble de types connus† ou d’un type
primitif qui n’a pas changé depuis le jeu de paramètres précédent.

†Le framework Blazor utilise un ensemble de règles intégrées et des vérifications


explicites de type de paramètre pour la détection des modifications. Ces règles et
les types sont susceptibles d’être modifiés à tout moment. Pour plus
d’informations, consultez l’API ChangeDetection dans la source de référence
ASP.NET Core .

7 Notes
Les liens de documentation vers la source de référence .NET chargent
généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

La méthode ShouldRender du composant retourne false .

Contrôler le flux de rendu


Dans la plupart des cas, les conventions ComponentBase entraînent le sous-ensemble
correct de nouveaux rendus de composant après qu’un événement se soit produit. Les
développeurs ne sont généralement pas tenus de fournir une logique manuelle pour
indiquer au framework les composants à restituer à nouveau et quand le faire. L’effet
global des conventions du framework est que le composant qui reçoit un événement
effectue son rendu à nouveau lui-même, ce qui déclenche de manière récursive le
nouveau rendu des composants descendants dont les valeurs de paramètre peuvent
avoir changé.

Pour plus d’informations sur les implications en termes de performances des


conventions du framework et sur la façon d’optimiser la hiérarchie des composants
d’une application pour le rendu, consultez Meilleures pratiques en matière de
performances ASP.NET Core Blazor.

Rendu en streaming
Utilisez le rendu de diffusion en continu avec le rendu interactif côté serveur (SSR
interactif) afin de diffuser en continu les mises à jour de contenu sur le flux de réponse
et améliorer l’expérience utilisateur en ce qui concerne les composants qui effectuent
des tâches asynchrones de longue durée pour effectuer un rendu complet.

Par exemple, considérez un composant qui effectue une requête de base de données
longue ou un appel d’API web pour afficher des données lorsque la page se charge.
Normalement, les tâches asynchrones exécutées dans le cadre du rendu d’un
composant côté serveur doivent être terminées avant l’envoi de la réponse rendue, ce
qui peut retarder le chargement de la page. Tout retard significatif dans le rendu de la
page nuit à l’expérience utilisateur. Pour améliorer l’expérience utilisateur, le rendu en
streaming affiche initialement la page entière rapidement, avec du contenu d’espace
réservé, pendant que les opérations asynchrones s’exécutent. Une fois les opérations
terminées, le contenu mis à jour est envoyé au client sur la même connexion de réponse
et corrigé dans le DOM.

Le rendu de diffusion en continu exige que le serveur évite de mettre la sortie en


mémoire tampon. Les données de réponse doivent être transmises au client à mesure
que les données sont générées. Pour les hôtes qui appliquent la mise en mémoire
tampon, le rendu de diffusion en continu se dégrade sans perte de données et la page
se charge sans rendu de diffusion en continu.

Pour diffuser en continu des mises à jour de contenu lors de l’utilisation du rendu
statique côté serveur, appliquez l’attribut [StreamRendering(true)] au composant. Le
rendu en streaming doit être explicitement activé, car les mises à jour diffusées en
continu peuvent entraîner le déplacement du contenu sur la page. Les composants sans
l’attribut adoptent automatiquement le rendu en streaming si le composant parent
utilise la fonctionnalité. Passez false à l’attribut d’un composant enfant pour désactiver
la fonctionnalité à ce stade et descendre plus loin dans la sous-arborescence du
composant. L’attribut est fonctionnel lorsqu’il est appliqué aux composants fournis par
une Razor bibliothèque de classes.

L’exemple suivant est basé sur le composant Weather d’une application créée à partir du
Blazor modèle de projet Web App. L’appel à Task.Delay simule la récupération
asynchrone de données météorologiques. Le composant affiche initialement le contenu
de l’espace réservé (« Loading... ») sans attendre la fin du délai asynchrone. Lorsque le
délai asynchrone se termine et que le contenu des données météorologiques est
généré, le contenu est diffusé en continu vers la réponse et corrigé dans la table des
prévisions météorologiques.

Weather.razor :

razor

@page "/weather"
@attribute [StreamRendering(true)]

...

@if (forecasts == null)


{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
...
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}

@code {
...

private WeatherForecast[]? forecasts;

protected override async Task OnInitializedAsync()


{
await Task.Delay(500);

...

forecasts = ...
}
}

Supprimer l’actualisation de l’interface


utilisateur ( ShouldRender )
ShouldRender est appelé chaque fois qu’un composant est rendu. Remplacez
ShouldRender pour gérer l’actualisation de l’interface utilisateur. Si l’implémentation
retourne true , l’interface utilisateur est actualisée.

Même si ShouldRender est remplacé, le composant est toujours rendu initialement.

ControlRender.razor :

razor

@page "/control-render"

<PageTitle>Control Render</PageTitle>

<h1>Control Render Example</h1>

<label>
<input type="checkbox" @bind="shouldRender" />
Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
<button @onclick="IncrementCount">Click me</button>
</p>

@code {
private int currentCount = 0;
private bool shouldRender = true;

protected override bool ShouldRender()


{
return shouldRender;
}

private void IncrementCount()


{
currentCount++;
}
}

Pour plus d’informations sur les meilleures pratiques en matière de performances


relatives à ShouldRender, consultez Meilleures pratiques en matière de performances
ASP.NET Core Blazor.

Quand appeler StateHasChanged


L’appel à StateHasChanged vous permet de déclencher un rendu à tout moment.
Toutefois, veillez à ne pas appeler StateHasChanged inutilement, ce qui est une erreur
courante qui impose des coûts de rendu inutiles.

Le code ne doit pas avoir besoin d’appeler StateHasChanged pour :

La gestion régulière des événements, de manière synchrone ou asynchrone, car


ComponentBase déclenche un rendu pour la plupart des gestionnaires
d’événements de routine.
L’implémentation d’une logique de cycle de vie classique, comme OnInitialized ou
OnParametersSetAsync, de manière synchrone ou asynchrone, car ComponentBase
déclenche un rendu pour les événements de cycle de vie classiques.

Toutefois, il peut être judicieux d’appeler StateHasChanged dans les cas décrits dans les
sections suivantes de cet article :

Un gestionnaire asynchrone implique plusieurs phases asynchrones


Réception d’un appel d’un élément externe au système de rendu et de gestion des
événements Blazor
Pour afficher le composant en dehors de la sous-arborescence qui est rendue à
nouveau par un événement particulier

Un gestionnaire asynchrone implique plusieurs phases


asynchrones
En raison de la façon dont les tâches sont définies dans .NET, un récepteur d’un Task
peut uniquement observer son achèvement final, et non les états asynchrones
intermédiaires. Par conséquent, ComponentBase ne peut déclencher un nouveau rendu
que lorsque le Task est retourné pour la première fois et que le Task se termine. Le
framework ne peut pas savoir comment rendre un composant à d’autres points
intermédiaires, par exemple quand un IAsyncEnumerable<T>retourne des données dans
une série de Task intermédiaires . Si vous souhaitez effectuer une nouvelle création à
des points intermédiaires, appelez StateHasChanged sur ces points.

Considérez le composant CounterState1 suivant, qui met à jour le compte quatre fois
chaque fois que la méthode IncrementCount s’exécute :

Les rendus automatiques se produisent après le premier et le dernier incréments


de currentCount .
Les rendus manuels sont déclenchés par des appels à StateHasChanged lorsque le
framework ne déclenche pas automatiquement de nouveaux rendus aux points de
traitement intermédiaires où currentCount est incrémenté.

CounterState1.razor :

razor

@page "/counter-state-1"

<PageTitle>Counter State 1</PageTitle>

<h1>Counter State Example 1</h1>

<p>
Current count: @currentCount
</p>

<p>
<button class="btn btn-primary" @onclick="IncrementCount">Click
me</button>
</p>

@code {
private int currentCount = 0;

private async Task IncrementCount()


{
currentCount++;
// Renders here automatically

await Task.Delay(1000);
currentCount++;
StateHasChanged();

await Task.Delay(1000);
currentCount++;
StateHasChanged();

await Task.Delay(1000);
currentCount++;
// Renders here automatically
}
}

Réception d’un appel d’un élément externe au système


de rendu et de gestion des événements Blazor
ComponentBase connaît uniquement ses propres méthodes de cycle de vie et ses
événements déclenchés par Blazor. ComponentBase ne connaît pas les autres
événements qui peuvent se produire dans le code. Par exemple, tous les événements C#
déclenchés par un magasin de données personnalisé sont inconnus de Blazor. Pour que
de tels événements déclenchent une nouvelle mise à jour afin d’afficher les valeurs
mises à jour dans l’interface utilisateur, appelez StateHasChanged.

Considérez le composant CounterState2 suivant qui utilise System.Timers.Timer pour


mettre à jour un nombre à intervalles réguliers et appelle StateHasChanged pour mettre
à jour l’interface utilisateur :

OnTimerCallback s’exécute en dehors d’un flux de rendu managé par Blazor ou

d’une notification d’événement. Par conséquent, OnTimerCallback doit appeler


StateHasChanged, car Blazor n’a pas connaissance des modifications apportées à
currentCount dans le rappel.

Le composant implémente IDisposable, où le Timer est supprimé lorsque le


framework appelle la méthode Dispose . Pour plus d’informations, consultez le
cycle de vie des composants Razor ASP.NET Core.

Étant donné que le rappel est appelé en dehors du contexte de synchronisation de


Blazor, le composant doit encapsuler la logique de OnTimerCallback dans
ComponentBase.InvokeAsync pour le déplacer vers le contexte de synchronisation du
système de rendu. Cela revient à marshaler vers le thread d’interface utilisateur dans
d’autres frameworks d’interface utilisateur. StateHasChanged ne peut être appelé qu’à
partir du contexte de synchronisation du convertisseur et lève une exception dans le cas
contraire :

System.InvalidOperationException : « Le thread actuel n’est pas associé au


répartiteur. Utilisez InvokeAsync() pour basculer l’exécution vers le répartiteur lors
du déclenchement de l’état du rendu ou du composant. »

CounterState2.razor :

razor

@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<PageTitle>Counter State 2</PageTitle>

<h1>Counter State Example 2</h1>

<p>
This counter demonstrates <code>Timer</code> disposal.
</p>

<p>
Current count: @currentCount
</p>

@code {
private int currentCount = 0;
private Timer timer = new(1000);

protected override void OnInitialized()


{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}

private void OnTimerCallback()


{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}

Pour afficher le composant en dehors de la sous-


arborescence qui est rendue à nouveau par un
événement particulier
L’interface utilisateur peut impliquer :

1. La distribution d’un événement à un composant.


2. La modification d’un état.
3. Le nouveau rendu d’un composant complètement différent qui n’est pas un
descendant du composant recevant l’événement.

Une façon de gérer ce scénario consiste à fournir une classe de gestion d’état, souvent
en tant que service d’injection de dépendances (DI) injecté dans plusieurs composants.
Quand un composant appelle une méthode sur le gestionnaire d’état, le gestionnaire
d’état déclenche un événement C# qui est ensuite reçu par un composant indépendant.

Pour connaître les approches de gestion de l’état, consultez les ressources suivantes :

Section Service de conteneur d’état en mémoire côté serveur (équivalent côté


client) de l’article sur la Gestion d’état.
Passez des données sur une hiérarchie de composants à l’aide de valeurs et de
paramètres en cascade.
Liez plus de deux composants à l’aide de liaisons de données.

Pour l’approche du gestionnaire d’état, les événements C# sont en dehors du pipeline


de rendu Blazor. Appelez StateHasChanged sur les autres composants que vous
souhaitez rendre à nouveau en réponse aux événements du gestionnaire d’état.

L’approche du gestionnaire d’état est similaire au cas précédent avec


System.Timers.Timer dans la section précédente. Étant donné que la pile des appels
d’exécution reste généralement sur le contexte de synchronisation du convertisseur,
l’appel InvokeAsync n’est normalement pas nécessaire. L’appel InvokeAsync n’est requis
que si la logique échappe au contexte de synchronisation, par exemple en appelant
ContinueWith sur un Task ou en attendant un Task avec ConfigureAwait(false). Pour plus
d’informations, consultez la section Réception d’un appel provenant d’un élément
externe au système de rendu et de gestion des événements Blazor.
6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Composants Blazor basés sur un modèle
ASP.NET Core
Article • 26/12/2023

Cet article explique comment les composants basés sur un modèle peuvent accepter un
ou plusieurs modèles d’interface utilisateur en tant que paramètres, qui peuvent ensuite
être utilisés dans le cadre de la logique de rendu du composant.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Quand vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.
La meilleure façon d’exécuter le code de démonstration consiste à télécharger
les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Composants basés sur un modèle


Les composants basés sur un modèle sont des composants recevant un ou plusieurs
modèles d’interface utilisateur en tant que paramètres qui peuvent ensuite être utilisés
dans le cadre de la logique de rendu du composant. En utilisant des composants basés
sur un modèle, vous pouvez créer des composants réutilisables et de niveau supérieur.
Voici quelques exemples :

Un composant de tableau qui permet à un utilisateur de spécifier des modèles


pour l’en-tête, les lignes et le pied de page de la table.
Un composant de liste qui permet à un utilisateur de spécifier un modèle pour le
rendu d’éléments dans une liste.

Un composant basé sur un modèle est défini en spécifiant un ou plusieurs paramètres


de composant de type RenderFragment ou RenderFragment<TValue>. Un fragment de
rendu représente un segment de l’interface utilisateur à afficher.
RenderFragment<TValue> prend un paramètre de type qui peut être spécifié lorsque le
fragment de rendu est invoqué.

7 Notes

Pour plus d’informations sur RenderFragment, consultez Composants ASP.NET


Core Razor.

Souvent, les composants basés sur un modèle sont typés de manière générique, comme
le montre le composant TableTemplate suivant. Le type générique <T> dans cet exemple
est utilisé pour afficher des valeurs IReadOnlyList<T> , qui dans ce cas est une série de
lignes d’animaux dans un composant qui affiche un tableau d’animaux.

TableTemplate.razor :

razor
@typeparam TItem
@using System.Diagnostics.CodeAnalysis

<table class="table">
<thead>
<tr>@TableHeader</tr>
</thead>
<tbody>
@foreach (var item in Items)
{
if (RowTemplate is not null)
{
<tr>@RowTemplate(item)</tr>
}
}
</tbody>
</table>

@code {
[Parameter]
public RenderFragment? TableHeader { get; set; }

[Parameter]
public RenderFragment<TItem>? RowTemplate { get; set; }

[Parameter, AllowNull]
public IReadOnlyList<TItem> Items { get; set; }
}

Lorsque vous utilisez un composant modèle, les paramètres de modèle peuvent être
spécifiés à l’aide d’éléments enfants qui correspondent aux noms des paramètres. Dans
l’exemple suivant, <TableHeader>...</TableHeader> et <RowTemplate>...<RowTemplate>
fournissent des modèles RenderFragment<TValue> pour TableHeader et RowTemplate
du composant TableTemplate .

Spécifiez l’attribut Context sur l’élément composant lorsque vous souhaitez spécifier le
nom du paramètre de contenu pour le contenu enfant implicite (sans élément enfant
d’enveloppement). Dans l’exemple suivant, l’attribut Context s’affiche sur l’élément
TableTemplate et s’applique à tous les paramètres de modèle

RenderFragment<TValue>.

Pets1.razor :

razor

@page "/pets-1"

<PageTitle>Pets 1</PageTitle>
<h1>Pets Example 1</h1>

<TableTemplate Items="pets" Context="pet">


<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@pet.PetId</td>
<td>@pet.Name</td>
</RowTemplate>
</TableTemplate>

@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};

private class Pet


{
public int PetId { get; set; }
public string? Name { get; set; }
}
}

Vous pouvez également modifier le nom du paramètre à l’aide de l’attribut Context sur
l’élément enfant RenderFragment<TValue>. Dans l’exemple suivant, le Context est
défini sur RowTemplate au lieu de TableTemplate :

Pets2.razor :

razor

@page "/pets-2"

<PageTitle>Pets 2</PageTitle>

<h1>Pets Example 2</h1>

<TableTemplate Items="pets">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate Context="pet">
<td>@pet.PetId</td>
<td>@pet.Name</td>
</RowTemplate>
</TableTemplate>

@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};

private class Pet


{
public int PetId { get; set; }
public string? Name { get; set; }
}
}

Les arguments de composant de type RenderFragment<TValue> ont un paramètre


implicite nommé context , qui peut être utilisé. Dans l’exemple suivant, Context n’est
pas défini. @context.{PROPERTY} fournit des valeurs pour animaux au modèle, où
{PROPERTY} est une propriété Pet :

Pets3.razor :

razor

@page "/pets-3"

<PageTitle>Pets 3</PageTitle>

<h1>Pets Example 3</h1>

<TableTemplate Items="pets">
<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@context.PetId</td>
<td>@context.Name</td>
</RowTemplate>
</TableTemplate>

@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};

private class Pet


{
public int PetId { get; set; }
public string? Name { get; set; }
}
}

Lorsque vous utilisez des composants de type générique, le paramètre de type est
déduit si possible. Toutefois, vous pouvez spécifier explicitement le type avec un attribut
qui a un nom correspondant au paramètre de type, qui est TItem dans l’exemple
précédent :

Pets4.razor :

razor

@page "/pets-4"

<PageTitle>Pets 4</PageTitle>

<h1>Pets Example 4</h1>

<TableTemplate Items="pets" TItem="Pet">


<TableHeader>
<th>ID</th>
<th>Name</th>
</TableHeader>
<RowTemplate>
<td>@context.PetId</td>
<td>@context.Name</td>
</RowTemplate>
</TableTemplate>

@code {
private List<Pet> pets = new()
{
new Pet { PetId = 2, Name = "Mr. Bigglesworth" },
new Pet { PetId = 4, Name = "Salem Saberhagen" },
new Pet { PetId = 7, Name = "K-9" }
};

private class Pet


{
public int PetId { get; set; }
public string? Name { get; set; }
}
}

Ressources supplémentaires
Meilleures pratiques d’ASP.NET Core Blazor en matière de performances
Blazor exemples de dépôt GitHub (dotnet/blazor-samples)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Isolation CSS ASP.NET Core Blazor
Article • 09/02/2024

Par Dave Brock

Cet article explique comment l’isolation CSS permet d’étendre les CSS aux composants
Razor, ce qui peut simplifier les CSS et éviter les collisions avec d’autres composants ou
bibliothèques.

Isolez les styles CSS en pages, vues et composants individuels afin de réduire ou
d’éviter :

Les dépendances sur les styles globaux qui peuvent être difficiles à gérer.
Les conflits de style dans du contenu imbriqué.

Activez l’isolation CSS


Pour définir des styles spécifiques au composant, créez un fichier .razor.css
correspondant au nom du fichier .razor du composant dans le même dossier. Le fichier
.razor.css est un fichier CSS délimité.

Pour un composant Example dans un fichier Example.razor , créez un fichier à côté du


composant nommé Example.razor.css . Le fichier Example.razor.css doit résider dans le
même dossier que le composant Example ( Example.razor ). Le nom de base « Example »
du fichier ne respecte pas la casse.

Example.razor :

razor

@page "/example"

<h1>Scoped CSS Example</h1>

Example.razor.css :

css

h1 {
color: brown;
font-family: Tahoma, Geneva, Verdana, sans-serif;
}
Les styles définis dans Example.razor.css sont uniquement appliqués à la sortie
rendue du composant Example . L’isolation CSS est appliquée aux éléments HTML dans
le fichier Razor correspondant. Les déclarations CSS h1 définies ailleurs dans
l’application ne sont pas en conflit avec les styles du composant Example .

7 Notes

Pour garantir l’isolation du style lors du regroupement, l’importation de CSS dans


des blocs de code Razor n’est pas prise en charge.

Regroupement d’isolation CSS


L’isolation CSS se produit au moment de la build. Blazor réécrit les sélecteurs CSS pour
qu’ils correspondent aux balises rendues par le composant. Les styles CSS réécrits sont
regroupés et produits sous la forme d’une ressource statique. La feuille de style est
référencée à l’intérieur de la balise <head> (emplacement du <head> contenu).
L’élément suivant <link> est ajouté par défaut à une application créée à partir des
modèles de projet Blazor, où l’espace réservé {ASSEMBLY NAME} est le nom de l’assembly
du projet :

HTML

<link href="{ASSEMBLY NAME}.styles.css" rel="stylesheet">

Dans le fichier groupé, chaque composant est associé à un identificateur d’étendue.


Pour chaque composant de style, un attribut HTML est ajouté au format b-{STRING} , où
l’espace réservé {STRING} est une chaîne de dix caractères générée par l’infrastructure.
L’identificateur est unique pour chaque application. Dans le composant rendu Counter ,
Blazor ajoute un identificateur d’étendue à l’élément h1 :

HTML

<h1 b-3xxtam6d07>

Le fichier {ASSEMBLY NAME}.styles.css utilise l’identificateur d’étendue pour regrouper


une déclaration de style avec son composant. L’exemple suivant fournit le style pour
l’élément <h1> précédent :

css
/* /Components/Pages/Counter.razor.rz.scp.css */
h1[b-3xxtam6d07] {
color: brown;
}

Au moment de la génération, un ensemble de projets est créé avec la convention


obj/{CONFIGURATION}/{TARGET FRAMEWORK}/scopedcss/projectbundle/{ASSEMBLY

NAME}.bundle.scp.css , où sont les espaces réservés :

{CONFIGURATION} : la configuration de build de l’application (par exemple, Debug ,

Release ).

{TARGET FRAMEWORK} : l’infrastructure cible (par exemple, net6.0 ).


{ASSEMBLY NAME} : le nom de l’assembly de l’application (par exemple,

BlazorSample ).

Prise en charge des composants enfants


Par défaut, l’isolation CSS s’applique uniquement au composant que vous associez au
format {COMPONENT NAME}.razor.css , où l’espace réservé {COMPONENT NAME} est
généralement le nom du composant. Pour appliquer des modifications à un composant
enfant, utilisez le ::deep pseudo-élément pour tous les éléments descendants dans le
fichier .razor.css du composant parent. Le pseudo-élément ::deep sélectionne les
éléments descendants de l’identificateur d’étendue généré d’un élément.

L’exemple suivant montre un composant parent appelé Parent avec un composant


enfant appelé Child .

Parent.razor :

razor

@page "/parent"

<div>
<h1>Parent component</h1>

<Child />
</div>

Child.razor :

razor
<h1>Child Component</h1>

Mettez à jour la déclaration h1 dans Parent.razor.css avec le pseudo-élément ::deep


pour signifier que la déclaration de style h1 doit s’appliquer au composant parent et à
ses enfants.

Parent.razor.css :

css

::deep h1 {
color: red;
}

Le style h1 s’applique désormais aux composants Parent et Child sans avoir besoin de
créer un fichier CSS délimité distinct pour le composant enfant.

Le pseudo-élément ::deep fonctionne uniquement avec les éléments descendants. La


balise suivante applique les styles h1 aux composants comme prévu. L’identificateur
d’étendue du composant parent est appliqué à l’élément div , afin que le navigateur
sache hériter des styles du composant parent.

Parent.razor :

razor

<div>
<h1>Parent</h1>

<Child />
</div>

Toutefois, l’exclusion de l’élément div supprime la relation descendante. Dans l’exemple


suivant, le style n’est pas appliqué au composant enfant.

Parent.razor :

razor

<h1>Parent</h1>

<Child />
Le pseudo-élément ::deep affecte l’emplacement où l’attribut d’étendue est appliqué à
la règle. Lorsque vous définissez une règle CSS dans un fichier CSS délimité, l’étendue
est appliquée à l’élément le plus à droite par défaut. Par exemple : div > a est
transformé en div > a[b-{STRING}] , où l’espace réservé {STRING} est une chaîne de dix
caractères générée par l’infrastructure (par exemple, b-3xxtam6d07 ). Si vous souhaitez
plutôt que la règle s’applique à un autre sélecteur, le pseudo-élément ::deep vous
permet de le faire. Par exemple, div ::deep > a est transformé en div[b-{STRING}] > a
(par exemple, div[b-3xxtam6d07] > a ).

La possibilité d’attacher le pseudo-élément ::deep à n’importe quel élément HTML vous


permet de créer des styles CSS délimités qui affectent les éléments rendus par d’autres
composants lorsque vous pouvez déterminer la structure des balises HTML rendues.
Pour un composant qui affiche une balise de lien hypertexte ( <a> ) à l’intérieur d’un
autre composant, vérifiez que le composant est enveloppé dans un div (ou tout autre
élément) et utilisez la règle ::deep > a pour créer un style qui n’est appliqué à ce
composant que lorsque le composant parent s’affiche.

) Important

Le CSS délimité s’applique uniquement aux éléments HTML et non aux composants
Razor ou aux assistants de balise, y compris les éléments auxquels un assistant de
balise a été appliqué, tels que <input asp-for="..." /> .

Prise en charge des préprocesseurs CSS


Les préprocesseurs CSS sont utiles pour améliorer le développement CSS en utilisant
des fonctionnalités comme les variables, l’imbrication, les modules, les mixins et
l’héritage. Bien que l’isolation CSS ne prenne pas nativement en charge les
préprocesseurs CSS comme Sass ou Less, l’intégration de préprocesseurs CSS se fait de
manière fluide dès lors que la compilation du préprocesseur se produit avant que Blazor
ne réécrive les sélecteurs CSS lors du processus de build. Par exemple, avec Visual
Studio, configurez la compilation du préprocesseur existant en tant que tâche Avant la
build dans l’Explorateur d’exécuteur de tâches Visual Studio.

De nombreux packages NuGet tiers, comme AspNetCore.SassCompiler , peuvent


compiler des fichiers SASS/SCSS au début du processus de build avant que l’isolation
CSS ne se produise.

Configuration de l’isolation CSS


L’isolation CSS est conçue pour fonctionner en l’état, mais permet de configurer certains
scénarios avancés, notamment lorsqu’il existe des dépendances avec des outils ou des
flux de travail existants.

Personnaliser le format de l’identificateur d’étendue


Par défaut, les identificateurs d’étendue utilisent le format b-{STRING} , où l’espace
réservé {STRING} est une chaîne de dix caractères générée par le framework. Pour
personnaliser le format de l’identificateur d’étendue, mettez à jour le fichier projet avec
un modèle souhaité :

XML

<ItemGroup>
<None Update="Components/Pages/Example.razor.css" CssScope="custom-scope-
identifier" />
</ItemGroup>

Dans l’exemple précédent, le CSS généré pour Example.razor.css change son


identificateur d’étendue de b-{STRING} en custom-scope-identifier .

Utilisez des identificateurs d’étendue pour mettre en œuvre l’héritage avec des fichiers
CSS délimités. Dans l’exemple de Fichier projet suivant, un fichier
BaseComponent.razor.css contient des styles communs entre les composants. Un fichier

DerivedComponent.razor.css hérite de ces styles.

XML

<ItemGroup>
<None Update="Components/Pages/BaseComponent.razor.css" CssScope="custom-
scope-identifier" />
<None Update="Components/Pages/DerivedComponent.razor.css"
CssScope="custom-scope-identifier" />
</ItemGroup>

Utilisez l’opérateur de caractère générique ( * ) pour partager des identificateurs


d’étendue entre plusieurs fichiers :

XML

<ItemGroup>
<None Update="Components/Pages/*.razor.css" CssScope="custom-scope-
identifier" />
</ItemGroup>
Changer le chemin de base pour les ressources web
statiques
Le fichier scoped.styles.css est généré à la racine de l’application. Dans le Fichier
projet, utilisez la <StaticWebAssetBasePath> propriété pour changer le chemin d’accès
par défaut. L’exemple suivant place le fichier scoped.styles.css et le reste des
ressources de l’application dans le chemin d’accès _content :

XML

<PropertyGroup>
<StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>

Désactiver le regroupement automatique


Pour ne pas accepter la façon dont Blazor publie et charge des fichiers délimités au
moment de l’exécution, utilisez la propriété DisableScopedCssBundling . Lors de
l’utilisation de cette propriété, cela signifie que d’autres outils ou processus sont chargés
de prendre les fichiers CSS isolés du répertoire obj , et de les publier et de les charger
au moment de l’exécution :

XML

<PropertyGroup>
<DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>

Désactivez l’isolation CSS


Désactivez l’isolation CSS pour un projet en définissant la propriété <ScopedCssEnabled>
sur false dans le Fichier projet de l’application :

XML

<ScopedCssEnabled>false</ScopedCssEnabled>

Prise en charge de la bibliothèque de classes


Razor (RCL)
Les styles isolés pour les composants d’un package NuGet ou d’une bibliothèque de
classes Razor (RCL) sont automatiquement regroupés :

L’application utilise des importations CSS pour référencer les styles groupés de la
RCL. Pour une bibliothèque de classes nommée ClassLib et une application Blazor
avec une feuille de style BlazorSample.styles.css , la feuille de style RCL est
importée en haut de la feuille de style de l’application :

css

@import '_content/ClassLib/ClassLib.bundle.scp.css';

Les styles groupés de la RCL ne sont pas publiés en tant que ressource web
statique de l’application qui consomme les styles.

Pour plus d’informations sur les bibliothèques de classes Razor, consultez les articles
suivants :

Consommer des composants Razor ASP.NET Core d’une bibliothèque de classes


Razor (RCL)
Interface utilisateur Razor réutilisable dans les bibliothèques de classes avec
ASP.NET Core

Ressources supplémentaires
Razor Isolation CSS des pages
Isolation CSS MVC

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Composants ASP.NET Core Razor rendus
dynamiquement
Article • 28/12/2023

Par Dave Brock

Utilisez le composant DynamicComponent intégré pour restituer les composants par


type.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.
La meilleure façon d’exécuter le code de démonstration consiste à télécharger
les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Composants dynamiques
Un DynamicComponent est utile pour le rendu des composants sans itérer par le biais
de types possibles ou à l’aide d’une logique conditionnelle. Par exemple,
DynamicComponent peut restituer un composant en fonction d’une sélection
d’utilisateur dans une liste déroulante.

Dans l’exemple suivant :

componentType spécifie le type.

parameters spécifie les paramètres de composant à passer au composant


componentType .

razor

<DynamicComponent Type="@componentType" Parameters="@parameters" />

@code {
private Type componentType = ...;
private IDictionary<string, object> parameters = ...;
}

Pour plus d’informations sur la transmission de valeurs de paramètres, consultez la


section Passer des paramètres plus loin dans cet article.

Utilisez la propriété Instance pour accéder à l’instance de composant créée


dynamiquement :

razor

<DynamicComponent Type="@typeof({COMPONENT})" @ref="dc" />

<button @onclick="Refresh">Refresh</button>

@code {
private DynamicComponent? dc;
private Task Refresh()
{
return (dc?.Instance as IRefreshable)?.Refresh();
}
}

Dans l'exemple précédent :

L’espace réservé {COMPONENT} est le type de composant créé dynamiquement.


IRefreshable est un exemple d’interface fourni par le développeur pour l’instance

de composant dynamique.

Exemple
Dans l’exemple suivant, un composant Razor affiche un composant en fonction de la
sélection de l’utilisateur dans une liste déroulante de quatre valeurs possibles.

ノ Agrandir le tableau

Sélection de vaisseau utilisateur Composant Razor partagé à restituer

Rocket Lab® RocketLab.razor

SpaceX® SpaceX.razor

ULA® UnitedLaunchAlliance.razor

Virgin Galactic® VirginGalactic.razor

RocketLab.razor :

razor

<h2>Rocket Lab®</h2>

<p>
Rocket Lab is a registered trademark of
<a href="https://www.rocketlabusa.com/">Rocket Lab USA Inc.</a>
</p>

SpaceX.razor :

razor

<h2>SpaceX®</h2>
<p>
SpaceX is a registered trademark of
<a href="https://www.spacex.com/">Space Exploration Technologies Corp.
</a>
</p>

UnitedLaunchAlliance.razor :

razor

<h2>United Launch Alliance®</h2>

<p>
United Launch Alliance and ULA are registered trademarks of
<a href="https://www.ulalaunch.com/">United Launch Alliance, LLC</a>.
</p>

VirginGalactic.razor :

razor

<h2>Virgin Galactic®</h2>

<p>
Virgin Galactic is a registered trademark of
<a href="https://www.virgingalactic.com/">Galactic Enterprises, LLC</a>.
</p>

DynamicComponent1.razor :

razor

@page "/dynamic-component-1"

<PageTitle>Dynamic Component 1</PageTitle>

<h1>Dynamic Component Example 1</h1>

<p>
<label>
Select your transport:
<select @onchange="OnDropdownChange">
<option value="">Select a value</option>
<option value="@nameof(RocketLab)">Rocket Lab</option>
<option value="@nameof(SpaceX)">SpaceX</option>
<option value="@nameof(UnitedLaunchAlliance)">ULA</option>
<option value="@nameof(VirginGalactic)">Virgin Galactic</option>
</select>
</label>
</p>
@if (selectedType is not null)
{
<div class="border border-primary my-1 p-1">
<DynamicComponent Type="@selectedType" />
</div>
}

@code {
private Type? selectedType;

private void OnDropdownChange(ChangeEventArgs e)


{
/*
IMPORTANT!
Change "BlazorSample.Components" to match
your shared component's namespace in the Type.GetType()
argument.
*/
selectedType = e.Value?.ToString()?.Length > 0 ?
Type.GetType($"BlazorSample.Components.{e.Value}") : null;
}
}

Dans l'exemple précédent :

Les noms de composant sont utilisés comme valeurs d’option à l’aide de


l’opérateur nameof, qui retourne les noms de composant sous forme de chaînes
constantes.
L’espace de noms de l’application est BlazorSample . Modifier l’espace de noms
pour qu’il corresponde à l’espace de noms de votre application.

Passer des paramètres


Si les composants rendus dynamiquement ont des paramètres de composant, passez-
les dans le DynamicComponent en tant que IDictionary<string, object> . La string est
le nom du paramètre et la object est la valeur du paramètre.

L’exemple suivant configure un objet de métadonnées de composant


( ComponentMetadata ) pour fournir des valeurs de paramètre aux composants rendus
dynamiquement en fonction du nom de type. L’exemple n’est qu’une des nombreuses
approches que vous pouvez adopter. Des données de paramètre peuvent également
être fournies à partir d’une API web, d’une base de données ou d’une méthode. La seule
exigence est que l’approche retourne un IDictionary<string, object> .

ComponentMetadata.cs :
C#

namespace BlazorSample;

public class ComponentMetadata


{
public string? Name { get; set; }
public Dictionary<string, object> Parameters { get; set; } = [];
}

Le composant RocketLabWithWindowSeat suivant ( RocketLabWithWindowSeat.razor ) a été


mis à jour à partir de l’exemple précédent pour inclure un paramètre de composant
nommé WindowSeat pour spécifier si le passager préfère un siège côté fenêtre sur son
vol :

RocketLabWithWindowSeat.razor :

razor

<h2>Rocket Lab®</h2>

<p>
User selected a window seat: @WindowSeat
</p>

<p>
Rocket Lab is a trademark of
<a href="https://www.rocketlabusa.com/">Rocket Lab USA Inc.</a>
</p>

@code {
[Parameter]
public bool WindowSeat { get; set; }
}

Dans l’exemple suivant :

Seul le paramètre du composant RocketLabWithWindowSeat pour un siège côté


fenêtre ( WindowSeat ) reçoit la valeur de la case Window Seat .
L’espace de noms de l’application est BlazorSample . Modifier l’espace de noms
pour qu’il corresponde à l’espace de noms de votre application.
Les composants rendus dynamiquement sont des composants partagés :
Illustré dans cette section de l’article : RocketLabWithWindowSeat
( RocketLabWithWindowSeat.razor )
Composants présentés dans la section Exemple plus haut dans cet article :
SpaceX ( SpaceX.razor )
UnitedLaunchAlliance ( UnitedLaunchAlliance.razor )
VirginGalactic ( VirginGalactic.razor )

DynamicComponent2.razor :

razor

@page "/dynamic-component-2"

<PageTitle>Dynamic Component 2</PageTitle>

<h1>Dynamic Component Example 2</h1>

<p>
<label>
<input type="checkbox" @bind="WindowSeat" />
Window Seat (Rocket Lab only)
</label>
</p>

<p>
<label>
Select your transport:
<select @onchange="OnDropdownChange">
<option value="">Select a value</option>
@foreach (var c in components)
{
<option value="@c.Key">@c.Value.Name</option>
}
</select>
</label>
</p>

@if (selectedType is not null)


{
<div class="border border-primary my-1 p-1">
<DynamicComponent Type="@selectedType"
Parameters="@components[selectedType.Name].Parameters" />
</div>
}

@code {
private Dictionary<string, ComponentMetadata> components =
new()
{
{
"RocketLabWithWindowSeat",
new ComponentMetadata
{
Name = "Rocket Lab with Window Seat",
Parameters = new() { { "WindowSeat", false } }
}
},
{
"VirginGalactic",
new ComponentMetadata { Name = "Virgin Galactic" }
},
{
"UnitedLaunchAlliance",
new ComponentMetadata { Name = "ULA" }
},
{
"SpaceX",
new ComponentMetadata { Name = "SpaceX" }
}
};
private Type? selectedType;
private bool windowSeat;

private bool WindowSeat


{
get { return windowSeat; }
set
{
windowSeat = value;

components[nameof(RocketLabWithWindowSeat)].Parameters["WindowSeat"] =
windowSeat;
}
}

private void OnDropdownChange(ChangeEventArgs e)


{
/*
IMPORTANT!
Change "BlazorSample.Components" to match
your shared component's namespace in the Type.GetType()
argument.
*/
selectedType = e.Value?.ToString()?.Length > 0 ?
Type.GetType($"BlazorSample.Components.{e.Value}") : null;
}
}

Rappels d’événements ( EventCallback )


Les rappels d’événements (EventCallback) peuvent être passés à un DynamicComponent
dans son dictionnaire de paramètres.

ComponentMetadata.cs :

C#
namespace BlazorSample;

public class ComponentMetadata


{
public string? Name { get; set; }
public Dictionary<string, object> Parameters { get; set; } = [];
}

Implémentez un paramètre de rappel d’événement (EventCallback) dans chaque


composant rendu dynamiquement.

RocketLab2.razor :

razor

<h2>Rocket Lab®</h2>

<p>
Rocket Lab is a registered trademark of
<a href="https://www.rocketlabusa.com/">Rocket Lab USA Inc.</a>
</p>

<button @onclick="OnClickCallback">
Trigger a Parent component method
</button>

@code {
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

SpaceX2.razor :

razor

<h2>SpaceX®</h2>

<p>
SpaceX is a registered trademark of
<a href="https://www.spacex.com/">Space Exploration Technologies Corp.
</a>
</p>

<button @onclick="OnClickCallback">
Trigger a Parent component method
</button>

@code {
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

UnitedLaunchAlliance2.razor :

razor

<h2>United Launch Alliance®</h2>

<p>
United Launch Alliance and ULA are registered trademarks of
<a href="https://www.ulalaunch.com/">United Launch Alliance, LLC</a>.
</p>

<button @onclick="OnClickCallback">
Trigger a Parent component method
</button>

@code {
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

VirginGalactic2.razor :

razor

<h2>Virgin Galactic®</h2>

<p>
Virgin Galactic is a registered trademark of
<a href="https://www.virgingalactic.com/">Galactic Enterprises, LLC</a>.
</p>

<button @onclick="OnClickCallback">
Trigger a Parent component method
</button>

@code {
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

Dans l’exemple de composant parent suivant, la méthode ShowDTMessage affecte une


chaîne avec l’heure actuelle à message , et la valeur de message est rendue.

Le composant parent transmet la méthode de rappel, ShowDTMessage , dans le


dictionnaire de paramètres :
La clé string est le nom de la méthode de rappel, OnClickCallback .
La valeur object est créée par EventCallbackFactory.Create pour la méthode de
rappel parent, ShowDTMessage . Notez que le mot clé this n’est pas pris en charge
dans les champs C#. Par conséquent, une propriété C# est utilisée pour le
dictionnaire de paramètres.

) Important

Pour le composant suivant, modifiez le code dans la méthode OnDropdownChange .


Modifiez le nom de l’espace de noms de « BlazorSample » dans l’argument
Type.GetType() pour qu’il corresponde à l’espace de noms de votre application.

DynamicComponent3.razor :

razor

@page "/dynamic-component-3"

<PageTitle>Dynamic Component 3</PageTitle>

<h1>Dynamic Component Example 3</h1>

<p>
<label>
Select your transport:
<select @onchange="OnDropdownChange">
<option value="">Select a value</option>
<option value="@nameof(RocketLab2)">Rocket Lab</option>
<option value="@nameof(SpaceX2)">SpaceX</option>
<option value="@nameof(UnitedLaunchAlliance2)">ULA</option>
<option value="@nameof(VirginGalactic2)">Virgin
Galactic</option>
</select>
</label>
</p>

@if (selectedType is not null)


{
<div class="border border-primary my-1 p-1">
<DynamicComponent Type="@selectedType"
Parameters="@Components[selectedType.Name].Parameters" />
</div>
}

<p>
@message
</p>

@code {
private Type? selectedType;
private string? message;

private Dictionary<string, ComponentMetadata> Components


{
get
{
return new Dictionary<string, ComponentMetadata>()
{
{
"RocketLab2",
new ComponentMetadata
{
Name = "Rocket Lab",
Parameters =
new()
{
{
"OnClickCallback",

EventCallback.Factory.Create<MouseEventArgs>(
this, ShowDTMessage)
}
}
}
},
{
"VirginGalactic2",
new ComponentMetadata
{
Name = "Virgin Galactic",
Parameters =
new()
{
{
"OnClickCallback",

EventCallback.Factory.Create<MouseEventArgs>(
this, ShowDTMessage)
}
}
}
},
{
"UnitedLaunchAlliance2",
new ComponentMetadata
{
Name = "ULA",
Parameters =
new()
{
{
"OnClickCallback",

EventCallback.Factory.Create<MouseEventArgs>(
this, ShowDTMessage)
}
}
}
},
{
"SpaceX2",
new ComponentMetadata
{
Name = "SpaceX",
Parameters =
new()
{
{
"OnClickCallback",

EventCallback.Factory.Create<MouseEventArgs>(
this, ShowDTMessage)
}
}
}
}
};
}
}

private void OnDropdownChange(ChangeEventArgs e)


{
/*
IMPORTANT!
Change "BlazorSample.Components" to match
your shared component's namespace in the Type.GetType()
argument.
*/
selectedType = e.Value?.ToString()?.Length > 0 ?
Type.GetType($"BlazorSample.Components.{e.Value}") : null;
}

private void ShowDTMessage(MouseEventArgs e) =>


message = $"The current DT is: {DateTime.Now}.";
}

Éviter les paramètres fourre-tout


Évitez l’utilisation de paramètres fourre-tout. Si des paramètres fourre-tout sont utilisés,
chaque paramètre explicite sur DynamicComponent est effectivement un mot réservé
que vous ne pouvez pas passer à un enfant dynamique. Tous les nouveaux paramètres
passés à DynamicComponent sont un changement cassant, car ils commencent à
remplacer les paramètres de composant enfant qui portent le même nom. Il est peu
probable que l’appelant connaisse toujours un ensemble fixe de noms de paramètres à
passer à tous les enfants dynamiques possibles.

Marques déposées
Rocket Lab est une marque déposée de Rocket Lab USA Inc. SpaceX est une marque
déposée de Space Exploration Technologies Corp. United Launch Alliance et ULA sont
des marques déposées de United Launch Alliance, LLC . Virgin Galactic est une marque
déposée de Galactic Enterprises, LLC .

Ressources supplémentaires
Gestion des événements ASP.NET Core Blazor
DynamicComponent

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
composant ASP.NET Core
Blazor QuickGrid
Article • 09/02/2024

Le composant QuickGrid est un composant Razor permettant d’afficher rapidement et


efficacement les données sous forme tabulaire. QuickGrid fournit un composant de
grille de données simple et pratique pour les scénarios d’affichage de grille courants et
sert d’architecture de référence et de base de performances pour la création de
composants de grille de données. QuickGrid est hautement optimisé et utilise des
techniques avancées pour obtenir des performances d’affichage optimales.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du dépôt GitHub d’exemples
Blazor qui correspond à la version de .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Package
Ajoutez une référence de package pour le package
Microsoft.AspNetCore.Components.QuickGrid .

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Exemple d’application
Pour différentes démonstrations QuickGrid , consultez l’application QuickGrid Blazor un
exemple d’application . Le site de démonstration est hébergé sur GitHub Pages. Le site
se charge rapidement grâce au pré-rendu statique utilisant le
BlazorWasmPrerendering.Buildprojet GitHub géré par la communauté.

Implémentation QuickGrid
Pour implémenter un composant QuickGrid :

Spécifiez des balises pour le composant QuickGrid dans le balisage Razor


( <QuickGrid>...</QuickGrid> ).
Nommez une source interrogeable de données pour la grille. Utilisez l’une des
sources de données suivantes :
Items : IQueryable<TGridItem> nullable, où TGridItem est le type de données

représentées par chaque ligne de la grille.


ItemsProvider : rappel qui fournit des données pour la grille.
Class : nom de classe CSS facultatif. S’il est fourni, le nom de la classe est inclus

dans l’attribut class de la table affichée.


Theme : nom de thème (valeur par défaut : default ). Cela affecte les règles de style

qui correspondent à la table.


Virtualize : si la valeur est true, la grille est affichée avec la virtualisation. Cela est

habituellement utilisé conjointement avec le défilement et fait en sorte que la grille


n’extrait et n’affiche que les données autour de la fenêtre d’affichage de
défilement actuelle. Cela peut considérablement améliorer les performances lors
du défilement de jeux de données volumineux. Si vous utilisez Virtualize , vous
devez fournir une valeur pour ItemSize et devez vous assurer que chaque ligne
s’affiche avec une hauteur constante. En règle générale, il est préférable de ne pas
utiliser Virtualize si la quantité de données affichées est petite ou si vous utilisez
la pagination.
ItemSize : applicable uniquement lors de l’utilisation de Virtualize . ItemSize

définit une hauteur attendue en pixels pour chaque ligne, ce qui permet au
mécanisme de virtualisation d’extraire le nombre correct d’éléments correspondant
à la taille d’affichage et de garantir un défilement précis.
ItemKey : définit éventuellement une valeur pour @key sur chaque ligne affichée. En
règle générale, il est utilisé pour spécifier un identificateur unique, tel qu’une
valeur de clé primaire, pour chaque élément de données. Cela permet à la grille de
conserver l’association entre les éléments de ligne et les éléments de données en
fonction de leurs identificateurs uniques, même lorsque les instances TGridItem
sont remplacées par de nouvelles copies (par exemple, après une nouvelle requête
sur le magasin de données sous-jacent). S’il n’est pas défini, @key est l’instance
TGridItem .
Pagination : lie éventuellement cette instance TGridItem à un modèle

PaginationState , faisant en sorte que la grille n’extrait et n’affiche que la page de

données active. Cela est habituellement utilisé conjointement avec un composant


Paginator ou une autre logique d’interface utilisateur qui affiche et met à jour

l’instance PaginationState fournie.


Dans le contenu enfant QuickGrid (RenderFragment), spécifiez PropertyColumn s,
qui représentent des colonnes TGridItem dont les cellules affichent les valeurs :
Property : définit la valeur à afficher dans les cellules de cette colonne.

Format : spécifie éventuellement une chaîne de format pour la valeur. Utiliser

Format nécessite que le type TProp implémente IFormattable .


Sortable : indique si les données doivent être triables par cette colonne. La

valeur par défaut peut varier en fonction du type de colonne. Par exemple,
TemplateColumn<TGridItem> est triable par défaut si un paramètre
TemplateColumn<TGridItem>.SortBy est spécifié.

InitialSortDirection : indique le sens de tri si IsDefaultSortColumn est true .

IsDefaultSortColumn : indique si cette colonne doit être triée par défaut.


PlaceholderTemplate : si elle est spécifiée, les grilles virtualisées utilisent ce

modèle pour afficher les cellules dont les données n’ont pas été chargées.

Par exemple, ajoutez le composant suivant pour afficher une grille.

Le composant suppose que le mode de rendu Serveur interactif ( InteractiveServer ) est


hérité d’un composant parent ou appliqué globalement à l’application, ce qui active les
fonctionnalités interactives. Dans l’exemple suivant, la seule fonctionnalité interactive est
la possibilité de trier les colonnes.

PromotionGrid.razor :

razor

@page "/promotion-grid"
@using Microsoft.AspNetCore.Components.QuickGrid

<PageTitle>Promotion Grid</PageTitle>

<h1>Promotion Grid Example</h1>

<QuickGrid Items="@people">
<PropertyColumn Property="@(p => p.PersonId)" Sortable="true" />
<PropertyColumn Property="@(p => p.Name)" Sortable="true" />
<PropertyColumn Property="@(p => p.PromotionDate)" Format="yyyy-MM-dd"
Sortable="true" />
</QuickGrid>

@code {
private record Person(int PersonId, string Name, DateOnly
PromotionDate);

private IQueryable<Person> people = new[]


{
new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)),
new Person(10944, "António Langa", new DateOnly(1991, 12, 1)),
new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)),
new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)),
new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)),
new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)),
}.AsQueryable();
}
Pour obtenir un exemple utilisant IQueryable avec Entity Framework Core comme source
de données interrogeable, consultez le composant SampleQuickGridComponent dans
l’application de test de base ASP.NET Core (dépôt GitHub dotnet/aspnetcore) .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Pour utiliser Entity Framework (EF) Core comme source de données :

Ajouter une référence de package prerelease pour le package


Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter . Si vous
utilisez l’interface CLI .NET pour ajouter la référence du package, incluez l’option -
-prerelease lorsque vous exécutez la dotnet add packagecommande.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET,
consultez les articles figurant sous Installer et gérer des packages dans Flux de
travail de la consommation des packages (documentation NuGet). Vérifiez
les versions du package sur NuGet.org .

Appelez AddQuickGridEntityFrameworkAdapter sur la collection de services dans le


fichier Program pour inscrire une implémentation de IAsyncQueryExecutor prenant
en charge EF :

C#

builder.Services.AddQuickGridEntityFrameworkAdapter();

QuickGrid prend en charge le passage d’attributs personnalisés à l’élément de table


rendu :

razor
<QuickGrid Items="..." custom-attribute="somevalue" class="custom-class">

Accédez au composant dans un navigateur au chemin relatif /promotion-grid .

Il n’existe actuellement pas de plans d’extension de QuickGrid avec des fonctionnalités


que les grilles commerciales peuvent offrir, par exemple, des lignes hiérarchiques, des
colonnes à faire glisser pour réorganiser ou des sélections de plages de type Excel. Si
vous avez besoin de fonctionnalités avancées que vous ne souhaitez pas développer par
vous-même, continuez à utiliser des grilles tierces.

Générateur de modèles automatique QuickGrid


(préversion)

) Important

Le générateur de modèles automatique QuickGrid est en préversion. Le générateur


de modèles automatique n’est pas pris en charge pour une utilisation dans des
applications de production. Les fonctionnalités du générateur de modèles
automatique sont susceptibles de changer sans préavis.

Le générateur de modèles automatique QuickGrid livré avec Visual Studio Preview


génère des modèles composants de composants Razor avec QuickGrid pour afficher
des données provenant d’une base de données.

Pour utiliser le générateur de modèles automatique, cliquez avec le bouton droit sur le
projet dans l’Explorateur de solutions, puis sélectionnez Ajouter>Nouvel élément
généré automatiquement. Ouvrez Installé>Commun>Composant Razor. Sélectionnez
RazorComposants utilisant Entity Framework (CRUD).

Le générateur de modèles automatique génère des pages CRUD (Create, Read, Update
et Delete) simples basées sur un modèle de données Entity Framework Core. Vous
pouvez générer des pages individuelles ou toutes les pages CRUD. Vous sélectionnez la
classe de modèle et le DbContext , en créant éventuellement un nouveau DbContext si
nécessaire.

Les composants Razor générés sont ajoutés au dossier Pages du projet, dans un dossier
généré nommé d’après la classe de modèle. Le composant Index généré utilise
QuickGrid pour afficher les données. Personnalisez les composants générés en fonction
des besoins et activez l’interactivité pour tirer parti des fonctionnalités interactives,
comme le tri et le filtrage.

Les composants générés par le générateur de modèles automatique nécessitent un


rendu côté serveur (SSR) : ils ne sont donc pas pris en charge lors de l’exécution sur
WebAssembly.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Intégrer des composants Razor ASP.NET
Core aux applications ASP.NET Core
Article • 09/02/2024

Cet article explique les scénarios Razor d’intégration des composants pour les
applications ASP.NET Core.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : dans une application web Blazor, rendu côté serveur
interactif (SSR interactif) qui fonctionne sur une connexion SignalR avec le client ou
rendu côté serveur statique (SSR statique).
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un composant doit avoir un mode de rendu
interactif appliqué pour l’interactivité sur une connexion SignalR avec le client,
dans le fichier de définition du composant ou hérité d’un composant parent. Les
composants qui ne définissent pas de mode de rendu ou qui n’en héritent pas
sont rendus avec un SSR statique sur le serveur. Aucune connexion SignalR n’est
établie pour les composants rendus statiquement. Pour plus d’informations,
consultez Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du dépôt GitHub d’exemples
Blazor qui correspond à la version de .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Intégration de composants Razor


Les composants Razor peuvent être intégrés dans Razor Pages, MVC et d’autres types
d’applications ASP.NET Core. Razorles composants peuvent également être intégrés à
n’importe quelle application web, y compris les applications non basées sur ASP.NET
Core, en tant qu’éléments HTML personnalisés.

Procédez comme expliqué dans les sections suivantes en fonction des exigences du
projet :

La prise en charge de Blazor peut être ajoutée à une application ASP.NET Core.
Pour connaître les composants interactifs qui ne sont pas routables directement à
partir de requêtes utilisateur, consultez la section Utiliser des composants non
routables dans des pages ou des vues. Suivez cette aide quand l’application
intègre des composants dans les pages et vues existantes à l’aide de l’assistance
des balises de composant.
Pour connaître les composants interactifs qui sont directement routables à partir
de requêtes utilisateur, consultez la section Utiliser les composants routables.
Suivez cette aide lorsque les visiteurs doivent être en mesure d’envoyer une
requête HTTP dans leur navigateur pour un composant avec une directive @page.

Ajouter la prise en charge de Blazor à une


application ASP.NET Core
Cette section traite de l’ajout de la prise en charge de Blazor à une application ASP.NET
Core :

Ajouter un rendu statique côté serveur (SSR statique)


Activer le rendu interactif côté serveur (SSR interactif)
Activer le rendu interactif (automatique) ou côté client (CSR)
7 Notes

Pour les exemples de cette section, le nom et l’espace de noms de l’exemple


d’application est BlazorSample .

Ajouter un rendu statique côté serveur (SSR statique)


Ajoutez un dossier Components à l’application.

Ajoutez le fichier _Imports suivant pour les espaces de noms utilisés par les composants
Razor.

Components/_Imports.razor :

razor

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using {APP NAMESPACE}
@using {APP NAMESPACE}.Components

Remplacez l’espace réservé ( {APP NAMESPACE} ) de l’espace de noms par l’espace de


noms de l’application. Par exemple :

razor

@using BlazorSample
@using BlazorSample.Components

Ajoutez le routeur Blazor ( <Router> , Router) à l’application dans un composant Routes ,


qui est placé dans le dossier Components de l’application.

Components/Routes.razor :

razor

<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>

Vous pouvez fournir une disposition par défaut avec le paramètre


RouteView.DefaultLayout du composant RouteView :

razor

<RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)" />

Pour plus d’informations, consultez layouts ASP.NET Core Blazor.

Ajoutez un composant App à l’application, qui sert de composant racine, c’est-à-dire le


premier composant que l’application charge.

Components/App.razor :

razor

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="BlazorSample.styles.css" />
<HeadOutlet />
</head>

<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
</body>

</html>

Pour l’élément <link> dans l’exemple précédent, changez BlazorSample dans le nom de
fichier de la feuille de style pour qu’il corresponde au nom du projet de l’application. Par
exemple, un projet nommé ContosoApp utilise le nom de fichier de feuille de style
ContosoApp.styles.css :

HTML

<link rel="stylesheet" href="ContosoApp.styles.css" />


Ajoutez un dossier Pages au dossier Components pour contenir les composants Razor
routables.

Ajoutez le composant Welcome suivant pour illustrer le SSR statique.

Components/Pages/Welcome.razor :

razor

@page "/welcome"

<PageTitle>Welcome!</PageTitle>

<h1>Welcome to Blazor!</h1>

<p>@message</p>

@code {
private string message =
"Hello from a Razor component and welcome to Blazor!";
}

Dans le fichier Program du projet ASP.NET Core :

Ajoutez une instruction using en haut du fichier pour les composants du projet :

C#

using {APP NAMESPACE}.Components;

Dans la ligne précédente, remplacez l’espace réservé {APP NAMESPACE} par l’espace
de noms de l’application. Par exemple :

C#

using BlazorSample.Components;

Ajouter des services de composants Razor (AddRazorComponents). Ajoutez la


ligne suivante avant celle qui appelle builder.Build() :

C#

builder.Services.AddRazorComponents();
Ajoutez middleware Antiforgery au pipeline de traitement des demandes après
l’appel à UseRouting . S’il y a des appels vers UseRouting et UseEndpoints , l’appel
vers UseAntiforgery doit passer entre eux. Un appel à UseAntiforgery doit être
placer après les appels à UseAuthentication et UseAuthorization .

C#

app.UseAntiforgery();

Ajoutez MapRazorComponents au pipeline de traitement des requêtes de


l’application avec le composant App ( App.razor ) spécifié comme composant racine
par défaut (le premier composant chargé). Placez le code suivant avant la ligne qui
appelle app.Run :

C#

app.MapRazorComponents<App>();

Lorsque l’application est exécutée, le composant Welcome est accessible au point de


terminaison /welcome .

Activer le rendu interactif côté serveur (SSR interactif)


Suivez les instructions de la section Ajouter le rendu statique côté serveur (SSR statique)
.

Dans le fichier Program de l’application, ajoutez un appel à


AddInteractiveServerComponents là où les services de composant Razor sont ajoutés
avec AddRazorComponents :

C#

builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();

Ajoutez un appel à AddInteractiveServerRenderMode là où les composants Razor sont


mappés avec MapRazorComponents :

C#

app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
Ajoutez le composant Counter suivant à l’application qui adopte le rendu interactif côté
serveur (SSR interactif).

Components/Pages/Counter.razor :

razor

@page "/counter"
@rendermode InteractiveServer

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Lorsque l’application est exécutée, le composant Counter est accessible à /counter .

Activer le rendu interactif (automatique) ou côté client


(CSR)
Suivez les instructions de la section Ajouter le rendu statique côté serveur (SSR statique)
.

Les composants utilisant le mode de rendu Automatique interactif utilisent initialement


le SSR interactif, mais basculent ensuite vers le rendu côté client une fois le pack Blazor
téléchargé et le runtime Blazor activé. Les composants utilisant le mode de rendu
WebAssembly interactif s’affichent uniquement de manière interactive sur le client une
fois le bundle Blazor téléchargé et le runtime Blazor activé. N’oubliez pas que lors de
l’utilisation du mode de rendu Automatique interactif ou WebAssembly interactif, le
code de composant téléchargé sur le client n’est pas privé. Pour plus d’informations,
consultez Modes de rendu ASP.NET Core Blazor.

Après avoir choisi le mode de rendu à adopter :


Si vous envisagez d’adopter le mode de rendu Automatique interactif, suivez les
instructions de la section Activer le rendu côté serveur interactif (SSR interactif).
Si vous envisagez d’adopter uniquement le rendu de WebAssembly interactif,
continuez sans ajouter le SSR interactif.

Ajoutez une référence de package pour le package NuGet


Microsoft.AspNetCore.Components.WebAssembly.Server à l’application.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Créez une application web Blazor donatrice pour fournir des ressources à l’application.
Suivez les instructions de l’article Outils pour ASP.NET Core Blazor, en sélectionnant la
prise en charge des fonctionnalités de modèle suivantes lors de la génération de
l’application web Blazor.

Pour le nom de l’application, utilisez le même nom que l’application ASP.NET Core, afin
que le balisage de nom d’application dans les composants et le balisage des espaces de
noms dans le code correspondent. L’utilisation du même nom/espace de noms n’est pas
strictement nécessaire, car les espaces de noms peuvent être ajustés une fois que les
ressources ont été déplacées de l’application donatrice vers l’application ASP.NET Core.
Toutefois, faire correspondre les espaces de noms dès le début permet de gagner du
temps.

Visual Studio :

Pour Mode de rendu interactif, sélectionnez Automatique (Server et


WebAssembly).
Pour Emplacement d’interactivité, sélectionnez Par page/composant.
Décochez la case Inclure des exemples de pages.

Interface CLI .NET :

Utilisez l'option -int Auto .


N’utilisez pas l’option -ai|--all-interactive .
Passez l’option -e|--empty .

À partir de l’application web Blazor donatrice, copiez l’intégralité du projet .Client dans
le dossier solution de l’application ASP.NET Core.
) Important

Ne copiez pas le dossier .Client dans le dossier du projet ASP.NET Core. La


meilleure approche pour organiser des solutions .NET consiste à placer chaque
projet de la solution dans son propre dossier à l’intérieur d’un dossier de solution
de niveau supérieur. S’il n’existe pas de dossier de solution au-dessus du dossier du
projet ASP.NET Core, créez-en un. Ensuite, copiez le dossier du projet .Client de
l’application web Blazor donatrice et collez-le dans le dossier solution. La structure
finale des dossiers de projets doit être la suivante :

BlazorSampleSolution (dossier de solution de niveau supérieur)

BlazorSample (projet ASP.NET Core d’origine)

BlazorSample.Client ( .Client dossier de projet à partir de l’application

web Blazor donatrice)

Vous pouvez laisser le fichier de solution ASP.NET Core dans le dossier du projet
ASP.NET Core. Vous pouvez également déplacer le fichier de solution ou en créer
un dans le dossier de solution de niveau supérieur tant que le projet référence
correctement les fichiers projet ( .csproj ) des deux projets dans le dossier de
solution.

Si, lorsque vous avez créé le projet donateur, vous avez donné à l’application web Blazor
donatrice un nom identique à celui de l’application ASP.NET Core, les espaces de noms
utilisés par les ressources données correspondent à ceux de l’application ASP.NET Core.
Vous n’avez pas besoin d’effectuer d’autres étapes pour faire correspondre les espaces
de noms. Si vous avez utilisé un autre espace de noms lors de la création du projet
d’application web Blazor donatrice, vous devez ajuster les espaces de noms parmi les
ressources données afin qu’ils correspondent si vous envisagez de suivre le reste de ces
instructions exactement comme indiqué. Si les espaces de noms ne correspondent pas,
vous devez soit les ajuster avant de continuer, soit les ajuster à mesure que vous suivez
les instructions restantes de cette section.

Supprimez l’application web Blazor donatrice, car elle n’a plus aucune utilité dans ce
processus.

Ajoutez le projet .Client à la solution :

Visual Studio : Cliquez avec le bouton droit sur la solution dans l’Explorateur de
solutions et sélectionnez Ajouter>Projet existant. Accédez au dossier .Client et
sélectionnez le fichier projet ( .csproj ).
Interface CLI .NET : Utilisez la commande dotnet sln add pour ajouter le projet
.Client à la solution.

Ajoutez une référence de projet à partir du projet ASP.NET Core au projet client :

Visual Studio : Cliquez avec le bouton droit sur le projet ASP.NET Core et
sélectionnez Ajouter>Référence de projet. Sélectionnez le projet .Client et
sélectionnez OK.

Interface CLI .NET : À partir du dossier du projet ASP.NET Core, exécutez la


commande suivante :

CLI .NET

dotnet add reference ../BlazorSample.Client/BlazorSample.Client.csproj

La commande précédente part du principe que :


Le nom du fichier projet est BlazorSample.Client.csproj .
Le projet .Client se trouve dans un dossier BlazorSample.Client dans le
dossier de la solution. Le dossier .Client est côte à côte avec le dossier du
projet ASP.NET Core.

Pour plus d’informations sur la commande dotnet add reference , consultez dotnet
add reference (documentation .NET).

Apportez les modifications suivantes au fichier Program de l’application ASP.NET Core :

Ajoutez des services de composants WebAssembly interactifs avec


AddInteractiveWebAssemblyComponents, où les services de composants Razor
sont ajoutés avec AddRazorComponents.

Pour le rendu Auto interactif :

C#

builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();

Pour le rendu WebAssembly interactif uniquement :

C#

builder.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents();

Ajoutez le mode de rendu WebAssembly interactif


(AddInteractiveWebAssemblyRenderMode) et les assemblys supplémentaires pour
le projet .Client où les composants Razor sont mappés avec
MapRazorComponents.

Pour le rendu automatique (Auto) interactif :

C#

app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()

.AddAdditionalAssemblies(typeof(BlazorSample.Client._Imports).Assembly)
;

Pour le rendu WebAssembly interactif uniquement :

C#

app.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode()

.AddAdditionalAssemblies(typeof(BlazorSample.Client._Imports).Assembly)
;

Dans les exemples précédents, changez BlazorSample.Client afin qu’il


corresponde à l’espace de noms du projet .Client .

Ajoutez un dossier Pages au projet .Client .

Si le projet ASP.NET Core a un composant Counter existant :

Déplacez le composant vers le dossier Pages du projet .Client .


Supprimez la directive @rendermode en haut du fichier de composant.

Si l’application ASP.NET Core n’a pas de composant Counter , ajoutez le composant


Counter suivant ( Pages/Counter.razor ) au projet .Client :

razor

@page "/counter"
@rendermode InteractiveAuto
<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Si l’application adopte uniquement le rendu WebAssembly interactif, supprimez la


directive @rendermode et la valeur :

diff

- @rendermode InteractiveAuto

Exécutez la solution à partir du projet d’application ASP.NET Core :

Visual Studio : Vérifiez que le projet ASP.NET Core est sélectionné dans
l’Explorateur de solutions lors de l’exécution de l’application.

Interface CLI .NET : Exécutez le projet à partir du dossier du projet ASP.NET Core.

Pour charger le composant Counter , accédez à /counter .

Utiliser des composants non routables dans des


pages ou des vues
Utilisez l’aide suivante pour intégrer des composants Razor à des pages et des vues
d’une application Razor Pages ou MVC existante à l’aide de Tag Helper de composant.

Lorsque le pré-rendu du serveur est utilisé et que la page ou la vue s’affiche :

Le composant est prérendu avec la page ou la vue.


L’état initial du composant utilisé pour le prérendu est perdu.
Un nouvel état de composant est créé lorsque la connexion SignalR est établie.
Pour plus d’informations sur les modes de rendu, y compris le rendu de composants
statiques non interactifs, consultez Tag Helper de composant dans ASP.NET Core. Pour
enregistrer l’état des composants Razor prédéfinis, consultez Conserver le Tag Helper
d’état du composant dans ASP.NET Core.

Ajoutez un dossier Components au dossier racine du projet.

Ajoutez un fichier d’importation au dossier Components avec le contenu suivant.


Remplacez l’espace réservé {APP NAMESPACE} par l’espace de noms du projet.

Components/_Imports.razor :

razor

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using {APP NAMESPACE}
@using {APP NAMESPACE}.Components

Dans le fichier de disposition du projet ( Pages/Shared/_Layout.cshtml dans les


applications Razor Pages ou Views/Shared/_Layout.cshtml dans les applications MVC) :

Ajoutez la balise <base> et le Tag Helper de composant suivants pour un


composant HeadOutlet au balisage <head> :

CSHTML

<base href="~/" />


<component
type="typeof(Microsoft.AspNetCore.Components.Web.HeadOutlet)"
render-mode="ServerPrerendered" />

La valeur href (le chemin d’accès de base de l’application) de l’exemple précédent


suppose que l’application réside dans le chemin d’accès de l’URL racine ( / ). Si
l’application est une sous-application, suivez l’aide de la section Chemin d’accès de
base de l’application de l’article Héberger et déployer ASP.NET CoreBlazor.

Le composant HeadOutlet est utilisé pour afficher le contenu de l’en-tête ( <head> )


pour les titres de page (composant PageTitle) et d’autres éléments de l’en-tête
(composant HeadContent) définis par les composants Razor. Pour plus
d’informations, consultez Contrôle du contenu des en-têtes dans les applications
ASP.NET Core Blazor.

Ajoutez une balise <script> au script blazor.web.js immédiatement avant la


section de rendu Scripts ( @await RenderSectionAsync(...) ) :

HTML

<script src="_framework/blazor.web.js"></script>

Il n’est pas nécessaire d’ajouter manuellement un script blazor.web.js à


l’application, car l’infrastructure Blazor ajoute le script blazor.web.js à
l’application.

7 Notes

En général, la disposition se charge via un fichier _ViewStart.cshtml .

Ajoutez un composant non opérationnel (no-op) App au projet.

Components/App.razor :

razor

@* No-op App component *@

Lorsque les services sont inscrits, ajoutez des services pour les composants et services
Razor pour prendre en charge le rendu interactif des composants de serveur.

En haut du fichier Program , ajoutez une instruction using pour les composants du projet
:

C#

using {APP NAMESPACE}.Components;

Dans la ligne précédente, remplacez l’espace réservé {APP NAMESPACE} par l’espace de
noms de l’application. Par exemple :

C#
using BlazorSample.Components;

Dans le fichier Program avant la ligne qui génère l’application ( builder.Build() ) :

C#

builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();

Pour plus d’informations sur l’ajout de la prise en charge des composants serveur
interactif et WebAssembly, consultez Modes de rendu Blazor ASP.NET Core.

Dans le fichier Program , immédiatement après l’appel pour mapper les Razor Pages
(MapRazorPages) dans une application Razor Pages ou pour mapper la route du
contrôleur par défaut (MapControllerRoute) dans une application MVC, appelez
MapRazorComponents pour découvrir les composants disponibles et spécifiez le
composant racine de l’application (le premier composant chargé). Par défaut, le
composant racine de l’application est le composant App ( App.razor ). Enchaînez un
appel à AddInteractiveInteractiveServerRenderMode pour configurer le rendu côté
serveur interactif (SSR interactif) pour l’application :

C#

app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();

7 Notes

Si l’application n’a pas déjà été mise à jour pour inclure l’intergiciel Antiforgery,
ajoutez la ligne suivante après l’appel de UseAuthorization :

C#

app.UseAntiforgery();

Intégrez des composants à n’importe quelle page ou vue. Par exemple, ajoutez un
composant EmbeddedCounter au dossier du projet Components .

Components/EmbeddedCounter.razor :

razor
<h1>Embedded Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Razor Pages :

Dans la page Index du projet d’une application Razor Pages, ajoutez l’espace de noms
du composant EmbeddedCounter et incorporez le composant dans la page. Quand la
page Index se charge, le composant EmbeddedCounter est prérendu dans la page. Dans
l’exemple suivant, remplacez l’espace réservé {APP NAMESPACE} par l’espace de noms du
projet.

Pages/Index.cshtml :

CSHTML

@page
@using {APP NAMESPACE}.Components
@model IndexModel
@{
ViewData["Title"] = "Home page";
}

<component type="typeof(EmbeddedCounter)" render-mode="ServerPrerendered" />

MVC :

Dans la vue Index du projet d’une application MVC, ajoutez l’espace de noms du
composant EmbeddedCounter et incorporez le composant dans la vue. Quand la vue
Index se charge, le composant EmbeddedCounter est prérendu dans la page. Dans

l’exemple suivant, remplacez l’espace réservé {APP NAMESPACE} par l’espace de noms du
projet.

Views/Home/Index.cshtml :
CSHTML

@using {APP NAMESPACE}.Components


@{
ViewData["Title"] = "Home Page";
}

<component type="typeof(EmbeddedCounter)" render-mode="ServerPrerendered" />

Utiliser des composants routables


Utilisez les instructions suivantes pour intégrer des composants routables Razor dans
une application Razor Pages ou MVC existante.

Les conseils de cette section supposent :

Le titre de l’application est Blazor Sample .


L’espace de noms de l’application est BlazorSample .

Pour prendre en charge les composants routables Razor :

Ajoutez un dossier Components au dossier racine du projet.

Ajoutez un fichier d’importation au dossier Components avec le contenu suivant.

Components/_Imports.razor :

razor

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using {APP NAMESPACE}
@using {APP NAMESPACE}.Components

Remplacez l’espace réservé {APP NAMESPACE} par l’espace de noms du projet. Par
exemple :

razor

@using BlazorSample
@using BlazorSample.Components

Ajoutez un dossier Layout au dossier Components .

Ajoutez un composant de pied de page et une feuille de style au dossier Layout .

Components/Layout/Footer.razor :

razor

<footer class="border-top footer text-muted">


<div class="container">
&copy; 2023 - {APP TITLE} - <a href="/privacy">Privacy</a>
</div>
</footer>

Dans le balisage précédent, définissez l’espace réservé {APP TITLE} sur le titre de
l’application. Par exemple :

HTML

&copy; 2023 - Blazor Sample - <a href="/privacy">Privacy</a>

Components/Layout/Footer.razor.css :

css

.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
}

Ajoutez un composant de menu de navigation au dossier Layout .

Components/Layout/NavMenu.razor :

razor

<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-


white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" href="/">{APP TITLE}</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-
between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" href="/privacy">Privacy</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" href="/counter">Counter</a>
</li>
</ul>
</div>
</div>
</nav>

Dans le balisage précédent, définissez l’espace réservé {APP TITLE} sur le titre de
l’application. Par exemple :

HTML

<a class="navbar-brand" href="/">Blazor Sample</a>

Components/Layout/NavMenu.razor.css :

css

a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}

a {
color: #0077cc;
}

.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}

.nav-pills .nav-link.active, .nav-pills .show > .nav-link {


color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}

.border-top {
border-top: 1px solid #e5e5e5;
}

.border-bottom {
border-bottom: 1px solid #e5e5e5;
}

.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}

button.accept-policy {
font-size: 1rem;
line-height: inherit;
}

Ajoutez un composant de disposition principal et une feuille de style au dossier Layout .

Components/Layout/MainLayout.razor :

razor

@inherits LayoutComponentBase

<header>
<NavMenu />
</header>

<div class="container">
<main role="main" class="pb-3">
@Body
</main>
</div>

<Footer />

<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

Components/Layout/MainLayout.razor.css :

css

#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}

#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

Ajoutez un composant Routes au dossier Components avec le contenu suivant.

Components/Routes.razor :

razor

<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData"
DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>

Ajoutez un composant App au dossier Components avec le contenu suivant.

Components/App.razor :

razor

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{APP TITLE}</title>
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.min.css"
/>
<link rel="stylesheet" href="/css/site.css" />
<link rel="stylesheet" href="/{APP NAMESPACE}.styles.css" />
<HeadOutlet />
</head>
<body>
<Routes />
<script src="/lib/jquery/dist/jquery.min.js"></script>
<script src="/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="/js/site.js"></script>
<script src="_framework/blazor.web.js"></script>
</body>
</html>

Dans le code précédent, mettez à jour le titre de l’application et le nom du fichier feuille
de style :

Pour l’espace réservé {APP TITLE} dans l’élément <title> , définissez le titre de
l’application. Par exemple :

HTML

<title>Blazor Sample</title>

Pour l’espace réservé {APP NAMESPACE} dans l’élément feuille de style <link> ,
définissez l’espace de noms de l’application. Par exemple :

HTML

<link rel="stylesheet" href="/BlazorSample.styles.css" />

Lorsque les services sont inscrits, ajoutez des services pour les composants et services
Razor pour prendre en charge le rendu interactif des composants de serveur.

En haut du fichier Program , ajoutez une instruction using pour les composants du projet
:

C#

using {APP NAMESPACE}.Components;

Dans la ligne précédente, remplacez l’espace réservé {APP NAMESPACE} par l’espace de
noms de l’application. Par exemple :

C#

using BlazorSample.Components;

Dans le fichier Program avant la ligne qui génère l’application ( builder.Build() ) :

C#
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();

Pour plus d’informations sur l’ajout de la prise en charge des composants serveur
interactif et WebAssembly, consultez Modes de rendu Blazor ASP.NET Core.

Dans le fichier Program , immédiatement après l’appel à mapper Razor Pages


(MapRazorPages), appelez MapRazorComponents pour découvrir les composants
disponibles et spécifiez le composant racine de l’application. Par défaut, le composant
racine de l’application est le composant App ( App.razor ). Enchaînez un appel à
AddInteractiveServerRenderMode pour configurer le rendu côté serveur interactif (SSR
interactif) pour l’application :

C#

app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();

7 Notes

Si l’application n’a pas déjà été mise à jour pour inclure l’intergiciel Antiforgery,
ajoutez la ligne suivante après l’appel de UseAuthorization :

C#

app.UseAntiforgery();

Créez un dossier Pages dans le dossier Components pour les composants routables.
L’exemple suivant est un composant Counter basé sur le composant Counter des
modèles de projet Blazor.

Components/Pages/Counter.razor :

razor

@page "/counter"
@rendermode InteractiveServer

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p>Current count: @currentCount</p>


<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Exécutez le projet et accédez au composant Counter routable sur /counter .

Pour plus d’informations sur les espaces de noms, consultez la section Espaces de noms
de composant.

Retourne RazorComponentResult à partir d’une


action de contrôleur MVC
Une action de contrôleur MVC peut retourner un composant avec
RazorComponentResult<TComponent>.

Components/Welcome.razor :

razor

<PageTitle>Welcome!</PageTitle>

<h1>Welcome!</h1>

<p>@Message</p>

@code {
[Parameter]
public string? Message { get; set; }
}

Dans un contrôleur :

C#

public IResult GetWelcomeComponent()


{
return new RazorComponentResult<Welcome>(new { Message = "Hello, world!"
});
}
Seules les balises HTML du composant rendu sont retournées. Les dispositions et les
balises de page HTML ne sont pas automatiquement rendues avec le composant. Pour
produire une page HTML complète, l’application peut utiliser une disposition Blazor, qui
fournit des balises HTML pour <html> , <head> , <body> et d’autres balises. Le composant
inclut la disposition avec la directive @layoutRazor en haut du fichier de définition de
composant, Welcome.razor pour l’exemple de cette section. L’exemple suivant part du
principe que l’application a une disposition nommée RazorComponentResultLayout
( Components/Layout/RazorComponentResultLayout.razor ) :

razor

@using BlazorSample.Components.Layout
@layout RazorComponentResultLayout

Vous pouvez éviter de placer l’instruction @using du dossier Layout dans des
composants individuels en la déplaçant vers le fichier _Imports.razor de l’application.

Pour plus d’informations, consultez layouts ASP.NET Core Blazor.

Espaces de noms de composant


Lors de l’utilisation d’un dossier personnalisé pour contenir les composants Razor du
projet, ajoutez l’espace de noms représentant le dossier à la page/vue ou au fichier
_ViewImports.cshtml . Dans l’exemple suivant :

Les composants sont stockés dans le dossier Components du projet.


L’espace réservé {APP NAMESPACE} est l’espace de noms du projet. Components
représente le nom du dossier.

CSHTML

@using {APP NAMESPACE}.Components

Par exemple :

CSHTML

@using BlazorSample.Components

Le fichier _ViewImports.cshtml se trouve dans le dossier Pages d’une application Razor


Pages ou dans le dossier Views d’une application MVC.
Pour plus d’informations, consultez Composants ASP.NET Core Razor.

Ressources supplémentaires
Prérendu des composants Razor ASP.NET Core

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Consommer des composants ASP.NET
Core Razor à partir d'une bibliothèque
de classes Razor (RCL)
Article • 09/02/2024

Les composants peuvent être partagés dans une bibliothèque de classes Razor (RCL)
entre les projets. Incluez des composants et des ressources statiques dans une
application à partir de :

Un autre projet dans la solution.


Bibliothèque .NET référencée.
Un package NuGet.

Tout comme les composants sont des types .NET standard, les composants fournis par
un RCL sont des assemblys .NET normaux.

Créer une RCL


Visual Studio

1. Créer un nouveau projet.


2. Dans la boîte de dialogue Créer un projet, sélectionnez Bibliothèque de
classes Razor dans la liste des modèles de projet ASP.NET Core. Cliquez sur
Suivant.
3. Dans la boîte de dialogue Configurer votre nouveau projet, indiquez un nom
de projet dans le champ Nom du projet ou acceptez le nom de projet par
défaut. Les exemples de cette rubrique utilisent le nom de projet
ComponentLibrary . Cliquez sur Créer.

4. Dans la boîte de dialogue Créer une bibliothèque de classes Razor,


sélectionnez Créer.
5. Ajoutez la RCL à une solution :
a. Ouvrez la solution.
b. Cliquez avec le bouton droit sur la solution dans l’Explorateur de solutions.
Sélectionnez Ajouter>un projet existant.
c. Accédez au fichier projet de la RCL.
d. Sélectionnez le fichier projet de la RCL ( .csproj ).
6. Ajoutez une référence à la RCL à partir de l’application :
a. Faites un clic droit sur le projet d’application. Sélectionnez
Ajouter>Référence de projet.
b. Sélectionnez le projet RCL. Sélectionnez OK.

Si la case Prendre en charge les pages et les vues est cochée pour prendre en
charge les pages et les vues lors de la génération de la RCL à partir du modèle :

Ajoutez un fichier _Imports.razor à la racine du projet RCL généré avec le


contenu suivant pour permettre la création de composants Razor :

razor

@using Microsoft.AspNetCore.Components.Web

Ajoutez l’élément SupportedPlatform suivant au fichier projet ( .csproj ) :

XML

<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>

Pour plus d'informations sur l'élément SupportedPlatform , consultez la section


Analyseur de compatibilité du navigateur côté client.

Consommer un composant Razor à partir d’une


bibliothèque RCL
Pour utiliser des composants d’une RCL dans un autre projet, utilisez l’une des
approches suivantes :

Utilisez le nom de type de composant complet, qui inclut l’espace de noms de la


RCL.
Des composants individuels peuvent être ajoutés par nom sans l’espace de noms
de la RCL si la directive @using de Razor déclare l’espace de noms de la RCL.
Utilisez les approches suivantes :
Ajoutez la directive @using à des composants individuels.
Incluez la directive @using dans le fichier _Imports.razor de niveau supérieur
pour rendre les composants de la bibliothèque accessibles à l’ensemble d’un
projet. Ajoutez la directive à un fichier _Imports.razor à n’importe quel niveau
pour appliquer l’espace de noms à un composant unique ou à un ensemble de
composants au sein d’un dossier. Lorsqu’un fichier _Imports.razor est utilisé,
les composants individuels ne nécessitent pas de directive @using pour l’espace
de noms de la RCL.

Dans les exemples suivants, ComponentLibrary est une RCL contenant le composant
Component1 . Le composant Component1 est un exemple de composant automatiquement

ajouté à une RCL créée à partir du modèle de projet RCL qui n’est pas créé pour prendre
en charge les pages et les vues.

7 Notes

Si la RCL est créée pour prendre en charge les pages et les vues, ajoutez
manuellement le composant Component1 et ses ressources statiques à la RCL si vous
envisagez de suivre les exemples de cet article. Les composants et les ressources
statiques sont présentés dans cette section.

Component1.razor dans la RCL ComponentLibrary :

razor

<div class="my-component">
This component is defined in the <strong>ComponentLibrary</strong>
package.
</div>

Dans l’application qui consomme la RCL, référencez le composant Component1 à l’aide de


son espace de noms, comme l’illustre l’exemple suivant.

ConsumeComponent1.razor :

razor

@page "/consume-component-1"

<h1>Consume component (full namespace example)</h1>

<ComponentLibrary.Component1 />

Vous pouvez également ajouter une directive @using et utiliser le composant sans son
espace de noms. La directive @using suivante peut également apparaître dans n’importe
quel fichier _Imports.razor dans ou au-dessus du dossier actif.
ConsumeComponent2.razor :

razor

@page "/consume-component-2"
@using ComponentLibrary

<h1>Consume component (<code>@@using</code> example)</h1>

<Component1 />

Pour les composants de bibliothèque qui utilisent l’isolation du CSS, les styles de
composant sont automatiquement mis à la disposition de l’application consommatrice.
Il n’est pas nécessaire de lier ou d’importer manuellement les feuilles de style des
composants individuels de la bibliothèque ou son fichier CSS groupé dans l’application
qui consomme la bibliothèque. L’application utilise des importations CSS pour
référencer les styles groupés de la RCL. Les styles groupés ne sont pas publiés en tant
que ressource web statique de l’application qui consomme la bibliothèque. Pour une
bibliothèque de classes nommée ClassLib et une application Blazor avec une feuille de
style BlazorSample.styles.css , la feuille de style RCL est importée en haut de la feuille
de style de l’application automatiquement au moment de la génération :

css

@import '_content/ClassLib/ClassLib.bundle.scp.css';

Pour les exemples précédents, la feuille de style de Component1 ( Component1.razor.css )


est groupée automatiquement.

Component1.razor.css dans la RCL ComponentLibrary :

css

.my-component {
border: 2px dashed red;
padding: 1em;
margin: 1em 0;
background-image: url('background.png');
}

L’image d’arrière-plan est également incluse à partir du modèle de projet RCL et réside
dans le dossier wwwroot de la RCL.

wwwroot/background.png dans la RCL ComponentLibrary :


Pour fournir des styles de composants de bibliothèque supplémentaires à partir de
feuilles de style dans le dossier wwwroot de la bibliothèque, ajoutez des balises <link>
de feuille de style au consommateur de la RCL, comme le montre l’exemple suivant.

) Important

En règle générale, les composants de bibliothèque utilisent l’isolation CSS pour


regrouper et fournir des styles de composants. Les styles de composants qui
reposent sur l’isolation CSS sont automatiquement mis à la disposition de
l’application qui utilise la RCL. Il n’est pas nécessaire de lier ou d’importer
manuellement les feuilles de style des composants individuels de la bibliothèque
ou son fichier CSS groupé dans l’application qui consomme la bibliothèque.
L’exemple suivant permet de fournir des feuilles de style globales en dehors de
l’isolation CSS, ce qui n’est généralement pas une exigence pour les applications
classiques qui consomment des RCL.

L’image d’arrière-plan suivante est utilisée dans le prochain exemple. Si vous


implémentez l’exemple présenté dans cette section, cliquez avec le bouton droit sur
l’image pour l’enregistrer localement.

wwwroot/extra-background.png dans la RCL ComponentLibrary :

Ajoutez une nouvelle feuille de style à la RCL avec une classe extra-style .

wwwroot/additionalStyles.css dans la RCL ComponentLibrary :

css

.extra-style {
border: 2px dashed blue;
padding: 1em;
margin: 1em 0;
background-image: url('extra-background.png');
}

Ajoutez un composant à la RCL qui utilise la classe extra-style .

ExtraStyles.razor dans la RCL ComponentLibrary :


razor

<div class="extra-style">
<p>
This component is defined in the <strong>ComponentLibrary</strong>
package.
</p>
</div>

Ajoutez une page à l’application qui utilise le composant ExtraStyles de la RCL.

ConsumeComponent3.razor :

razor

@page "/consume-component-3"
@using ComponentLibrary

<h1>Consume component (<code>additionalStyles.css</code> example)</h1>

<ExtraStyles />

Lien vers la feuille de style de la bibliothèque dans le balisage <head> de l’application


(emplacement du contenu <head>).

HTML

<link href="_content/ComponentLibrary/additionalStyles.css" rel="stylesheet"


/>

Rendre les composants routables disponibles à


partir de la Bibliothèque de classes (RCL)
Pour rendre des composants routables dans la RCL disponibles pour des requêtes
directes, l’assembly de la RCL doit être divulgué au routeur de l’application.

Ouvrez le composant App de l’application ( App.razor ). Affectez une collection Assembly


au paramètre AdditionalAssemblies du composant Router pour inclure l’assembly de la
RCL. Dans l’exemple suivant, le composant ComponentLibrary.Component1 est utilisé pour
découvrir l’assembly de la RCL.

razor
AdditionalAssemblies="new[] { typeof(ComponentLibrary.Component1).Assembly
}"

Pour plus d’informations, consultez Routage et navigation ASP.NET Core Blazor.

Créer une RCL avec des ressources statiques


dans le dossier wwwroot
Les ressources statiques d’une RCL sont disponibles pour toute application qui
consomme la bibliothèque.

Placez les ressources statiques dans le dossier wwwroot de la RCL et référencez les
ressources statiques avec le chemin d’accès suivant dans l’application :
_content/{PACKAGE ID}/{PATH AND FILE NAME} . L’espace réservé {PACKAGE ID} est l’ID de

package de la bibliothèque. L’ID de package est défini par défaut sur le nom d’assembly
du projet si <PackageId> n’est pas spécifié dans le fichier projet. L’espace réservé {PATH
AND FILE NAME} correspond au chemin d’accès et au nom de fichier sous wwwroot . Ce

format de chemin d’accès est également utilisé dans l’application pour les ressources
statiques fournies par les packages NuGet ajoutés à la RCL.

L’exemple suivant illustre l’utilisation de ressources statiques de la RCL avec une RCL
nommée ComponentLibrary et une application Blazor qui utilise la RCL. L’application a
une référence de projet pour la RCL ComponentLibrary .

L’image Jeep® suivante est utilisée dans l’exemple de cette section. Si vous
implémentez l’exemple présenté dans cette section, cliquez avec le bouton droit sur
l’image pour l’enregistrer localement.

wwwroot/jeep-yj.png dans la RCL ComponentLibrary :


Ajoutez le composant JeepYJ suivant à la RCL.

JeepYJ.razor dans la RCL ComponentLibrary :

razor

<h3>ComponentLibrary.JeepYJ</h3>

<p>
<img alt="Jeep YJ&reg;" src="_content/ComponentLibrary/jeep-yj.png" />
</p>

Ajoutez le composant Jeep suivant à l’application qui consomme la RCL


ComponentLibrary . Le composant Jeep utilise :

L’image Jeep YJ® du dossier wwwroot de la RCL ComponentLibrary .


Composant JeepYJ de la RCL.

Jeep.razor :

razor

@page "/jeep"
@using ComponentLibrary

<div style="float:left;margin-right:10px">
<h3>Direct use</h3>

<p>
<img alt="Jeep YJ&reg;" src="_content/ComponentLibrary/jeep-yj.png"
/>
</p>
</div>

<JeepYJ />

<p>
<em>Jeep</em> and <em>Jeep YJ</em> are registered trademarks of
<a href="https://www.stellantis.com">FCA US LLC (Stellantis NV)</a>.
</p>

Composant Jeep rendu :


Pour plus d’informations, consultez Interface utilisateur Razor réutilisable dans les
bibliothèques de classes avec ASP.NET Core.

Créer une RCL avec des fichiers JavaScript


colocalisés avec des composants
La colocation de fichiers JavaScript (JS) pour les composants Razor constitue un moyen
pratique pour organiser des scripts dans une application.

Les composants Razor pour les applications Blazor colocalisent des fichiers JS en
utilisant l’extension .razor.js et sont adressables publiquement en tirant parti du
chemin d’accès au fichier dans le projet :

{PATH}/{COMPONENT}.{EXTENSION}.js

L’espace réservé {PATH} constitue le chemin d’accès au composant.


L’espace réservé {COMPONENT} constitue le composant.
L’espace réservé {EXTENSION} correspond à l’extension du composant ( razor ).

Quand l’application est publiée, l’infrastructure déplace automatiquement le script vers


la racine web. Les scripts sont déplacés vers bin/Release/{TARGET FRAMEWORK
MONIKER}/publish/wwwroot/{PATH}/Pages/{COMPONENT}.razor.js où les espaces réservés

sont :

{TARGET FRAMEWORK MONIKER} constitue le moniker de framework cible (TFM).

{PATH} constitue le chemin d’accès au composant.


{COMPONENT} constitue le nom de composant.
Aucune modification n’est requise pour l’URL relative du script, car Blazor se charge de
placer le fichier JS dans des ressources statiques publiées à votre place.

Cette section et les exemples suivants sont principalement axés sur l’explication de la
colocation de fichiers JS. Le premier exemple illustre un fichier colocalisé JS avec une
fonction ordinaire JS. Le deuxième exemple illustre l’utilisation d’un module pour
charger une fonction, ce qui correspond à l’approche recommandée pour la plupart des
applications de production. L’appel JS à partir de .NET est entièrement couvert dans les
fonctions JavaScript d’appel à partir de méthodes .NET dans ASP.NET CoreBlazor, où il
existe d’autres explications de l’API BlazorJS avec des exemples supplémentaires.
L’élimination des composants, qui est présente dans le deuxième exemple, est abordée
dans Cycle de vie des composants Razor ASP.NET Core .

Le composant JsCollocation1 suivant charge un script via un composant HeadContent


et appelle une fonction JS avec IJSRuntime.InvokeAsync. L’espace réservé {PATH}
constitue le chemin d’accès au composant.

) Important

Si vous utilisez le code suivant pour une démonstration dans une application de
test, remplacez l’espace réservé {PATH} par le chemin d’accès du composant
(exemple : Components/Pages dans .NET 8 ou ultérieur ou Pages dans .NET 7 ou
antérieur). Dans une application web Blazor (.NET 8 ou version ultérieure), le
composant nécessite un mode de rendu interactif appliqué globalement à
l’application ou à la définition du composant.

Ajoutez le script suivant après le script Blazor (emplacement du script de démarrage


Blazor) :

HTML

<script src="{PATH}/JsCollocation1.razor.js"></script>

Composant JsCollocation1 ( {PATH}/JsCollocation1.razor ) :

razor

@page "/js-collocation-1"
@inject IJSRuntime JS

<PageTitle>JS Collocation 1</PageTitle>

<h1>JS Collocation Example 1</h1>


<button @onclick="ShowPrompt">Call showPrompt1</button>

@if (!string.IsNullOrEmpty(result))
{
<p>
Hello @result!
</p>
}

@code {
private string? result;

public async void ShowPrompt()


{
result = await JS.InvokeAsync<string>(
"showPrompt1", "What's your name?");
StateHasChanged();
}
}

Le fichier colocalisé JS est placé en regard du fichier de composant JsCollocation1 avec


le nom de fichier JsCollocation1.razor.js . Dans le composant JsCollocation1 , le script
est référencé au niveau du chemin du dossier colocalisé. Dans l’exemple suivant, la
fonction showPrompt1 accepte le nom de l’utilisateur à partir d’un Window prompt() et
le retourne au composant JsCollocation1 pour l’affichage.

{PATH}/JsCollocation1.razor.js :

JavaScript

function showPrompt1(message) {
return prompt(message, 'Type your name here');
}

L’approche précédente n’est pas recommandée pour une utilisation générale dans des
applications de production, car elle pollue le client avec des fonctions globales. Une
meilleure approche pour les applications de production consiste à utiliser des modules
JS. Ces mêmes principes généraux s’appliquent au chargement d’un module JS à partir
d’un fichier JS colocalisé, tel qu’illustré dans l’exemple suivant.

La méthode OnAfterRenderAsync du composant JsCollocation2 suivant charge un


module JS dans module qui est un IJSObjectReference de la classe de composant.
module est utilisé pour appeler la fonction showPrompt2 . L’espace réservé {PATH}

constitue le chemin d’accès au composant.


) Important

Si vous utilisez le code suivant pour une démonstration dans une application de
test, remplacez l’espace réservé {PATH} par le chemin d’accès du composant. Dans
une application web Blazor (.NET 8 ou version ultérieure), le composant nécessite
un mode de rendu interactif appliqué globalement à l’application ou à la définition
du composant.

Composant JsCollocation2 ( {PATH}/JsCollocation2.razor ) :

razor

@page "/js-collocation-2"
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>JS Collocation 2</PageTitle>

<h1>JS Collocation Example 2</h1>

<button @onclick="ShowPrompt">Call showPrompt2</button>

@if (!string.IsNullOrEmpty(result))
{
<p>
Hello @result!
</p>
}

@code {
private IJSObjectReference? module;
private string? result;

protected async override Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
/*
Change the {PATH} placeholder in the next line to the path
of
the collocated JS file in the app. Examples:

./Components/Pages/JsCollocation2.razor.js (.NET 8 or later)


./Pages/JsCollocation2.razor.js (.NET 7 or earlier)
*/
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./{PATH}/JsCollocation2.razor.js");
}
}
public async void ShowPrompt()
{
if (module is not null)
{
result = await module.InvokeAsync<string>(
"showPrompt2", "What's your name?");
StateHasChanged();
}
}

async ValueTask IAsyncDisposable.DisposeAsync()


{
if (module is not null)
{
await module.DisposeAsync();
}
}
}

{PATH}/JsCollocation2.razor.js :

JavaScript

export function showPrompt2(message) {


return prompt(message, 'Type your name here');
}

Pour les scripts ou modules fournis par une bibliothèque de classes (RCL) Razor, le
chemin d’accès suivant est utilisé :

_content/{PACKAGE ID}/{PATH}/{COMPONENT}.{EXTENSION}.js

L’espace réservé {PACKAGE ID} est l’identificateur de package de la bibliothèque


RCL (ou le nom de la bibliothèque pour une bibliothèque de classes référencée par
l’application).
L’espace réservé {PATH} constitue le chemin d’accès au composant. Si un
composant Razor se trouve à la racine de la bibliothèque RCL, le segment de
chemin n’est pas inclus.
L’espace réservé {COMPONENT} constitue le nom de composant.
L’espace réservé {EXTENSION} correspond à l’extension du composant, razor ou
cshtml .

Dans l’exemple d’application Blazor suivant :

L’identificateur de package de la bibliothèque RCL est AppJS .


Les scripts d’un module sont chargés pour le composant JsCollocation3
( JsCollocation3.razor ).
Le composant JsCollocation3 figure dans le dossier Components/Pages de la
bibliothèque RCL.

C#

module = await JS.InvokeAsync<IJSObjectReference>("import",


"./_content/AppJS/Components/Pages/JsCollocation3.razor.js");

Analyseur de compatibilité du navigateur côté


client
Les applications côté client ciblent toute la surface de l’API .NET, mais toutes les API
.NET ne sont pas prises en charge sur WebAssembly en raison des contraintes du bac à
sable du navigateur. Les API non prises en charge lèvent
PlatformNotSupportedException lors de leur exécution sur WebAssembly. Un analyseur
de compatibilité de plateforme avertit le développeur quand l’application utilise des API
qui ne sont pas prises en charge par les plateformes cibles de l’application. Pour les
applications côté client, cela signifie vérifier que les API sont prises en charge dans les
navigateurs. L’annotation des API .NET Framework pour l’analyseur de compatibilité est
un processus constant. Par conséquent, toutes les API du framework .NET ne sont pas
actuellement annotées.

Les applications web Blazor qui activent des composants WebAssembly interactifs, les
applications Blazor WebAssembly et les projets RCL activent automatiquement les
vérifications de compatibilité du navigateur en ajoutant browser en tant que plateforme
prise en charge avec l’élément MSBuild SupportedPlatform . Les développeurs de
bibliothèques peuvent ajouter manuellement l’élément SupportedPlatform au fichier
projet d’une bibliothèque pour activer la fonctionnalité :

XML

<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>

Lors de la création d’une bibliothèque, indiquez qu’une API particulière n’est pas prise
en charge dans les navigateurs en spécifiant browser sur
UnsupportedOSPlatformAttribute :
C#

using System.Runtime.Versioning;

...

[UnsupportedOSPlatform("browser")]
private static string GetLoggingDirectory()
{
...
}

Pour plus d’informations, consultez Annoter les API comme non prises en charge sur des
plateformes spécifiques (dépôt GitHub dotnet/designs .

Isolation JavaScript dans les modules JavaScript


Blazor active l’isolation JavaScript dans les modules JavaScript standard. L’isolation
JavaScript offre les avantages suivants :

Le code JavaScript importé ne pollue plus l’espace de noms global.


Les consommateurs d’une bibliothèque et de composants ne sont pas tenus
d’importer le code JavaScript associé manuellement.

Pour plus d’informations, consultez Appeler des fonctions JavaScript à partir de


méthodes .NET dans ASP.NET Core Blazor.

Évitez de découper les méthodes .NET


disponibles pour JavaScript
La nouvelle liaison du runtime supprime les méthodes .NET disponibles pour JavaScript
de l’instance de classe, sauf si elles sont explicitement conservées. Pour plus
d’informations, consultez Appeler des fonctions JavaScript à partir de méthodes .NET
dans ASP.NET Core Blazor.

Générer, empaqueter et expédier à NuGet


Étant donné que les bibliothèques de classes Razor qui contiennent des composants
Razor sont des bibliothèques .NET standard, leur empaquetage et leur expédition à
NuGet ne sont pas différents de l’empaquetage et de l’expédition d’une bibliothèque à
NuGet. L’empaquetage est effectué à l’aide de la commande dotnet pack dans un
interpréteur de commandes :
CLI .NET

dotnet pack

Chargez le package dans NuGet à l’aide de la commande dotnet nuget push dans un
interpréteur de commandes.

Marques déposées
Jeep et Jeep YJ sont des marques déposées de FCA US LLC (Stellantis NV) .

Ressources supplémentaires
Interface utilisateur Razor réutilisable dans les bibliothèques de classes avec
ASP.NET Core
Utiliser les API ASP.NET Core dans une bibliothèque de classes
Ajouter un fichier de configuration de découpage en langage intermédiaire (IL)
XML à une bibliothèque
Prise en charge de l’isolation CSS avec les bibliothèques de classes Razor

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Bibliothèques de classes Razor (RCL)
ASP.NET Core avec rendu côté serveur
statique (SSR statique)
Article • 09/02/2024

Cet article fournit des conseils aux auteurs de bibliothèques de composants qui
envisagent de prendre en charge le rendu côté serveur statique (SSR statique).

Blazor encourage le développement d’un écosystème de bibliothèques de composants


open source et commerciales, officiellement appelées bibliothèques de classes Razor
(RCL). Les développeurs peuvent également créer des composants réutilisables pour les
partager en privé entre les applications de leur propre entreprise. Dans l’idéal, les
composants sont développés pour être compatibles avec autant de modèles
d’hébergement et de modes de rendu que possible. Le SSR statique introduit des
restrictions supplémentaires qui peuvent être plus difficiles à prendre en charge que les
modes de rendu interactifs.

Comprendre les fonctionnalités et les


restrictions du SSR statique
Le SSR statique est un mode dans lequel les composants s’exécutent lorsque le serveur
gère une requête HTTP entrante. Blazor restitue le composant au format HTML, qui est
inclus dans la réponse. Une fois la réponse envoyée, le composant côté serveur et l’état
du renderer sont abandonnés, ne laissant dans le navigateur que le code HTML.

L’avantage de ce mode est un hébergement plus économique et plus évolutif, car


aucune ressource de serveur continue n’est requise pour contenir l’état du composant,
aucune connexion continue ne doit être conservée entre le navigateur et le serveur, et
aucune charge utile WebAssembly n’est requise dans le navigateur.

Par défaut, tous les composants existants peuvent toujours être utilisés avec le SSR
statique. Toutefois, le prix à payer de ce mode est que les gestionnaires d’événements,
tels que @onclick †, ne peuvent pas être exécutés pour les raisons suivantes :

Il n’y a aucun code .NET dans le navigateur pour les exécuter.


Le serveur abandonne immédiatement tous les composants et tous les états du
renderer qui seraient nécessaires pour exécuter des gestionnaires d’événements ou
pour re-restituer les mêmes instances de composant.
†Exception faite du gestionnaire d’événements @onsubmit pour les formulaires, qui reste
fonctionnel, quel que soit le mode de rendu.

Cela équivaut à la façon dont les composants se comportent pendant le prérendu, avant
le démarrage d’un circuit Blazor ou d’un runtime Blazor WebAssembly.

Pour les composants dont le seul rôle consiste à produire du contenu DOM en lecture
seule, ces comportements pour le SSR statique sont tout à fait suffisants. Toutefois, les
auteurs de bibliothèques doivent réfléchir à l’approche à adopter lorsqu’ils incluent des
composants interactifs dans leurs bibliothèques.

Options pour les auteurs de composants


Les trois (3) principales approches sont les suivantes :

Ne pas utiliser les comportements interactifs (de base)

Pour les composants dont le seul rôle consiste à produire du contenu DOM en
lecture seule, le développeur n’est pas tenu de prendre des mesures spéciales. Ces
composants fonctionnent naturellement avec n’importe quel mode de rendu.

Exemples :
Un composant « carte utilisateur » qui charge les données correspondant à une
personne et les restitue dans une interface utilisateur stylée avec une photo, un
nom de poste et autres détails.
Un composant « vidéo » qui fait office d’enveloppe pour l’élément HTML
<video> , ce qui le rend plus pratique à utiliser dans un composant Razor.

Exiger un rendu interactif (de base)

Vous pouvez choisir d’exiger que votre composant soit utilisé uniquement avec le
rendu interactif. Cela limite l’applicabilité de votre composant, mais cela signifie
que vous pouvez librement vous appuyer sur des gestionnaires d’événements
arbitraires. Même après, vous devriez toujours éviter de déclarer un @rendermode
spécifique et autoriser l’auteur de l’application qui consomme votre bibliothèque à
en sélectionner un.

Exemples :
Composant de montage vidéo où les utilisateurs peuvent coller et réorganiser
les séquences vidéo. Même s’il y avait un moyen de représenter ces opérations
de montage avec des boutons HTML simples et des posts de formulaire,
l’expérience utilisateur ne serait pas viable sans une vraie interactivité.
Un éditeur de documents collaboratif qui doit afficher les activités des autres
utilisateurs en temps réel.

Utiliser des comportements interactifs, mais concevoir pour le SSR statique et


une amélioration progressive (Avancé)

De nombreux comportements interactifs peuvent être implémentés à l’aide de


fonctionnalités HTML uniquement. Avec une bonne compréhension du code HTML
et CSS, vous pouvez souvent produire une base de référence utile qui fonctionne
avec le SSR statique. Vous pouvez toujours déclarer des gestionnaires
d’événements qui implémentent des comportements facultatifs plus avancés,
lesquels fonctionnent uniquement dans les modes de rendu interactifs.

Exemples :
Un composant de grille. Sous le SSR statique, le composant peut uniquement
prendre en charge l’affichage des données et la navigation entre les pages
(implémentées avec des liens <a> ). Lorsqu’il est utilisé avec un rendu interactif,
le composant peut ajouter un tri et un filtrage dynamiques.
Un composant d’ensemble de tabulations. Tant que la navigation entre les
onglets se fait en utilisant des liens <a> et que l’état est conservé uniquement
dans les paramètres de requête de l’URL, le composant peut fonctionner sans
@onclick .

Un composant de chargement de fichier avancé. Sous le SSR statique, le


composant peut se comporter en tant que <input type=file> natif. Lorsqu’il est
utilisé avec le rendu interactif, le composant peut également afficher la
progression du chargement.
Un composant de cotations boursières. Sous le SSR statique, le composant peut
afficher les cotations boursières lors du rendu du code HTML. Lorsqu’il est
utilisé avec un rendu interactif, le composant peut ensuite mettre à jour le cours
des actions en temps réel.

Pour l’une de ces stratégies, il existe également la possibilité d’implémenter des


fonctionnalités interactives avec JavaScript.

Pour choisir parmi ces approches, les auteurs de composants Razor réutilisables doivent
faire un compromis entre le coût et les avantages. Votre composant est plus utile et
dispose d’une base d’utilisateurs potentielle plus large s’il prend en charge tous les
modes de rendu, y compris le SSR statique. Toutefois, concevoir et implémenter un
composant qui prend en charge et tire le meilleur parti de chaque mode de rendu
demande plus de travail.
Quand utiliser la directive @rendermode
Dans la plupart des cas, les auteurs de composants réutilisables n’ont pas intérêt à
spécifier de mode de rendu, même lorsque l’interactivité est requise. C’est parce que
l’auteur du composant ne sait pas si l’application active la prise en charge pour
InteractiveServer, InteractiveWebAssembly ou pour les deux avec InteractiveAuto. En ne
spécifiant aucun @rendermode , l’auteur du composant laisse le choix au développeur de
l’application.

Même si l’auteur du composant pense qu’une interactivité est requise, il peut toujours y
avoir des cas où un auteur d’application considère qu’il suffit d’utiliser le SSR statique
seul. Par exemple, un composant de carte avec une interactivité glisser-zoomer peut
sembler nécessiter une interactivité. Toutefois, certains scénarios peuvent uniquement
appeler le rendu d’une image de carte statique et éviter les fonctionnalités de
déplacement/zoom.

La seule raison pour laquelle un auteur de composant réutilisable aurait intérêt à utiliser
la directive @rendermode sur son composant est si l’implémentation est
fondamentalement couplée à un mode de rendu spécifique. Cela provoquerait
certainement une erreur si elle était utilisée dans un autre mode. Imaginez un
composant ayant pour objectif principal d’interagir directement avec le système
d’exploitation hôte à l’aide d’API spécifiques à Windows ou Linux. Il est probablement
impossible d’utiliser un tel composant sur WebAssembly. Dans ce cas, il est raisonnable
de déclarer @rendermode InteractiveServer pour le composant.

Rendu en streaming
Les composants réutilisables Razor sont libres de déclarer @attribute
[StreamRendering] pour le rendu en streaming ([StreamRendering] attribut API). Cela
entraîne des mises à jour incrémentielles de l’interface utilisateur pendant le SSR
statique. Étant donné que les mêmes modèles de chargement de données produisent
des mises à jour incrémentielles de l'interface utilisateur lors du rendu interactif, quelle
que soit la présence de l'attribut [StreamRendering] , le composant peut se comporter
correctement dans tous les cas. Même dans les cas où le SSR statique en streaming est
supprimé sur le serveur, le composant restitue toujours son état final correctement.

Utilisation de liens entre les modes de rendu


Les composants Razor réutilisables peuvent utiliser des liens et une navigation
améliorée. Les balises HTML <a> devraient produire des comportements équivalents
avec ou sans composant Router interactif et indiquer si la navigation améliorée est
activée/désactivée au niveau ancêtre du DOM.

Utilisation de formulaires dans les modes de


rendu
Les composants Razor réutilisables peuvent inclure des formulaires ( <form
@onsubmit=...> ou <EditForm OnValidSubmit=...> ), car ceux-ci peuvent être

implémentés pour fonctionner de la même manière dans les modes de rendu statiques
et interactifs.

Prenons l’exemple suivant :

razor

<EditForm Enhance FormName="NewProduct" Model="Model"


OnValidSubmit="SaveProduct">
<DataAnnotationsValidator />
<ValidationSummary />

<p>Name: <InputText @bind-Value="Item.Name" /></p>

<button type="submit">Submit</button>
</EditForm>

@code {
[SupplyParameterFromForm]
public Product? Model { get; set; }

protected override void OnInitialized() => Model ??= new();

private async Task Save()


{
...
}
}

Les API Enhance, FormName et SupplyParameterFromFormAttribute sont utilisées


uniquement pendant le SSR statique et sont ignorées pendant le rendu interactif. Le
formulaire fonctionne correctement pendant le SSR interactif et statique.

Éviter les API propres au SSR statique


Pour rendre un composant réutilisable qui fonctionne sur tous les modes de rendu, ne
comptez pas sur HttpContext parce qu’il est seulement disponible pendant le SSR
statique. L’utilisation de l’API HttpContext ne présente aucun intérêt pendant le rendu
interactif, car il n’y a pas de requête HTTP active en cours à ce moment-là. Il est inutile
de penser à définir un code d’état ou à écrire quelque chose dans la réponse.

Les composants réutilisables sont libres de recevoir un HttpContext s’il est disponible,
comme suit :

C#

[CascadingParameter]
public HttpContext? Context { get; set; }

La valeur est null pendant le rendu interactif et est définie uniquement pendant le SSR
statique.

Dans de nombreux cas, il existe de meilleures alternatives que d’utiliser HttpContext. Si


vous avez besoin de connaître l’URL actuelle ou d’effectuer une redirection, les API sur
NavigationManager fonctionnent avec tous les modes de rendu. Si vous avez besoin de
connaître l’état d’authentification de l’utilisateur, faites appel au service
AuthenticationStateProvider de Blazor plutôt qu’au service HttpContext.User.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Utilisez des composants Razordans les
applications JavaScript et les
infrastructures SPA
Article • 09/02/2024

Cet article explique comment afficher des composants Razor à partir de JavaScript,
utiliser des éléments personnalisés Blazor et générer des composants Angular et React.

Générer le rendu de composants Razor à partir


de JavaScript
Les composants Razor peuvent être rendus dynamiquement à partir de JavaScript (JS)
pour des applications JS existantes.

L’exemple de cette section affiche le composant Razor suivant dans une page via JS.

Quote.razor :

razor

<div class="m-5 p-5">


<h2>Quote</h2>
<p>@Text</p>
</div>

@code {
[Parameter]
public string? Text { get; set; }
}

Dans le fichier Program , ajoutez l’espace de noms pour l’emplacement du composant.

Appelez RegisterForJavaScript sur la collection de composants racine de l’application


pour inscrire le composant Razor en tant que composant racine pour le rendu JS.

RegisterForJavaScript inclut une surcharge qui accepte le nom d’une fonction JS qui
exécute la logique d’initialisation ( javaScriptInitializer ). La fonction JS est appelée
une fois par inscription de composant immédiatement après le démarrage de
l’application Blazor et avant le rendu des composants. Cette fonction peut être utilisée
pour l’intégration à des technologies JS, telles que des éléments personnalisés HTML ou
une infrastructure SPA basée sur JS.
Une ou plusieurs fonctions d’initialiseur peuvent être créées et appelées par différentes
inscriptions de composants. Le cas d’usage classique consiste à réutiliser la même
fonction d’initialiseur pour plusieurs composants, ce qui est attendu si la fonction
d’initialiseur configure l’intégration avec des éléments personnalisés ou une autre
infrastructure SPA basée sur JS.

) Important

Ne confondez pas le paramètre javaScriptInitializer de RegisterForJavaScript


avec des initialiseurs JavaScript. Le nom du paramètre et de la fonctionnalité
d’initialiseurs JS est une coïncidence.

L’exemple suivant illustre l’inscription dynamique du composant Quote précédent avec


« quote » comme identificateur.

Dans une application web Blazor, modifiez l’appel à


AddInteractiveServerComponents dans le fichier Program côté serveur :

C#

builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(options =>
{
options.RootComponents.RegisterForJavaScript<Quote>(identifier:
"quote",
javaScriptInitializer: "initializeComponent");
});

Dans une application Blazor WebAssembly, appelez RegisterForJavaScript sur


RootComponents dans le fichier Program côté client :

C#

builder.RootComponents.RegisterForJavaScript<Quote>(identifier:
"quote",
javaScriptInitializer: "initializeComponent");

Attachez la fonction d’initialiseur avec les paramètres de fonction name et parameters à


l’objet window . À des fins de démonstration, la fonction suivante initializeComponent
journalise le nom et les paramètres du composant inscrit.

wwwroot/jsComponentInitializers.js :

JavaScript
window.initializeComponent = (name, parameters) => {
console.log({ name: name, parameters: parameters });
}

Affichez le composant à partir de JS dans un élément conteneur à l’aide de


l’identificateur inscrit, en passant les paramètres du composant si nécessaire.

Dans l’exemple suivant :

Le composant Quote (identificateur quote ) est affiché dans l’élément


quoteContainer lorsque la fonction showQuote est appelée.

Une chaîne de guillemets est passée au paramètre du Text composant.

wwwroot/scripts.js :

JavaScript

async function showQuote() {


let targetElement = document.getElementById('quoteContainer');
await Blazor.rootComponents.add(targetElement, 'quote',
{
text: "Crow: I have my doubts that this movie is actually 'starring' " +
"anybody. More like, 'camera is generally pointed at.'"
});
}

Une fois le script Blazor chargé, chargez les scripts précédents dans l’application JS :

HTML

<script src="_framework/{BLAZOR SCRIPT}"></script>


<script src="jsComponentInitializers.js"></script>
<script src="scripts.js"></script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le script Blazor.

En HTML, placez l’élément conteneur cible ( quoteContainer ). Pour la démonstration de


cette section, un bouton déclenche le rendu du composant Quote en appelant la
fonction showQuote JS :

HTML

<button onclick="showQuote()">Show Quote</button>

<div id="quoteContainer"></div>
Lors de l’initialisation avant le rendu de tout composant, la console des outils du
développeur du navigateur journalise l’identificateur du composant Quote ( name ) et les
paramètres ( parameters ) quand initializeComponent est appelé :

Console

Object { name: "quote", parameters: (1) […] }


name: "quote"
parameters: Array [ {…} ]
0: Object { name: "Text", type: "string" }
length: 1

Lorsque le bouton Show Quote est sélectionné, le composant Quote est affiché avec la
citation stockée dans Text :

Citation ©1988-1999 Satellite of Love LLC : Mystery Science Theater 3000 (Trace
Beaulieu (Crow) )

7 Notes

rootComponents.add retourne une instance du composant. Appelez dispose de

l’instance pour la libérer :

JavaScript

const rootComponent = await window.Blazor.rootComponents.add(...);

...

rootComponent.dispose();

L’exemple précédent affiche dynamiquement le composant racine lorsque la fonction


showQuote() JS est appelée. Pour afficher un composant racine dans un élément

conteneur lorsque Blazor démarre, utilisez un Initialiseur JavaScript pour afficher le


composant, comme le montre l’exemple suivant.

L’exemple suivant s’appuie sur l’exemple précédent, à l’aide du composant Quote , de


l’inscription du composant racine dans le fichier Program et de l’initialisation de
jsComponentInitializers.js . La fonction showQuote() (et le fichier script.js ) ne sont

pas utilisés.
En HTML, placez l’élément conteneur cible, quoteContainer2 pour cet exemple :

HTML

<div id="quoteContainer2"></div>

À l’aide d’un Initialiseur JavaScript, ajoutez le composant racine à l’élément conteneur


cible.

wwwroot/{PACKAGE ID/ASSEMBLY NAME}.lib.module.js :

Pour une application web Blazor :

JavaScript

export function afterWebStarted(blazor) {


let targetElement = document.getElementById('quoteContainer2');
blazor.rootComponents.add(targetElement, 'quote',
{
text: "Crow: I have my doubts that this movie is actually 'starring' "
+
"anybody. More like, 'camera is generally pointed at.'"
});
}

Pour une application Blazor Server ou Blazor WebAssembly :

JavaScript

export function afterStarted(blazor) {


let targetElement = document.getElementById('quoteContainer2');
blazor.rootComponents.add(targetElement, 'quote',
{
text: "Crow: I have my doubts that this movie is actually 'starring' "
+
"anybody. More like, 'camera is generally pointed at.'"
});
}

7 Notes

Pour l’appel à rootComponents.add , utilisez le paramètre blazor ( b (en) minuscules)


fourni par l’événement de démarrage Blazor. Bien que l’inscription soit valide lors
de l’utilisation de l’objet Blazor ( B majuscule), l’approche recommandée consiste à
utiliser le paramètre.
Pour obtenir un exemple avancé avec des fonctionnalités supplémentaires, consultez
l’exemple dans le BasicTestApp de la source de référence ASP.NET Core (référentiel
GitHub dotnet/aspnetcore ) :

JavaScriptRootComponents.razor
wwwroot/js/jsRootComponentInitializers.js
wwwroot/index.html

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Éléments personnalisés Blazor


Utilisez des éléments personnalisés Blazor pour afficher dynamiquement des
composants Razor à partir d’autres infrastructures SPA, telles qu’Angular ou React.

Éléments personnalisés Blazor :

Utilisez des interfaces HTML standard pour implémenter des éléments HTML
personnalisés.
Éliminez la nécessité de gérer manuellement l’état et le cycle de vie des
composants racines Razor à l’aide des API JavaScript.
Sont utiles pour introduire progressivement des composants Razor dans des
projets existants écrits dans d’autres infrastructures SPA.

Les éléments personnalisés ne prennent pas en charge le contenu enfant ou les


composants basés sur un modèle.

Nom de l'élément
Conformément à la spécification HTML , les noms des balises d’éléments personnalisés
doivent adopter la casse kebab :
❌ mycounter
❌ MY-COUNTER
❌ MyCounter
✔️my-counter
✔️my-cool-counter

Package
Ajoutez une référence de package pour
Microsoft.AspNetCore.Components.CustomElements au fichier projet de l’application.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Exemple de composant
Les exemples suivants sont basés sur le composant Counter du modèle de projet Blazor.

Counter.razor :

razor

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}
BlazorInscription Web App
Procédez comme suit pour inscrire un composant racine en tant qu’élément
personnalisé dans une application web Blazor.

Ajoutez l’espace de noms Microsoft.AspNetCore.Components.Web en haut du fichier


Program côté serveur :

C#

using Microsoft.AspNetCore.Components.Web;

Ajoutez un espace de noms pour les composants de l’application. Dans l’exemple


suivant, l’espace de noms de l’application est BlazorSample et les composants se
trouvent dans le dossier Components/Pages :

C#

using BlazorSample.Components.Pages;

Modifiez l’appel sur AddInteractiveServerComponents pour spécifier l’élément


personnalisé avec RegisterCustomElement sur l’option de circuit RootComponents.
L’exemple suivant inscrit le composant Counter avec l’élément HTML my-counter
personnalisé :

C#

builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(options =>
{
options.RootComponents.RegisterCustomElement<Counter>("my-counter");
});

Inscription Blazor WebAssembly


Procédez comme suit pour inscrire un composant racine en tant qu’élément
personnalisé dans une application Blazor WebAssembly.

Ajoutez l’espace de noms Microsoft.AspNetCore.Components.Web en haut du fichier


Program :

C#
using Microsoft.AspNetCore.Components.Web;

Ajoutez un espace de noms pour les composants de l’application. Dans l’exemple


suivant, l’espace de noms de l’application est BlazorSample et les composants se
trouvent dans le dossier Pages :

C#

using BlazorSample.Pages;

Appelez RegisterCustomElement sur RootComponents. L’exemple suivant inscrit le


composant Counter avec l’élément HTML my-counter personnalisé :

C#

builder.RootComponents.RegisterCustomElement<Counter>("my-counter");

Utilisez l’élément personnalisé inscrit


Utilisez l’élément personnalisé avec n’importe quel framework web. Par exemple,
l’élément HTML personnalisé my-counter précédent qui affiche le composant Counter
de l’application est utilisé dans une application React avec la balise suivante :

HTML

<my-counter></my-counter>

Pour obtenir un exemple complet de création d’éléments personnalisés avec Blazor,


consultez le CustomElementsComponentcomposant dans la source de référence.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .
Passer des paramètres
Transmettez les paramètres à votre composant Blazor en tant qu’attributs HTML ou en
tant que propriétés JavaScript sur l’élément DOM.

Le composant suivant Counter utilise un paramètre IncrementAmount pour définir


l’incrémentation du bouton Click me.

Counter.razor :

razor

@page "/counter"

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

[Parameter]
public int IncrementAmount { get; set; } = 1;

private void IncrementCount()


{
currentCount += IncrementAmount;
}
}

Affichez le composant Counter avec l’élément personnalisé et transmettez une valeur au


paramètre IncrementAmount en tant qu’attribut HTML. Le nom de l’attribut adopte la
syntaxe de la casse kebab ( increment-amount et non IncrementAmount ) :

HTML

<my-counter increment-amount="10"></my-counter>

Vous pouvez également définir la valeur du paramètre en tant que propriété JavaScript
sur l’objet élément. Le nom de propriété adopte la syntaxe de la casse chameau
( incrementAmount et non IncrementAmount ) :

JavaScript
const elem = document.querySelector("my-counter");
elem.incrementAmount = 10;

Vous pouvez mettre à jour les valeurs de paramètres à tout moment à l’aide de la
syntaxe d’attribut ou de propriété.

Types de paramètres pris en charge :

À l’aide de la syntaxe de propriété JavaScript, vous pouvez passer des objets de


n’importe quel type JSsérialisable.
À l’aide d’attributs HTML, vous êtes limité à la transmission d’objets de type chaîne,
booléen ou numérique.

Générer des composants Angular et React


Générez des composants JavaScript spécifiques au framework (JS) à partir de
composants Razor pour les frameworks web, comme Angular ou React. Cette
fonctionnalité n’est pas incluse avec .NET, mais elle est disponible grâce à la prise en
charge du rendu des composants Razor à partir de JS. L’exemple de génération de
composants JS sur GitHub montre comment générer des composants Angular et
React à partir de composants Razor. Pour plus d’informations, consultez le fichier
README.md de l’exemple d’application GitHub.

2 Avertissement

Les fonctionnalités des composants Angular et React sont actuellement


expérimentales, non prises en charge et susceptibles d’être modifiées ou
supprimées à tout moment. Nous vous invitons à nous faire part de vos
commentaires sur la façon dont cette approche particulière répond à vos besoins.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Restituer des composants Razor en
dehors de ASP.NET Core
Article • 09/02/2024

Razor les composants peuvent être rendus en dehors du contexte d’une requête HTTP.
Vous pouvez restituer des composants Razor au format HTML directement dans une
chaîne ou un flux indépendamment de l’environnement d’hébergement ASP.NET Core.
Cela est pratique pour les scénarios où vous souhaitez générer des fragments HTML, par
exemple pour générer du contenu de courrier électronique, générer du contenu de site
statique ou créer un moteur de création de modèles de contenu.

Dans l’exemple suivant, un Razor composant est rendu dans une chaîne HTML à partir
d’une application console :

Dans un interpréteur de commandes, créez un projet d’application console :

CLI .NET

dotnet new console -o ConsoleApp1


cd ConsoleApp1

Dans un interpréteur de commandes du dossier ConsoleApp1 , ajoutez des références de


package pour Microsoft.AspNetCore.Components.Web et Microsoft.Extensions.Logging
à l’application console :

CLI .NET

dotnet add package Microsoft.AspNetCore.Components.Web


dotnet add package Microsoft.Extensions.Logging

Dans le fichier projet de l’application console ( ConsoleApp1.csproj ), mettez à jour le


projet d’application console pour utiliser le Razor Kit de développement logiciel (SDK) :

diff

- <Project Sdk="Microsoft.NET.Sdk">
+ <Project Sdk="Microsoft.NET.Sdk.Razor">

Ajoutez le composant RenderMessage suivant au projet.

RenderMessage.razor :
razor

<h1>Render Message</h1>

<p>@Message</p>

@code {
[Parameter]
public string Message { get; set; }
}

Mettez à jour le fichier Program :

Configurez l’injection de dépendances (IServiceCollection/BuildServiceProvider) et


la journalisation (AddLogging/ILoggerFactory).
Créez un HtmlRenderer et affichez le composant RenderMessage en appelant
RenderComponentAsync.

Tous les appels à RenderComponentAsync doivent être effectués dans le contexte de


l’appel InvokeAsync sur un répartiteur de composants. Un répartiteur de composants est
disponible à partir de la propriété HtmlRenderer.Dispatcher.

C#

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ConsoleApp1;

IServiceCollection services = new ServiceCollection();


services.AddLogging();

IServiceProvider serviceProvider = services.BuildServiceProvider();


ILoggerFactory loggerFactory =
serviceProvider.GetRequiredService<ILoggerFactory>();

await using var htmlRenderer = new HtmlRenderer(serviceProvider,


loggerFactory);

var html = await htmlRenderer.Dispatcher.InvokeAsync(async () =>


{
var dictionary = new Dictionary<string, object?>
{
{ "Message", "Hello from the Render Message component!" }
};

var parameters = ParameterView.FromDictionary(dictionary);


var output = await htmlRenderer.RenderComponentAsync<RenderMessage>
(parameters);
return output.ToHtmlString();
});

Console.WriteLine(html);

7 Notes

Passez ParameterView.Empty à RenderComponentAsync lors du rendu du


composant sans passer de paramètres.

Vous pouvez également écrire le code HTML dans un TextWriter en appelant


output.WriteHtmlTo(textWriter) .

La tâche retournée par RenderComponentAsync se termine lorsque le composant est


entièrement rendu, y compris l’exécution des méthodes de cycle de vie asynchrones. Si
vous souhaitez observer le code HTML rendu précédemment, appelez
BeginRenderingComponent plutôt. Attendez ensuite que le rendu du composant se
termine en attendant HtmlRootComponent.QuiescenceTask sur l’instance retournée
HtmlRootComponent .

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Composants intégrés d’ASP.NET Core
Razor
Article • 09/02/2024

Les composants intégrés Razor suivants sont fournis par l’infrastructure Blazor. Pour plus
d’informations sur les composants de modèle de projet non liés à la sécurité, consultez
Structure de projet ASP.NET Core Blazor. Pour plus d’informations sur les composants de
modèle de projet liés à la sécurité, consultez les articles sur le Nœud de sécurité.

AntiforgeryToken
AuthorizeView
CascadingValue
DataAnnotationsValidator
DynamicComponent
Editor<T>
EditForm
ErrorBoundary
FocusOnNavigate
HeadContent
HeadOutlet
InputCheckbox
InputDate
InputFile
InputNumber
InputRadio
InputRadioGroup
InputSelect
InputText
InputTextArea
LayoutView
NavigationLock
NavLink
PageTitle
QuickGrid
Router
RouteView
SectionContent
SectionOutlet
ValidationSummary
Virtualize

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Globalisation et localisation dans
ASP.NET Core Blazor
Article • 09/02/2024

Cet article explique comment restituer du contenu globalisé et localisé aux utilisateurs
aux cultures et langues différentes.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du Blazorréférentiel GitHub
d’exemples qui correspond à la version de .NET que vous ciblez. Pour le moment, tous
les exemples de la documentation ne figurent pas dans les exemples d’applications,
mais nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans
les exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant
du premier trimestre 2024.

Globalisation et localisation
Pour la globalisation, Blazor fournit la mise en forme des nombres et des dates. Pour la
localisation, Blazor restitue le contenu à l’aide du système de ressources .NET.

Un ensemble limité de fonctionnalités de localisation d’ASP.NET Core sont prises en


charge :

✔️IStringLocalizer et IStringLocalizer<T> sont pris en charge dans les applications


Blazor.

❌IHtmlLocalizer, IViewLocalizer et la localisation des annotations de données sont des


fonctionnalités MVC ASP.NET Core ; elles ne sont pas prises en charge dans les
applications Blazor.

Cet article explique comment utiliser les fonctionnalités de globalisation et de


localisation de Blazor en fonction des éléments suivants :

L’en-tête Accept-Language , qui est défini par le navigateur en fonction des


préférences linguistiques d’un utilisateur dans les paramètres du navigateur.
La culture définie par l’application, qui n’est pas basée sur la valeur de l’en-tête
Accept-Language . Le paramètre peut être statique pour tous les utilisateurs, ou
dynamique en fonction de la logique de l’application. Lorsque le paramètre est
basé sur la préférence de l’utilisateur, le paramètre est généralement enregistré
pour être rechargé lors de visites ultérieures.

Pour des informations générales supplémentaires, consultez les ressources suivantes :

Globalisation et localisation dans ASP.NET Core


Principes de base de .NET : Globalisation
Principes de base de .NET : Localisation

Souvent, les termes langue et culture sont utilisés indifféremment lorsqu’il s’agit de
concepts de globalisation et de localisation.

Dans cet article, langue fait référence aux sélections effectuées par un utilisateur dans
les paramètres de son navigateur. Les sélections de langue de l’utilisateur sont envoyées
dans les requêtes de navigateur dans l’en-tête Accept-Language . Les paramètres du
navigateur utilisent généralement le mot « langue » dans l’interface utilisateur.

La culture concerne les membres de .NET et de l’API Blazor. Par exemple, la requête d’un
utilisateur peut inclure l’en-tête Accept-Language spécifiant une langue du point de
vue de l’utilisateur, mais l’application définit la propriété CurrentCulture (« culture ») à
partir de la langue demandée par l’utilisateur. L’API utilise généralement le mot
« culture » dans ses noms de membres.

7 Notes

Les exemples de code de cet article utilisent les types de référence null (NRT,
nullable reference types) et l'analyse statique de l'état null du compilateur .NET,
qui sont pris en charge dans ASP.NET Core 6 et ses versions ultérieures. Lorsque
vous ciblez ASP.NET Core 5.0 ou version antérieure, supprimez la désignation de
type null ( ? ) des exemples de l’article.

Globalisation
La directive d’attribut @bind applique des formats et analyse les valeurs d’affichage en
fonction de la première langue préférée de l’utilisateur prise en charge par
l’application. @bind prend en charge le paramètre @bind:culture pour fournir un
System.Globalization.CultureInfo pour l’analyse et la mise en forme d’une valeur.

La culture actuelle est accessible à partir de la propriété


System.Globalization.CultureInfo.CurrentCulture.

CultureInfo.InvariantCulture est utilisé pour les types de champs suivants ( <input type="
{TYPE}" /> , où l’espace réservé {TYPE} est le type) :

date
number

Les types de champs précédents :

Sont affichés à l’aide de leurs règles de mise en forme basées sur le navigateur
appropriées.
Ne peuvent pas contenir de texte de forme libre.
Fournissent des caractéristiques d’interaction utilisateur en fonction de
l’implémentation du navigateur.
Lorsque vous utilisez les types de champ date et number , la spécification d’une culture
avec @bind:culture n’est pas recommandée, car Blazor fournit une prise en charge
intégrée pour afficher des valeurs dans la culture actuelle.

Les types de champ suivants ont des exigences de mise en forme spécifiques et ne sont
actuellement pas pris en charge par Blazor, car ils ne sont pas pris en charge par tous les
principaux navigateurs :

datetime-local

month
week

Pour connaître la prise en charge actuelle des navigateurs des types précédents,
consultez Puis-je utiliser .

Globalisation .NET et prise en charge (Blazor


WebAssembly) des composants internationaux
pour Unicode (ICU)
Blazor WebAssembly utilise une API de globalisation réduite et un ensemble de
paramètres régionaux pour les composants internationaux Unicode (ICU) intégrés. Pour
plus d’informations, consultez Globalisation .NET et ICU : ICU sur WebAssembly.

Pour charger un fichier de données DCU personnalisé et contrôler les paramètres


régionaux de l’application, consultez l’ICU de globalisation WASM . Actuellement, la
génération manuelle du fichier de données d’ICU personnalisé est requise. Les outils
.NET pour faciliter le processus de création du fichier sont prévus pour .NET 9 en
novembre 2024.

Globalisation invariante
Si l’application ne nécessite pas de localisation, configurez l’application pour prendre en
charge la culture invariante, qui est généralement basée sur l’anglais des États-Unis ( en-
US ). Définissez la propriété InvariantGlobalization sur true dans le fichier projet de

l’application ( .csproj ) :

XML

<PropertyGroup>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

Vous pouvez également configurer la globalisation invariante avec les approches


suivantes :

Dans runtimeconfig.json :

JSON

{
"runtimeOptions": {
"configProperties": {
"System.Globalization.Invariant": true
}
}
}

Avec une variable d’environnement :


Clé : DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
Valeur : true ou 1

Pour plus d’informations, consultez Options de configuration d’exécution pour la


globalisation (documentation .NET).

Composant de démonstration
Le composant CultureExample1 suivant peut être utilisé pour illustrer les concepts de
globalisation et de localisation de Blazor abordés par cet article.

CultureExample1.razor :

razor

@page "/culture-example-1"
@using System.Globalization

<h1>Culture Example 1</h1>

<p>
<b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>

<h2>Rendered values</h2>

<ul>
<li><b>Date</b>: @dt</li>
<li><b>Number</b>: @number.ToString("N2")</li>
</ul>

<h2><code>&lt;input&gt;</code> elements that don't set a <code>type</code>


</h2>

<p>
The following <code>&lt;input&gt;</code> elements use
<code>CultureInfo.CurrentCulture</code>.
</p>

<ul>
<li><label><b>Date:</b> <input @bind="dt" /></label></li>
<li><label><b>Number:</b> <input @bind="number" /></label></li>
</ul>

<h2><code>&lt;input&gt;</code> elements that set a <code>type</code></h2>

<p>
The following <code>&lt;input&gt;</code> elements use
<code>CultureInfo.InvariantCulture</code>.
</p>

<ul>
<li><label><b>Date:</b> <input type="date" @bind="dt" /></label></li>
<li><label><b>Number:</b> <input type="number" @bind="number" /></label>
</li>
</ul>

@code {
private DateTime dt = DateTime.Now;
private double number = 1999.69;
}

Le format de chaîne numérique ( N2 ) dans l’exemple précédent ( .ToString("N2") ) est un


spécificateur de format numérique .NET standard. Le format N2 est pris en charge pour
tous les types numériques, inclut un séparateur de groupe et affiche jusqu’à deux
décimales.

Si vous le souhaitez, ajoutez un élément de menu à la navigation dans le composant


NavMenu ( NavMenu.razor ) pour le composant CultureExample1 .

Définir dynamiquement la culture à partir de


l’en-tête Accept-Language
Ajoutez le package Microsoft.Extensions.Localization à l’application.

L’en-tête Accept-Language est défini par le navigateur et contrôlé par les préférences
linguistiques de l’utilisateur dans les paramètres du navigateur. Dans les paramètres du
navigateur, un utilisateur définit une ou plusieurs langues préférées par ordre de
préférence. L’ordre de préférence est utilisé par le navigateur pour définir des valeurs de
qualité ( q , 0-1) pour chaque langue dans l’en-tête. L’exemple suivant spécifie l’anglais
des États-Unis, l’anglais et l’espagnol chilien avec une préférence pour l’anglais des
États-Unis ou l’anglais :

Accept-Language: en-US,en;q=0.9,es-CL;q=0.8

La culture de l’application est définie en mettant en correspondance la première langue


demandée qui correspond à une culture prise en charge de l’application.

Lors du développement côté client, définissez la propriété


BlazorWebAssemblyLoadAllGlobalizationData sur true dans le fichier projet de

l’application côté client ( .csproj ) :

XML

<PropertyGroup>

<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlo
balizationData>
</PropertyGroup>

7 Notes

Si la spécification de l’application nécessite de limiter les cultures prises en charge à


une liste explicite, consultez la section Définir dynamiquement la culture côté
client par préférence utilisateur de cet article.

Les applications sont localisées à l’aide d’un intergiciel de localisation. Ajoutez des
services de localisation à l’application avec AddLocalization.

Ajoutez la ligne suivante au fichier Program dans lequel les services sont inscrits :

C#

builder.Services.AddLocalization();

Dans le développement côté serveur, vous pouvez spécifier les cultures prises en charge
de l’application immédiatement après l’ajout de l’intergiciel de routage au pipeline de
traitement. L’exemple suivant configure les cultures prises en charge pour l’anglais des
États-Unis et l’espagnol chilien :
C#

app.UseRequestLocalization(new RequestLocalizationOptions()
.AddSupportedCultures(new[] { "en-US", "es-CL" })
.AddSupportedUICultures(new[] { "en-US", "es-CL" }));

Pour plus d’informations sur la commande d’intergiciels de localisation dans le pipeline


d’intergiciels du fichier Program , consultez Intergiciel ASP.NET Core.

Utilisez le composant CultureExample1 indiqué dans la section Composant de


démonstration pour étudier le fonctionnement de la globalisation. Émettre une requête
avec l’anglais des États-Unis ( en-US ). Basculez vers l’espagnol chilien ( es-CL ) dans les
paramètres de langue du navigateur. Demandez à nouveau la page web.

7 Notes

Certains navigateurs vous obligent à utiliser le paramètre de langue par défaut


pour les requêtes et les propres paramètres d’interface utilisateur du navigateur.
Cela peut rendre difficile la modification de la langue sur une langue que vous
comprenez, car tous les écrans de l’interface utilisateur de paramètres peuvent se
retrouver dans une langue que vous ne pouvez pas lire. Un navigateur comme
Opera est un bon choix pour les tests, car il vous permet de définir une langue
par défaut pour les requêtes de page web, tout en laissant l’interface utilisateur des
paramètres du navigateur dans votre langue.

Lorsque la culture est l’anglais des États-Unis ( en-US ), le composant rendu utilise la mise
en forme de date mois/jour ( 6/7 ), une heure sur le modèle de 12 heures ( AM / PM ) et des
virgules de séparation dans les nombres avec un point pour la valeur décimale
( 1,999.69 ) :

Date : 6/7/2021 6:45:22 AM


Number : 1,999.69

Lorsque la culture est l’espagnol chilien ( es-CL ), le composant rendu utilise la mise en
forme de date jour/mois ( 7/6 ), une heure sur le modèle de 24 heures et des points
comme séparateur de nombres avec une virgule pour la valeur décimale ( 1.999,69 ) :

Date : 7/6/2021 6:49:38


Number : 1.999,69

Définir statiquement la culture côté client


Définissez la propriété BlazorWebAssemblyLoadAllGlobalizationData sur true dans le
fichier projet de l’application ( .csproj ) :

XML

<PropertyGroup>

<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlo
balizationData>
</PropertyGroup>

La culture de l’application peut être définie en JavaScript lorsque Blazor commence par
l’option de démarrage applicationCulture Blazor. L’exemple suivant configure le
lancement de l’application à l’aide de la culture Anglais des États-Unis ( en-US ).

Empêchez le démarrage automatique de Blazor en ajoutant autostart="false" à la


balise de script de Blazor :

HTML

<script src="{BLAZOR SCRIPT}" autostart="false"></script>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script, consultez
ASP.NET Core Blazor structure du projet.

Ajoutez le bloc <script> suivant après la balise <script> de Blazor et avant la balise
</body> fermante :

Application webBlazor :

HTML

<script>
Blazor.start({
webAssembly: {
applicationCulture: 'en-US'
}
});
</script>

Blazor WebAssembly autonome :

HTML
<script>
Blazor.start({
applicationCulture: 'en-US'
});
</script>

La valeur pour applicationCulture doit être conforme au format de balise de langue


BCP-47 . Pour plus d’informations sur le démarrage de Blazor, consultez Démarrage
d’ASP.NET Core Blazor.

Une alternative à la définition de l’option de démarrage Blazor de la culture consiste à


définir la culture en code C#. Définissez CultureInfo.DefaultThreadCurrentCulture et
CultureInfo.DefaultThreadCurrentUICulture dans le fichier Program sur la même culture.

Ajoutez l’espace de noms System.Globalization au fichier Program :

C#

using System.Globalization;

Ajoutez les paramètres de culture avant la ligne qui génère et exécute


WebAssemblyHostBuilder ( await builder.Build().RunAsync(); ) :

C#

CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");


CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");

) Important

Définissez toujours DefaultThreadCurrentCulture et


DefaultThreadCurrentUICulture sur la même culture afin d’utiliser IStringLocalizer
et IStringLocalizer<T>.

Utilisez le composant CultureExample1 indiqué dans la section Composant de


démonstration pour étudier le fonctionnement de la globalisation. Émettre une requête
avec l’anglais des États-Unis ( en-US ). Basculez vers l’espagnol chilien ( es-CL ) dans les
paramètres de langue du navigateur. Demandez à nouveau la page web. Lorsque la
langue demandée est l’espagnol chilien, la culture de l’application reste l’anglais des
États-Unis ( en-US ).
Définir statiquement la culture côté serveur
Les applications côté serveur sont localisées à l’aide d’un intergiciel de localisation.
Ajoutez des services de localisation à l’application avec AddLocalization.

Dans le fichier Program :

C#

builder.Services.AddLocalization();

Spécifiez la culture statique dans le fichier Program immédiatement après l’ajout de


l’intergiciel de routage au pipeline de traitement. L’exemple suivant configure l’anglais
des États-Unis :

C#

app.UseRequestLocalization("en-US");

La valeur de culture pour UseRequestLocalization doit être conforme au format de balise


de langue BCP-47 .

Pour plus d’informations sur la commande d’intergiciels de localisation dans le pipeline


d’intergiciels du fichier Program , consultez Intergiciel ASP.NET Core.

Utilisez le composant CultureExample1 indiqué dans la section Composant de


démonstration pour étudier le fonctionnement de la globalisation. Émettre une requête
avec l’anglais des États-Unis ( en-US ). Basculez vers l’espagnol chilien ( es-CL ) dans les
paramètres de langue du navigateur. Demandez à nouveau la page web. Lorsque la
langue demandée est l’espagnol chilien, la culture de l’application reste l’anglais des
États-Unis ( en-US ).

Définir dynamiquement la culture côté client


par préférence utilisateur
Des exemples d’emplacements où une application peut stocker les préférences d’un
utilisateur incluent le stockage local du navigateur (courant avec le scénario côté
client), un cookie ou une base de données de localisation (courant avec le scénario côté
serveur), ou un service externe attaché à une base de données externe et accessible par
une API web. L’exemple suivant montre comment utiliser le stockage local du
navigateur.
Ajoutez le package Microsoft.Extensions.Localization à l’application.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Définissez la propriété BlazorWebAssemblyLoadAllGlobalizationData sur true dans le


fichier projet :

XML

<PropertyGroup>

<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlo
balizationData>
</PropertyGroup>

La culture de l'application pour le rendu côté client est définie à l'aide de l'API du
framework Blazor. La sélection de culture d’un utilisateur peut être conservée dans le
stockage local du navigateur.

Fournissez des fonctions JS pour obtenir et définir la sélection de culture de l’utilisateur


avec le stockage local du navigateur :

HTML

<script>
window.blazorCulture = {
get: () => window.localStorage['BlazorCulture'],
set: (value) => window.localStorage['BlazorCulture'] = value
};
</script>

7 Notes

L’exemple précédent pollue le client avec des fonctions globales. Pour une
meilleure approche dans les applications de production, consultez Isolation
JavaScript dans les modules JavaScript.
Ajoutez les espaces de noms pour System.Globalization et Microsoft.JSInterop en haut
du fichier Program :

C#

using System.Globalization;
using Microsoft.JSInterop;

Supprimez la ligne suivante :

diff

- await builder.Build().RunAsync();

Remplacez la ligne précédente par le code suivant. Le code ajoute le service de


localisation de Blazor à la collection de services de l’application avec AddLocalization, et
utilise l’interopérabilité JS pour appeler JS et récupérer la sélection de la culture de
l’utilisateur à partir du stockage local. Si le stockage local ne contient pas de culture
pour l’utilisateur, le code définit la valeur par défaut Anglais des États-Unis ( en-US ).

C#

builder.Services.AddLocalization();

var host = builder.Build();

CultureInfo culture;
var js = host.Services.GetRequiredService<IJSRuntime>();
var result = await js.InvokeAsync<string>("blazorCulture.get");

if (result != null)
{
culture = new CultureInfo(result);
}
else
{
culture = new CultureInfo("en-US");
await js.InvokeVoidAsync("blazorCulture.set", "en-US");
}

CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;

await host.RunAsync();

) Important
Définissez toujours DefaultThreadCurrentCulture et
DefaultThreadCurrentUICulture sur la même culture afin d’utiliser IStringLocalizer
et IStringLocalizer<T>.

Le composant CultureSelector suivant montre comment effectuer les actions


suivantes :

Définissez la sélection de culture de l’utilisateur dans le stockage local du


navigateur via l’interopérabilité JS.
Rechargez le composant demandé ( forceLoad: true ), qui utilise la culture mise à
jour.

CultureSelector.razor :

razor

@using System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation

<p>
<label>
Select your locale:
<select @bind="Culture">
@foreach (var culture in supportedCultures)
{
<option value="@culture">@culture.DisplayName</option>
}
</select>
</label>
</p>

@code
{
private CultureInfo[] supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("es-CL"),
};

private CultureInfo Culture


{
get => CultureInfo.CurrentCulture;
set
{
if (CultureInfo.CurrentCulture != value)
{
var js = (IJSInProcessRuntime)JS;
js.InvokeVoid("blazorCulture.set", value.Name);
Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
}
}
}
}

7 Notes

Pour plus d’informations sur IJSInProcessRuntime, consultez Appeler des fonctions


JavaScript à partir de méthodes .NET dans ASP.NET Core Blazor.

À l’intérieur de la balise fermante de l’élément </main> du composant MainLayout


( MainLayout.razor ), ajoutez le composant CultureSelector :

razor

<article class="bottom-row px-4">


<CultureSelector />
</article>

Utilisez le composant CultureExample1 indiqué dans la section Composant de


démonstration pour étudier le fonctionnement de l’exemple précédent.

Définir dynamiquement la culture côté serveur


par préférence utilisateur
Des exemples d’emplacements où une application peut stocker les préférences d’un
utilisateur incluent le stockage local du navigateur (courant avec les scénarios côté
client), un cookie ou une base de données de localisation (courant avec les scénarios
côté serveur), ou un service externe attaché à une base de données externe et accessible
par une API web. L’exemple suivant montre comment utiliser un cookie de localisation.

7 Notes

L’exemple suivant suppose que l’application adopte une interactivité globale en


spécifiant le rendu interactif côté serveur (interactive SSR) sur le composant Routes
dans le composant App ( Components/App.razor ) :

razor

<Routes @rendermode="InteractiveServer" />


Si l’application adopte l’interactivité par page/composant , consultez les remarques
à la fin de cette section pour modifier les modes de rendu des composants de
l’exemple.

Ajoutez le package Microsoft.Extensions.Localization à l’application.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Les applications côté serveur sont localisées à l’aide d’un intergiciel de localisation.
Ajoutez des services de localisation à l’application avec AddLocalization.

Dans le fichier Program :

C#

builder.Services.AddLocalization();

Définissez les cultures par défaut et prises en charge de l’application avec


RequestLocalizationOptions.

Avant l’appel à app.MapRazorComponents dans le pipeline de traitement des requêtes,


placez le code suivant :

C#

var supportedCultures = new[] { "en-US", "es-CL" };


var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

Pour plus d’informations sur la commande d’intergiciels de localisation dans le pipeline


d’intergiciels, consultez Intergiciel ASP.NET Core.

L’exemple suivant montre comment définir la culture actuelle dans un cookie qui peut
être lu par l’intergiciel de localisation.
Les espaces de noms suivants sont requis pour le composant App :

System.Globalization
Microsoft.AspNetCore.Localization

Ajoutez ce qui suit en haut du fichier de composant App ( Components/App.razor ) :

razor

@using System.Globalization
@using Microsoft.AspNetCore.Localization

Ajoutez le bloc @code suivant au bas du fichier de composant App :

razor

@code {
[CascadingParameter]
public HttpContext? HttpContext { get; set; }

protected override void OnInitialized()


{
HttpContext?.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(
CultureInfo.CurrentCulture,
CultureInfo.CurrentUICulture)));
}
}

Pour plus d’informations sur la commande d’intergiciels de localisation dans le pipeline


d’intergiciels, consultez Intergiciel ASP.NET Core.

Si l’application n’est pas configurée pour traiter les actions du contrôleur :

Ajoutez des services MVC en appelant AddControllers sur la collection de services


dans le fichier Program :

C#

builder.Services.AddControllers();

Ajoutez le routage du point de terminaison du contrôleur dans le fichier Program


en appelant MapControllers sur le IEndpointRouteBuilder ( app ) :

C#
app.MapControllers();

Pour fournir une interface utilisateur permettant à un utilisateur de sélectionner une


culture, utilisez une approche basée sur la redirection avec un cookie de localisation.
L’application conserve la culture sélectionnée de l’utilisateur via une redirection vers un
contrôleur. Le contrôleur définit la culture sélectionnée de l’utilisateur dans un cookie et
redirige l’utilisateur vers l’URI d’origine. Le processus est similaire à ce qui se produit
dans une application web lorsqu’un utilisateur tente d’accéder à une ressource
sécurisée, où l’utilisateur est redirigé vers une page de connexion, puis vers la ressource
d’origine.

Controllers/CultureController.cs :

C#

using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;

[Route("[controller]/[action]")]
public class CultureController : Controller
{
public IActionResult Set(string culture, string redirectUri)
{
if (culture != null)
{
HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture, culture)));
}

return LocalRedirect(redirectUri);
}
}

2 Avertissement

Utilisez le résultat de l’action LocalRedirect pour empêcher les attaques par


redirection ouverte. Pour plus d’informations, consultez Empêcher les attaques par
redirection ouverte dans ASP.NET Core.

Le composant CultureSelector suivant montre comment appeler la méthode Set de


CultureController avec la nouvelle culture. Le composant est placé dans le dossier
Shared pour une utilisation dans l’ensemble de l’application.
CultureSelector.razor :

razor

@using System.Globalization
@inject NavigationManager Navigation

<p>
<label>
Select your locale:
<select @bind="Culture">
@foreach (var culture in supportedCultures)
{
<option value="@culture">@culture.DisplayName</option>
}
</select>
</label>
</p>

@code
{
private CultureInfo[] supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("es-CL"),
};

protected override void OnInitialized()


{
Culture = CultureInfo.CurrentCulture;
}

private CultureInfo Culture


{
get => CultureInfo.CurrentCulture;
set
{
if (CultureInfo.CurrentCulture != value)
{
var uri = new Uri(Navigation.Uri)
.GetComponents(UriComponents.PathAndQuery,
UriFormat.Unescaped);
var cultureEscaped = Uri.EscapeDataString(value.Name);
var uriEscaped = Uri.EscapeDataString(uri);

Navigation.NavigateTo(
$"Culture/Set?culture={cultureEscaped}&redirectUri=
{uriEscaped}",
forceLoad: true);
}
}
}
}
Ajoutez le composant CultureSelector au composant MainLayout . Placez la balise
suivante à l’intérieur de la balise de fermeture </main> dans le fichier
Components/Layout/MainLayout.razor :

Ajoutez le composant CultureSelector au composant MainLayout . Placez la balise


suivante à l’intérieur de la balise de fermeture </main> dans le fichier
Shared/MainLayout.razor :

razor

<article class="bottom-row px-4">


<CultureSelector />
</article>

Utilisez le composant CultureExample1 indiqué dans la section Composant de


démonstration pour étudier le fonctionnement de l’exemple précédent.

L’exemple précédent suppose que l’application adopte l’interactivité globale en


spécifiant le mode de rendu de serveur interactif sur le composant Routes dans le
composant App ( Components/App.razor ) :

razor

<Routes @rendermode="InteractiveServer" />

Si l’application adopte l’interactivité par page/composant, apportez les modifications


suivantes :

Ajoutez le mode de rendu Serveur interactif en haut du fichier du composant


CultureExample1 ( Components/Pages/CultureExample1.razor ) :

razor

@rendermode InteractiveServer

Dans la présentation principale de l’application


( Components/Layout/MainLayout.razor ), appliquez le mode de rendu Serveur
interactif au composant CultureSelector :

razor

<CultureSelector @rendermode="InteractiveServer" />


Localisation
Si l’application ne prend pas déjà en charge la sélection de culture dynamique, ajoutez
le package Microsoft.Extensions.Localization à l’application.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Localisation côté client


Définissez la propriété BlazorWebAssemblyLoadAllGlobalizationData sur true dans le
fichier projet de l’application ( .csproj ) :

XML

<PropertyGroup>

<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlo
balizationData>
</PropertyGroup>

Dans le fichier Program , ajoutez l’espace de noms pour System.Globalization en haut du


fichier :

C#

using System.Globalization;

Ajoutez le service de localisation de Blazor à la collection de services de l’application


avec AddLocalization :

C#

builder.Services.AddLocalization();

Localisation côté serveur


Utilisez l’intergiciel de localisation pour définir la culture de l’application.

Si l’application ne prend pas déjà en charge la sélection de culture dynamique :

Ajoutez des services de localisation à l’application avec AddLocalization.


Spécifiez les cultures par défaut et prises en charge de l’application dans le fichier
Program . L’exemple suivant configure les cultures prises en charge pour l’anglais

des États-Unis et l’espagnol chilien.

C#

builder.Services.AddLocalization();

Immédiatement après l’ajout de l’intergiciel de routage au pipeline de traitement :

C#

var supportedCultures = new[] { "en-US", "es-CL" };


var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

Pour plus d’informations sur la commande d’intergiciels de localisation dans le pipeline


d’intergiciels, consultez Intergiciel ASP.NET Core.

Si l’application doit localiser des ressources en fonction du stockage du paramètre de


culture d’un utilisateur, utilisez un cookie de culture de localisation. L’utilisation d’un
cookie garantit que la connexion WebSocket peut propager correctement la culture. Si
les schémas de localisation sont basés sur le chemin d’URL ou la chaîne de requête, le
schéma peut ne pas être en mesure de fonctionner avec WebSockets pour conserver la
culture. Par conséquent, l’approche recommandée consiste à utiliser un cookie de
culture de localisation. Consultez la section Définir dynamiquement la culture côté
serveur par préférence utilisateur de cet article pour voir un exemple d’expression Razor
qui conserve la sélection de culture de l’utilisateur.

Exemple de ressources localisées


L’exemple de ressources localisées dans cette section fonctionne avec les exemples
précédents de cet article, où les cultures prises en charge de l’application sont l’anglais
( en ) comme paramètre régional par défaut et l’espagnol ( es ) comme paramètre
régional alternatif sélectionnable par l’utilisateur ou spécifié par le navigateur.
Créez un fichier de ressources pour chacun des paramètres régionaux. Dans l’exemple
suivant, des ressources sont créées pour une chaîne Greeting en anglais et en espagnol
:

Anglais ( en ) : Hello, World!


Espagnol ( es ) : ¡Hola, Mundo!

7 Notes

Vous pouvez ajouter le fichier de ressources suivant dans Visual Studio en cliquant
avec le bouton droit sur le dossier Pages et en sélectionnant Ajouter>Nouvel
élément>Fichier de ressources. Nommez le fichier CultureExample2.resx . Lorsque
l’éditeur s’affiche, fournissez des données pour une nouvelle entrée. Définissez le
Nom sur Greeting et la Valeur sur Hello, World! . Enregistrez le fichier.

Si vous utilisez Visual Studio Code, nous vous recommandons d’installer l’outil ResX
Viewer and Editor de Tim Heuer . Ajoutez un fichier CultureExample2.resx vide
au dossier Pages . L’extension prend automatiquement en charge la gestion du
fichier dans l’interface utilisateur. Sélectionnez le bouton Ajouter une nouvelle
ressource. Suivez les instructions pour ajouter une entrée pour Greeting (clé),
Hello, World! (valeur) et None (commentaire). Enregistrez le fichier. Si vous fermez

et rouvrez le fichier, vous pouvez voir la ressource Greeting .

L’outil ResX Viewer and Editor de Tim Heuer n’est ni détenu ni géré par
Microsoft et n’est couvert par aucun contrat de support ou licence Microsoft.

L’exemple suivant illustre un fichier de ressources standard. Vous pouvez placer


manuellement les fichiers de ressources dans le dossier Pages de l’application si vous
préférez ne pas utiliser d’outils intégrés avec un environnement de développement
intégré (IDE), tel que l’éditeur de fichiers de ressources intégré de Visual Studio ou
Visual Studio Code avec une extension pour créer et éditer des fichiers de ressources.

Pages/CultureExample2.resx :

XML

<?xml version="1.0" encoding="utf-8"?>


<root>
<xsd:schema id="root" xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-
microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"
msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0"
msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"
msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string"
msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string"
msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"
msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Greeting" xml:space="preserve">
<value>Hello, World!</value>
</data>
</root>

7 Notes

Vous pouvez ajouter le fichier de ressources suivant dans Visual Studio en cliquant
avec le bouton droit sur le dossier Pages et en sélectionnant Ajouter>Nouvel
élément>Fichier de ressources. Nommez le fichier CultureExample2.es.resx .
Lorsque l’éditeur s’affiche, fournissez des données pour une nouvelle entrée.
Définissez le Nom sur Greeting et la Valeur sur ¡Hola, Mundo! . Enregistrez le
fichier.

Si vous utilisez Visual Studio Code, nous vous recommandons d’installer l’outil ResX
Viewer and Editor de Tim Heuer . Ajoutez un fichier CultureExample2.resx vide
au dossier Pages . L’extension prend automatiquement en charge la gestion du
fichier dans l’interface utilisateur. Sélectionnez le bouton Ajouter une nouvelle
ressource. Suivez les instructions pour ajouter une entrée pour Greeting (clé),
¡Hola, Mundo! (valeur) et None (commentaire). Enregistrez le fichier. Si vous fermez

et rouvrez le fichier, vous pouvez voir la ressource Greeting .

L’exemple suivant illustre un fichier de ressources standard. Vous pouvez placer


manuellement les fichiers de ressources dans le dossier Pages de l’application si vous
préférez ne pas utiliser d’outils intégrés avec un environnement de développement
intégré (IDE), tel que l’éditeur de fichiers de ressources intégré de Visual Studio ou
Visual Studio Code avec une extension pour créer et éditer des fichiers de ressources.

Pages/CultureExample2.es.resx :

XML

<?xml version="1.0" encoding="utf-8"?>


<root>
<xsd:schema id="root" xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-
microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"
msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0"
msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"
msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string"
msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string"
msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"
msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Greeting" xml:space="preserve">
<value>¡Hola, Mundo!</value>
</data>
</root>

Le composant suivant illustre l’utilisation de la chaîne localisée Greeting avec


IStringLocalizer<T>. Le balisage Razor @Loc["Greeting"] de l’exemple suivant localise la
chaîne clé de la valeur Greeting , qui est définie dans les fichiers de ressources
précédents.

Ajoutez l’espace de noms pour Microsoft.Extensions.Localization au fichier


_Imports.razor de l’application :

razor

@using Microsoft.Extensions.Localization

CultureExample2.razor :

razor

@page "/culture-example-2"
@using System.Globalization
@inject IStringLocalizer<CultureExample2> Loc

<h1>Culture Example 2</h1>

<p>
<b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>

<h2>Greeting</h2>

<p>
@Loc["Greeting"]
</p>

<p>
@greeting
</p>

@code {
private string? greeting;

protected override void OnInitialized()


{
greeting = Loc["Greeting"];
}
}

Si vous le souhaitez, ajoutez un élément de menu pour le composant CultureExample2 à


la navigation pour le composant NavMenu ( NavMenu.razor ).

Source de référence du fournisseur de culture


WebAssembly
Pour mieux comprendre comment le framework Blazor traite la localisation, consultez la
classe WebAssemblyCultureProvider dans la source de référence ASP.NET Core.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Ressources partagées
Pour créer des ressources partagées de localisation, adoptez l’approche suivante.

Créez une classe factice avec un nom de classe arbitraire. Dans l’exemple suivant :
L’application utilise l’espace de noms BlazorSample et les ressources de
localisation utilisent l’espace de noms BlazorSample.Localization .
La classe factice est nommée SharedResource .
Le fichier de classe est placé dans un dossier Localization à la racine de
l’application.
Localization/SharedResource.cs :

C#

namespace BlazorSample.Localization;

public class SharedResource


{
}

Créez les fichiers de ressources partagées avec une Action de génération de


Embedded resource . Dans l’exemple suivant :

Les fichiers sont placés dans le dossier Localization avec la classe


SharedResource factice ( Localization/SharedResource.cs ).

Nommez les fichiers de ressources pour qu’ils correspondent au nom de la


classe factice. Les exemples de fichiers suivants incluent un fichier de
localisation par défaut et un fichier pour la localisation en espagnol ( es ).

Localization/SharedResource.resx

Localization/SharedResource.es.resx

7 Notes

Localization est le chemin de ressource qui peut être défini via

LocalizationOptions.

Pour référencer la classe factice pour un IStringLocalizer<T> injecté dans un


composant Razor, placez une directive @using pour l’espace de noms de
localisation ou incluez l’espace de noms de localisation dans la référence de classe
factice. Dans les exemples suivants :
Le premier exemple indique l’espace de noms Localization pour la classe
factice SharedResource avec une directive @using.
Le deuxième exemple indique explicitement l’espace de noms de la classe
factice SharedResource .

Dans un composant Razor, utilisez l’une des approches suivantes :

razor

@using Localization
@inject IStringLocalizer<SharedResource> Loc

razor

@inject IStringLocalizer<Localization.SharedResource> Loc

Pour plus d’instructions, consultez Globalisation et localisation dans ASP.NET Core.

Ressources supplémentaires
Définir le chemin d’accès de base de l’application
Globalisation et localisation dans ASP.NET Core
Internationalisation et localisation d’applications .NET
Ressources dans les fichiers .resx
Kit de ressources pour application multilingue Microsoft
Localisation et classes génériques
L’appel à InvokeAsync(StateHasChanged) cause le repli de la page sur la culture
par défaut (dotnet/aspnetcore #28521)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Vue d’ensemble des formulaires Blazor
ASP.NET Core
Article • 09/02/2024

Cet article explique comment utiliser des formulaires dans Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du Blazorréférentiel GitHub
d’exemples qui correspond à la version de .NET que vous ciblez. Pour le moment, tous
les exemples de la documentation ne figurent pas dans les exemples d’applications,
mais nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans
les exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant
du premier trimestre 2024.

Formulaires et composants d’entrée


L’infrastructure Blazor prend en charge les formulaires et fournit des composants
d’entrée intégrés :

Lier à un objet ou un modèle pouvant utiliser des annotations de données


Formulaires HTML avec l’élément <form>
Composants EditForm
Composants d’entrée intégrés

L’espace de noms Microsoft.AspNetCore.Components.Forms fournit :

Des classes de gestion des éléments de formulaire, de l’état et de la validation.


Accéder aux composants Input* intégrés.

Un projet, créé à partir du modèle de projet Blazor, comprend l’espace de noms par
défaut dans le fichier _Imports.razor de l’application, ce qui rend l’espace de noms
disponible pour les composants Razor de l’application.

Les formulaires HTML standard sont pris en charge. Créez un formulaire à l’aide de la
balise HTML <form> normale et spécifiez un gestionnaire @onsubmit pour gérer la
requête du formulaire envoyée.

StarshipPlainForm.razor :

razor

@page "/starship-plain-form"
@inject ILogger<StarshipPlainForm> Logger

<form method="post" @onsubmit="Submit" @formname="starship-plain-form">


<AntiforgeryToken />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>

@code {
[SupplyParameterFromForm]
public Starship? Model { get; set; }

protected override void OnInitialized() => Model ??= new();

private void Submit()


{
Logger.LogInformation("Id = {Id}", Model?.Id);
}

public class Starship


{
public string? Id { get; set; }
}
}

Dans le composant StarshipPlainForm précédent :

Le formulaire est affiché où l’élément <form> apparaît.


Le modèle est créé dans le bloc @code du composant et conservé dans une
propriété publique ( Model ). L’attribut [SupplyParameterFromForm] indique que la
valeur de la propriété associée doit être fournie à partir des données du formulaire.
Les données de la requête, correspondant au nom de la propriété, sont liées à la
propriété.
InputText est un composant d’entrée permettant de modifier des valeurs de
chaîne. L’attribut de directive @bind-Value lie la propriété de modèle Model.Id à la
propriété Value du composant InputText.
La méthode Submit est inscrite en tant que gestionnaire pour le rappel @onsubmit .
Le gestionnaire est appelé lorsque le formulaire est envoyé par l’utilisateur.

Blazor améliore la navigation entre les pages et la gestion des formulaires en


interceptant la requête, afin d’appliquer la réponse au DOM existant, en conservant
autant que possible le formulaire rendu. L’amélioration évite la nécessité de charger
entièrement la page et propose une expérience utilisateur beaucoup plus fluide,
similaire à une application monopage (SPA), bien que le composant soit rendu sur le
serveur. Pour plus d’informations, consultez Routage et navigation ASP.NET Core Blazor.

Le rendu de la diffusion en continu est pris en charge pour les formulaires HTML
standard.

7 Notes
Les liens de documentation vers la source de référence .NET chargent
généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

L’exemple précédent inclut la prise en charge antiforgery en incluant un composant


AntiforgeryToken dans le formulaire. La prise en charge antiforgery est expliquée plus
loin dans la section Prise en charge antiforgery de cet article.

Pour envoyer un formulaire basé sur les événements DOM d’un autre élément, par
exemple oninput ou onblur , utilisez JavaScript pour envoyer le
formulaire (submit (documentation MDN) ).

) Important

Pour un formulaire HTML, utilisez toujours la directive d’attribut @formname pour


affecter le nom du formulaire.

Au lieu d’utiliser des formulaires standard dans les applications Blazor, un formulaire est
généralement défini avec la prise en charge intégrée du formulaire de Blazor à l’aide du
composant EditForm du framework. Le composant Razor suivant illustre les éléments,
les composants et le code Razor classiques pour afficher un formulaire web à l’aide d’un
composant EditForm.

Starship1.razor :

razor

@page "/starship-1"
@inject ILogger<Starship1> Logger

<EditForm Model="Model" OnSubmit="Submit" FormName="Starship1">


<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
[SupplyParameterFromForm]
public Starship? Model { get; set; }

protected override void OnInitialized() => Model ??= new();

private void Submit()


{
Logger.LogInformation("Id = {Id}", Model?.Id);
}

public class Starship


{
public string? Id { get; set; }
}
}

Dans le composant Starship1 précédent :

Le composant EditForm est affiché à l’endroit où l’élément <EditForm> s’affiche.


Le modèle est créé dans le bloc @code du composant et conservé dans une
propriété publique ( Model ). La propriété affectée à EditForm.Model est attribuée
au paramètre EditForm.Model. L’attribut [SupplyParameterFromForm] indique que la
valeur de la propriété associée doit être fournie à partir des données du formulaire.
Les données de la requête, correspondant au nom de la propriété, sont liées à la
propriété.
InputText est un composant d’entrée permettant de modifier des valeurs de
chaîne. L’attribut de directive @bind-Value lie la propriété de modèle Model.Id à la
propriété Value du composant InputText.
La méthode Submit est inscrite en tant que gestionnaire pour le rappel OnSubmit.
Le gestionnaire est appelé lorsque le formulaire est envoyé par l’utilisateur.

Blazor améliore la navigation de page et la gestion des formulaires pour les composants
EditForm. Pour plus d’informations, consultez Routage et navigation ASP.NET Core
Blazor.

Le rendu de la diffusion en continu est pris en charge pour EditForm.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

†Pour plus d’informations sur la liaison de propriétés, consultez Liaison de données


Blazor ASP.NET Core.

Dans l’exemple suivant, le composant précédent est modifié pour créer le formulaire
dans le composant Starship2 :

OnSubmit est remplacé par OnValidSubmit, qui traite le gestionnaire d’événements


affecté si le formulaire est valide lors de l’envoi par l’utilisateur.
Un composant ValidationSummary est ajouté pour afficher les messages de
validation lorsque le formulaire n’est pas valide lors de l’envoi du formulaire.
Le validateur d’annotations de données (composant† DataAnnotationsValidator)
assure la prise en charge de la validation à l’aide d’annotations de données :
Si le champ de formulaire <input> est vide lorsque le bouton Submit est
sélectionné, une erreur s’affiche dans le résumé de validation (composant‡
ValidationSummary) (« The Id field is required. ») et Submit n’est pas
appelé.
Si le champ de formulaire <input> contient plus de dix caractères, lorsque le
bouton Submit est sélectionné, une erreur s’affiche dans le résumé de validation
(« Id is too long. »). Submit n’est pas appelé.
Si le champ de formulaire <input> contient une valeur valide lorsque le bouton
Submit est sélectionné, Submit est appelé.

†Le composant DataAnnotationsValidator est abordé dans la section Composant


validateur. ‡Le composant ValidationSummary est abordé dans la section Composants
du résumé de validation et du message de validation.

Starship2.razor :

razor

@page "/starship-2"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship2> Logger

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship2">


<DataAnnotationsValidator />
<ValidationSummary />
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
<button type="submit">Submit</button>
</EditForm>

@code {
[SupplyParameterFromForm]
public Starship? Model { get; set; }

protected override void OnInitialized() => Model ??= new();

private void Submit()


{
Logger.LogInformation("Id = {Id}", Model?.Id);
}

public class Starship


{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}

Gérer l’envoi du formulaire


Le EditForm fournit les rappels suivants pour la gestion de la soumission du formulaire :

Utilisez OnValidSubmit pour affecter un gestionnaire d’événements à exécuter


lorsqu’un formulaire avec des champs valides est envoyé.
Utilisez OnInvalidSubmit pour affecter un gestionnaire d’événements à exécuter
lorsqu’un formulaire avec des champs non valides est envoyé.
Utilisez OnSubmit pour affecter un gestionnaire d’événements à exécuter, quel que
soit l’état de validation des champs du formulaire. Le formulaire est validé en
appelant EditContext.Validate dans la méthode du gestionnaire d’événements. Si
Validate renvoie true , le formulaire est valide.

Prise en charge d’Antiforgery


Le composant AntiforgeryToken restitue un jeton antiforgery en tant que champ
masqué et l’attribut [RequireAntiforgeryToken] active la protection antiforgery. Si une
vérification antiforgery échoue, une réponse 400 - Bad Request est levée et le
formulaire n’est pas traité.

Pour les formulaires basés sur EditForm, le composant AntiforgeryToken et l’attribut


[RequireAntiforgeryToken] sont automatiquement ajoutés afin de fournir une
protection antiforgery par défaut.

Pour les formulaires basés sur l’élément <form> HTML, ajoutez manuellement le
composant AntiforgeryToken au formulaire :

razor

<form method="post" @onsubmit="Submit" @formname="starshipForm">


<AntiforgeryToken />
<input id="send" type="submit" value="Send" />
</form>

@if (submitted)
{
<p>Form submitted!</p>
}

@code{
private bool submitted = false;

private void Submit() => submitted = true;


}

2 Avertissement

Pour les formulaires basés sur EditForm ou sur l’élément HTML <form> , la
protection antiforgery peut être désactivée en transmettant required: false à
l’attribut [RequireAntiforgeryToken] . L’exemple suivant désactive antiforgery et
n’est pas recommandé pour les applications publiques :

razor

@using Microsoft.AspNetCore.Antiforgery
@attribute [RequireAntiforgeryToken(required: false)]

Pour plus d’informations, consultez ASP.NET Core Blazor Authentification et autorisation.

Gestion améliorée des formulaires


La navigation améliorée pour les requêtes POST de formulaire avec le paramètre
Enhance pour les formulaires EditForm ou l’attribut data-enhance pour les
formulaires HTML ( <form> ) :

razor
<EditForm Enhance ...>
...
</EditForm>

HTML

<form data-enhance ...>


...
</form>

❌ vous ne pouvez pas définir la navigation améliorée sur l’élément ancêtre d’un
formulaire pour activer la gestion améliorée des formulaires.

HTML

<div data-enhance>
<form ...>
<!-- NOT enhanced -->
</form>
</div>

Les publications de formulaire améliorées fonctionnent uniquement avec les points de


terminaison Blazor. La publication d’un formulaire amélioré sur un point de terminaison
non-Blazor entraîne une erreur.

Pour désactiver la gestion améliorée des formulaires :

Pour un EditForm, supprimez le paramètre Enhance de l’élément de formulaire (ou


définissez-le sur false : Enhance="false" ).
Pour un code HTML <form> , supprimez l’attribut data-enhance de l’élément de
formulaire (ou définissez-le sur false : data-enhance="false" ).

La navigation améliorée et la gestion des formulaires de Blazor peuvent annuler les


modifications dynamiques apportées au DOM si le contenu mis à jour ne fait pas partie
du rendu du serveur. Pour conserver le contenu d’un élément, utilisez l’attribut data-
permanent .

Dans l’exemple suivant, le contenu de l’élément <div> est mis à jour dynamiquement
par un script lorsque la page se charge :

HTML

<div data-permanent>
...
</div>

Pour désactiver globalement la navigation améliorée et la gestion des formulaires,


consultez Démarrage de ASP.NET Core Blazor.

Pour obtenir des conseils sur l’utilisation de l’événement enhancedload pour écouter les
mises à jour de page améliorées, consultez Routage et navigation ASP.NET Core Blazor.

Exemples
Les exemples n’adoptent pas la gestion améliorée des formulaires pour les requêtes
POST de formulaire, mais tous les exemples peuvent être mis à jour pour adopter les
fonctionnalités améliorées en suivant les instructions de la section Gestion améliorée
des formulaires.

Pour montrer comment les formulaires fonctionnent avec la validation des annotations
de données, les exemples de composants s’appuient sur l’API
System.ComponentModel.DataAnnotations. Si vous souhaitez éviter une ligne de code
supplémentaire dans les composants qui utilisent des annotations de données, rendez
l’espace de noms disponible dans les composants de l’application avec le fichier
d’importation ( _Imports.razor ) :

razor

@using System.ComponentModel.DataAnnotations

Des exemples de formulaires référencent des aspects de l’univers Star Trek . Star Trek
est une œuvre sous droits d’auteur ©1966-2023 de CBS Studios et Paramount .

Ressources supplémentaires
Chargements de fichiers Blazor ASP.NET Core
Blazor exemples de dépôt GitHub (dotnet/blazor-samples)
Ressources de formulaires de test du référentiel GitHub ASP.NET Core
(dotnet/aspnetcore)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Composants d’entrée Blazor ASP.NET
Core
Article • 09/02/2024

Cet article décrit les composants d’entrée intégrés de Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du Blazordépôt GitHub
d’exemples correspondant à la version .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Composants d’entrée
L’infrastructure Blazor fournit des composants d’entrée intégrés pour recevoir et valider
les entrées utilisateur. Les composants d’entrée intégrés dans le tableau suivant sont
pris en charge dans un EditForm avec un EditContext.

Les composants du tableau sont également pris en charge en dehors d’un formulaire
dans la balise de composant Razor. Les entrées sont validées lorsqu’elles sont modifiées
et lorsqu’un formulaire est envoyé.

ノ Agrandir le tableau

Composant d’entrée Rendu comme suit…

InputCheckbox <input type="checkbox">

InputDate<TValue> <input type="date">

InputFile <input type="file">

InputNumber<TValue> <input type="number">

InputRadio<TValue> <input type="radio">

InputRadioGroup<TValue> Groupe d’enfants InputRadio<TValue>

InputSelect<TValue> <select>

InputText <input>

InputTextArea <textarea>

Pour plus d’informations sur le composant InputFile, consultez Téléchargements de


fichiers Blazor ASP.NET Core.

Tous les composants d’entrée, y compris EditForm, prennent en charge les attributs
arbitraires. Tout attribut qui ne correspond pas à un paramètre de composant est ajouté
à l’élément HTML rendu.

Les composants d’entrée fournissent un comportement par défaut pour la validation


lorsqu’un champ est modifié :
Pour les composants d’entrée dans un formulaire avec un EditContext, le
comportement de validation par défaut comprend la mise à jour de la classe CSS
de champ pour refléter l’état du champ comme valide ou non valide avec le style
de validation de l’élément HTML sous-jacent.
Pour les contrôles qui n’ont pas de EditContext, la validation par défaut reflète
l’état valide ou non valide, mais ne fournit pas de style de validation à l’élément
HTML sous-jacent.

Certains composants incluent une logique d’analyse utile. Par exemple,


InputDate<TValue> et InputNumber<TValue> gèrent correctement les valeurs non
analysables en inscrivant les valeurs non analysables en tant qu’erreurs de validation. Les
types qui peuvent accepter des valeurs nulles prennent également en charge la
possibilité de valeur nulle du champ cible (par exemple, int? pour un entier pouvant
accepter la valeur Null).

Pour plus d’informations sur le composant InputFile, consultez Téléchargements de


fichiers Blazor ASP.NET Core.

Exemple de formulaire
Le type Starship suivant , qui est utilisé dans plusieurs exemples de cet article et dans
des exemples d’autres articles du nœud Formulaires, définit un ensemble diversifié de
propriétés avec des annotations de données :

Id est requis, car il est annoté avec le RequiredAttribute. Id nécessite une valeur

d’au moins un caractère, mais pas plus de 16 caractères à l’aide du


StringLengthAttribute.
Description est facultatif, car il n’est pas annoté avec le RequiredAttribute.
Classification est obligatoire.

La propriété MaximumAccommodation a la valeur zéro par défaut, mais nécessite une


valeur comprise entre un et 100 000 par son RangeAttribute.
IsValidatedDesign exige que la propriété ait une valeur true , qui correspond à un

état sélectionné lorsque la propriété est liée à une case à cocher dans l’interface
utilisateur ( <input type="checkbox"> ).
ProductionDate est un DateTime et est obligatoire.

Starship.cs :

C#

using System.ComponentModel.DataAnnotations;
namespace BlazorSample;

public class Starship


{
[Required]
[StringLength(16, ErrorMessage = "Identifier too long (16 character
limit).")]
public string? Id { get; set; }

public string? Description { get; set; }

[Required]
public string? Classification { get; set; }

[Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]


public int MaximumAccommodation { get; set; }

[Required]
[Range(typeof(bool), "true", "true", ErrorMessage = "Approval
required.")]
public bool IsValidatedDesign { get; set; }

[Required]
public DateTime ProductionDate { get; set; }
}

Le formulaire suivant accepte et valide l’entrée utilisateur à l’aide de :

Les propriétés et la validation définies dans le modèle Starship précédent.


Plusieurs des composants d’entrée intégrés de Blazor.

Lorsque la propriété du modèle pour la classification du vaisseau ( Classification ) est


définie, l’option correspondant au modèle est cochée. Par exemple,
checked="@(Model!.Classification == "Exploration")" pour la classification d’un

vaisseau d’exploration. La raison pour laquelle l’option cochée est définie de manière
explicite est que la valeur d’un élément <select> n’est présente que dans le navigateur.
Si le formulaire est affiché sur le serveur après sa soumission, tout état du client est
remplacé par l’état du serveur, ce qui n’entraîne généralement pas le marquage d’une
option comme cochée. Le fait de définir l’option cochée à partir de la propriété du
modèle permet à la classification de toujours refléter l’état du modèle. La sélection de la
classification est donc conservée entre les soumissions de formulaire qui entraînent un
nouvel affichage du formulaire sur le serveur. Dans les situations où le formulaire n’est
pas affiché à nouveau sur le serveur, par exemple lorsque le mode d’affichage Serveur
interactif est appliqué directement au composant, l’affectation explicite de l’option
cochée à partir du modèle n’est pas nécessaire, car elle Blazor préserve l’état de
<select> l’élément sur le client.
Starship3.razor :

razor

@page "/starship-3"
@inject ILogger<Starship3> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship3">


<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description" />
</label>
</div>
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.Classification">
<option value="">
Select classification ...
</option>
<option checked="@(Model!.Classification == "Exploration")"
value="Exploration">
Exploration
</option>
<option checked="@(Model!.Classification == "Diplomacy")"
value="Diplomacy">
Diplomacy
</option>
<option checked="@(Model!.Classification == "Defense")"
value="Defense">
Defense
</option>
</InputSelect>
</label>
</div>
<div>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="Model!.MaximumAccommodation" />
</label>
</div>
<div>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="Model!.IsValidatedDesign" />
</label>
</div>
<div>
<label>
Production Date:
<InputDate @bind-Value="Model!.ProductionDate" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>

@code {
[SupplyParameterFromForm]
private Starship? Model { get; set; }

protected override void OnInitialized() =>


Model ??= new() { ProductionDate = DateTime.UtcNow };

private void Submit()


{
Logger.LogInformation("Id = {Id} Description = {Description} " +
"Classification = {Classification} MaximumAccommodation = " +
"{MaximumAccommodation} IsValidatedDesign = " +
"{IsValidatedDesign} ProductionDate = {ProductionDate}",
Model?.Id, Model?.Description, Model?.Classification,
Model?.MaximumAccommodation, Model?.IsValidatedDesign,
Model?.ProductionDate);
}
}

Le EditForm dans l’exemple précédent crée un EditContext basé sur l’instance de


Starship affectée ( Model="..." ) et gère un formulaire valide. L’exemple suivant montre

comment affecter un EditContext à un formulaire et valider la soumission du formulaire.

Dans l’exemple suivant :

Une version abrégée du formulaire Starfleet Starship Database précédent


(composant Starship3 ) est utilisée et n’accepte qu’une valeur pour l’ID du navire.
Les autres propriétés Starship reçoivent des valeurs par défaut valides lorsqu’une
instance du type Starship est créée.
La méthode Submit s’exécute lorsque le bouton Submit est sélectionné.
Le formulaire est validé en appelant EditContext.Validate dans la méthode Submit .
La journalisation est exécutée en fonction du résultat de la validation.
7 Notes

Submit est démontré (dans l’exemple suivant) comme une méthode asynchrone,

car le stockage des valeurs de formulaire utilise souvent des appels asynchrones
( await ... ). Si le formulaire est utilisé dans une application de test comme indiqué,
Submit s’exécute simplement de manière synchrone. À des fins de test, ignorez

l’avertissement de build suivant :

Cette méthode async n’a pas d’opérateur ’await’ et elle va s’exécute de façon
synchrone. ...

Starship4.razor :

razor

@page "/starship-4"
@inject ILogger<Starship4> Logger

<EditForm EditContext="editContext" OnSubmit="Submit" FormName="Starship4">


<DataAnnotationsValidator />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>

@code {
private EditContext? editContext;

[SupplyParameterFromForm]
private Starship? Model { get; set; }

protected override void OnInitialized()


{
Model ??=
new()
{
Id = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
editContext = new(Model);
}

private async Task Submit()


{
if (editContext != null && editContext.Validate())
{
Logger.LogInformation("Submit called: Form is valid");

// await ...
}
else
{
Logger.LogInformation("Submit called: Form is INVALID");
}
}
}

7 Notes

La modification du EditContext après son affectation n’est pas prise en charge.

Sélection de plusieurs options avec le


composant InputSelect
La liaison prend en charge la sélection d’options multiple avec le composant
InputSelect<TValue>. L’événement @onchange fournit un tableau des options
sélectionnées via des arguments d’événement (ChangeEventArgs). La valeur doit être
liée à un type de tableau, et la liaison à un type de tableau rend l’attribut multiple
facultatif sur la balise InputSelect<TValue>.

Dans l’exemple suivant, l’utilisateur doit sélectionner au moins deux classifications de


vaisseaux, mais pas plus de trois classifications.

Starship5.razor :

razor

@page "/starship-5"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship5> Logger

<h1>Bind Multiple <code>InputSelect</code> Example</h1>

<EditForm EditContext="editContext" OnValidSubmit="Submit"


FormName="Starship5">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Select classifications (Minimum: 2, Maximum: 3):
<InputSelect @bind-Value="Model!.SelectedClassification">
<option
value="@Classification.Exploration">Exploration</option>
<option value="@Classification.Diplomacy">Diplomacy</option>
<option value="@Classification.Defense">Defense</option>
<option value="@Classification.Research">Research</option>
</InputSelect>
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>

@if (Model?.SelectedClassification?.Length > 0)


{
<div>@string.Join(", ", Model.SelectedClassification)</div>
}

@code {
private EditContext? editContext;

[SupplyParameterFromForm]
private Starship? Model { get; set; }

protected override void OnInitialized()


{
Model = new();
editContext = new(Model);
}

private void Submit()


{
Logger.LogInformation("Submit called: Processing the form");
}

private class Starship


{
[Required]
[MinLength(2, ErrorMessage = "Select at least two
classifications.")]
[MaxLength(3, ErrorMessage = "Select no more than three
classifications.")]
public Classification[]? SelectedClassification { get; set; } =
new[] { Classification.None };
}

private enum Classification { None, Exploration, Diplomacy, Defense,


Research }
}
Pour plus d’informations sur la façon dont les chaînes et les valeurs null vides sont
gérées dans la liaison de données, consultez la section Options InputSelect de liaison à
des valeurs null d’objet C#.

Options InputSelect de liaison aux valeurs


null d’objet C#
Pour plus d’informations sur la façon dont les chaînes et les valeurs null vides sont
gérées dans la liaison de données, consultez Liaison de données Blazor ASP.NET Core.

Prise en charge des noms d’affichage


Plusieurs composants intégrés prennent en charge les noms d’affichage avec le
paramètre InputBase<TValue>.DisplayName.

Dans le formulaire Starfleet Starship Database (composant Starship3 ) de la section


Exemple de formulaire, la date de production d’un nouveau vaisseau ne spécifie pas de
nom d’affichage :

razor

<label>
Production Date:
<InputDate @bind-Value="Model!.ProductionDate" />
</label>

Si le champ contient une date non valide lors de l’envoi du formulaire, le message
d’erreur n’affiche pas de nom convivial. Le nom de champ, « ProductionDate » n’a pas
d’espace entre « Production » et « Date » lorsqu’il s’affiche dans le résumé de
validation :

Le champ ProductionDate doit être une date.

Définissez la propriété DisplayName sur un nom convivial avec un espace entre les mots
« Production » et « Date » :

razor

<label>
Production Date:
<InputDate @bind-Value="Model!.ProductionDate"
DisplayName="Production Date" />
</label>

Le résumé de validation affiche le nom convivial lorsque la valeur du champ n’est pas
valide :

Le champ Date de production doit être une date.

Prise en charge du modèle de message d’erreur


InputDate<TValue> et InputNumber<TValue> prennent en charge les modèles de
message d’erreur :

InputDate<TValue>.ParsingErrorMessage
InputNumber<TValue>.ParsingErrorMessage

Dans le formulaire Starfleet Starship Database (composant Starship3 ) de la section


Exemple de formulaire avec un nom d’affichage convivial affecté, le champ Production
Date génère un message d’erreur à l’aide du modèle de message d’erreur par défaut

suivant :

css

The {0} field must be a date.

La position de l’espace réservé {0} correspond à l’emplacement où la valeur de la


propriété DisplayName s’affiche lorsque l’erreur est affichée à l’utilisateur.

razor

<label>
Production Date:
<InputDate @bind-Value="Model!.ProductionDate"
DisplayName="Production Date" />
</label>

Le champ Date de production doit être une date.

Affectez un modèle personnalisé à ParsingErrorMessage pour fournir un message


personnalisé :

razor
<label>
Production Date:
<InputDate @bind-Value="Model!.ProductionDate"
DisplayName="Production Date"
ParsingErrorMessage="The {0} field has an incorrect date value." />
</label>

Le champ Date de production a une valeur de date incorrecte.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Liaison des formulaires Blazor ASP.NET
Core
Article • 05/01/2024

Cet article explique comment utiliser des liaisons dans des formulaires Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

EditForm / EditContext modèle


Un EditForm crée un EditContext en fonction de l’objet affecté comme valeur en cascade
pour d’autres composants du formulaire. Le EditContext suit les métadonnées relatives
au processus de modification, y compris les champs de formulaire qui ont été modifiés
et les messages de validation actuels. L’affectation à un EditForm.Model ou à un
EditForm.EditContext peut lier un formulaire à des données.

Liaison de données
Affectation à EditForm.Model :

razor

<EditForm ... Model="@Model" ...>


...
</EditForm>

@code {
[SupplyParameterFromForm]
public Starship? Model { get; set; }

protected override void OnInitialized() => Model ??= new();


}

Liaison du contexte
Affectation à EditForm.EditContext :

razor

<EditForm ... EditContext="@editContext" ...>


...
</EditForm>

@code {
private EditContext? editContext;

[SupplyParameterFromForm]
public Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
}
}

Affectez soit un EditContextou un Model à un EditForm. Si les deux sont affectés, une
erreur d’exécution est levée.

Types pris en charge


La liaison prend en charge :

Types primitifs
Collections
Types complexes
Types récursifs
Types avec constructeurs
Enums

Vous pouvez également utiliser les attributs [DataMember] et [IgnoreDataMember] pour


personnaliser la liaison de données. Utilisez ces attributs pour renommer, ignorer et
marquer les propriétés en fonction des besoins.

Options supplémentaires de liaison


Des options supplémentaires de liaison de données sont disponibles auprès de
RazorComponentsServiceOptions lors de l’appel à AddRazorComponents :

MaxFormMappingCollectionSize : nombre maximal d’éléments autorisés dans une


collection de formulaires.
MaxFormMappingRecursionDepth : profondeur maximale autorisée lors du
mappage récursif des données du formulaire.
MaxFormMappingErrorCount : nombre maximal d’erreurs autorisées lors du
mappage des données du formulaire.
MaxFormMappingKeySize : taille maximale de la mémoire tampon utilisée pour lire
les clés des données du formulaire.

Les valeurs par défaut affectées par l’infrastructure sont les suivantes :

C#
builder.Services.AddRazorComponents(options =>
{
options.FormMappingUseCurrentCulture = true;
options.MaxFormMappingCollectionSize = 1024;
options.MaxFormMappingErrorCount = 200;
options.MaxFormMappingKeySize = 1024 * 2;
options.MaxFormMappingRecursionDepth = 64;
}).AddInteractiveServerComponents();

Noms de formulaires
Utilisez le paramètre FormName pour attribuer un nom du formulaire. Les noms de
formulaires doivent être uniques pour lier des données du modèle. Le formulaire suivant
est nommé RomulanAle :

razor

<EditForm ... FormName="RomulanAle">


...
</EditForm>

Ajout d’un nom de formulaire :

Est nécessaire pour tous les formulaires soumis par des composants côté serveur
rendus statiquement.
N’est pas nécessaire pour les formulaires soumis par des composants rendus
interactivement, ce qui inclut les formulaires dans des applications et des
composants Blazor WebAssembly avec un mode de rendu interactif. Nous vous
recommandons cependant de fournir un nom du formulaire unique pour chaque
formulaire afin d’éviter les erreurs de publication si l’interactivité était supprimée
pour un formulaire.

Le nom du formulaire est vérifie seulement quand le formulaire est publié sur un point
de terminaison sous la forme d’une requête HTTP POST traditionnelle depuis un
composant côté serveur rendu statiquement. L’infrastructure ne lève aucune exception
au moment du rendu d’un formulaire, mais uniquement lorsqu’une requête HTTP POST
arrive sans spécifier de nom du formulaire.

Par défaut, il existe une étendue de formulaires non nommés (chaîne vide) au-dessus du
composant racine de l’application, ce qui suffit quand il n’y a pas de collisions de noms
de formulaire dans l’application. Si des collisions de noms de formulaire sont possibles,
par exemple quand vous incluez un formulaire depuis une bibliothèque et que vous
n’avez aucun contrôle sur le nom de formulaire utilisé par le développeur de la
bibliothèque, fournissez une étendue de noms de formulaires avec le composant
FormMappingScope dans le projet principal de l’application web Blazor.

Dans l’exemple suivant, le composant HelloFormFromLibrary a un formulaire nommé


Hello et se trouve dans une bibliothèque.

HelloFormFromLibrary.razor :

razor

<EditForm Model="@this" OnSubmit="@Submit" FormName="Hello">


<InputText @bind-Value="Name" />
<button type="submit">Submit</button>
</EditForm>

@if (submitted)
{
<p>Hello @Name from the library's form!</p>
}

@code {
bool submitted = false;

[SupplyParameterFromForm]
public string? Name { get; set; }

private void Submit() => submitted = true;


}

Le composant NamedFormsWithScope suivant utilise le composant HelloFormFromLibrary


de la bibliothèque et a aussi un formulaire nommé Hello . Le nom de l’étendue du
composant FormMappingScope est ParentContext pour les formulaires fournis par le
composant HelloFormFromLibrary . Bien que les deux formulaires de cet exemple aient le
même nom de formulaire ( Hello ), les noms de formulaire n’entrent pas en collision et
les événements sont routés vers le formulaire approprié pour les événements POST des
formulaires.

NamedFormsWithScope.razor :

razor

@page "/named-forms-with-scope"

<div>Hello form from a library</div>

<FormMappingScope Name="ParentContext">
<HelloFormFromLibrary />
</FormMappingScope>
<div>Hello form using the same form name</div>

<EditForm Model="@this" OnSubmit="@Submit" FormName="Hello">


<InputText @bind-Value="Name" />
<button type="submit">Submit</button>
</EditForm>

@if (submitted)
{
<p>Hello @Name from the app form!</p>
}

@code {
bool submitted = false;

[SupplyParameterFromForm]
public string? Name { get; set; }

private void Submit() => submitted = true;


}

Fournir un paramètre à partir du formulaire


( [SupplyParameterFromForm] )
L’attribut [SupplyParameterFromForm] indique que la valeur de la propriété associée doit
être fournie à partir des données du formulaire. Les données de la requête,
correspondant au nom de la propriété, sont liées à la propriété. Les entrées, basées sur
InputBase<TValue> , génèrent des noms de valeur de formulaire correspondant aux
noms Blazor utilisés pour la liaison de modèle.

Vous pouvez spécifier les paramètres de liaison de formulaire suivants à


l'[SupplyParameterFromForm]attribut :

Name : Obtient ou définit le nom du paramètre. Le nom sert à déterminer le


préfixe à utiliser pour correspondre aux données du formulaire et décider de lier
(ou pas) la valeur.
FormName : Obtient ou définit le nom du gestionnaire. Le nom est utilisé pour
faire correspondre le paramètre au formulaire par le nom du formulaire, afin de
décider de lier (ou pas) la valeur.

L’exemple suivant lie indépendamment deux formulaires à leurs modèles par le nom du
formulaire.

Starship6.razor :
razor

@page "/starship-6"
@inject ILogger<Starship6> Logger

<EditForm Model="@Model1" OnSubmit="@Submit1" FormName="Holodeck1">


<div>
<label>
Holodeck 1 Identifier:
<InputText @bind-Value="Model1!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>

<EditForm Model="@Model2" OnSubmit="@Submit2" FormName="Holodeck2">


<div>
<label>
Holodeck 2 Identifier:
<InputText @bind-Value="Model2!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>

@code {
[SupplyParameterFromForm(FormName = "Holodeck1")]
public Holodeck? Model1 { get; set; }

[SupplyParameterFromForm(FormName = "Holodeck2")]
public Holodeck? Model2 { get; set; }

protected override void OnInitialized()


{
Model1 ??= new();
Model2 ??= new();
}

private void Submit1()


{
Logger.LogInformation("Submit1: Id = {Id}", Model1?.Id);
}

private void Submit2()


{
Logger.LogInformation("Submit2: Id = {Id}", Model2?.Id);
}

public class Holodeck


{
public string? Id { get; set; }
}
}

Imbriquer et lier des formulaires


Les instructions suivantes montrent comment imbriquer et lier des formulaires enfants.

La classe de détails du navire ( ShipDetails ) suivante contient une description et une


longueur d’un sous-formulaire.

ShipDetails.cs :

C#

namespace BlazorSample;

public class ShipDetails


{
public string? Description { get; set; }
public int? Length { get; set; }
}

La classe Ship suivante nomme un identificateur ( Id ) et inclut les détails du navire.

Ship.cs :

C#

namespace BlazorSample
{
public class Ship
{
public string? Id { get; set; }
public ShipDetails Details { get; set; } = new();
}
}

Le sous-formulaire suivant sert à modifier les valeurs du type ShipDetails . Cette


opération est implémentée en héritant d’un Editor<T> au-dessus du composant.
Editor<T> garantit que le composant enfant génère les noms de champs de formulaire
appropriés en fonction du modèle ( T ), avec T qui a pour valeur ShipDetails dans
l’exemple suivant.

StarshipSubform.razor :
razor

@inherits Editor<ShipDetails>

<div>
<label>
Description:
<InputText @bind-Value="Value!.Description" />
</label>
</div>
<div>
<label>
Length:
<InputNumber @bind-Value="Value!.Length" />
</label>
</div>

Le formulaire principal est lié à la classe Ship . Le composant StarshipSubform est utilisé
pour modifier les détails du navire, liés en tant que Model!.Details .

Starship7.razor :

razor

@page "/starship-7"
@inject ILogger<Starship7> Logger

<EditForm Model="@Model" OnSubmit="@Submit" FormName="Starship7">


<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<StarshipSubform @bind-Value="Model!.Details" />
<div>
<button type="submit">Submit</button>
</div>
</EditForm>

@code {
[SupplyParameterFromForm]
public Ship? Model { get; set; }

protected override void OnInitialized() => Model ??= new();

private void Submit()


{
Logger.LogInformation("Id = {Id} Desc = {Description} Length =
{Length}",
Model?.Id, Model?.Details?.Description, Model?.Details?.Length);
}
}

Scénarios avancés d’erreur de mappage du


formulaire
L’infrastructure instancie et remplit le FormMappingContext pour un formulaire, ce qui
est le contexte associé à l’opération de mappage d’un formulaire donné. Chaque
étendue de mappage (définie par un composant FormMappingScope) instancie
FormMappingContext. Chaque fois qu’un [SupplyParameterFromForm] demande le
contexte d’une valeur, l’infrastructure remplit le FormMappingContext par la valeur
essayée et toutes les erreurs de mappage.

Les développeurs ne sont pas censés interagir directement avec FormMappingContext,


car il s’agit principalement d’une source de données pour InputBase<TValue>,
EditContext et d’autres implémentations internes pour afficher les erreurs de mappage
en tant qu’erreurs de validation. Dans les scénarios personnalisés avancés, les
développeurs peuvent accéder directement à FormMappingContext en tant que
[CascadingParameter] pour écrire du code personnalisé qui consomme les valeurs

essayées et les erreurs de mappage.

Cases d’option
L’exemple de cette section est basé sur le formulaire Starfleet Starship Database
(composant Starship3 ) de la section Exemple de formulaire de cet article.

Ajoutez les types enum suivants à l’application. Créez un nouveau fichier pour les
contenir ou ajoutez-les au fichier Starship.cs .

C#

public class ComponentEnums


{
public enum Manufacturer { SpaceX, NASA, ULA, VirginGalactic, Unknown }
public enum Color { ImperialRed, SpacecruiserGreen, StarshipBlue,
VoyagerOrange }
public enum Engine { Ion, Plasma, Fusion, Warp }
}

Rendre la classe enums accessible au :

Modèle Starship dans Starship.cs (par exemple using static ComponentEnums; ).


Formulaire Starfleet Starship Database ( Starship3.razor ) (par exemple @using
static ComponentEnums ).

Utilisez des composants InputRadio<TValue> avec le composant


InputRadioGroup<TValue> pour créer un groupe de cases d’option. Dans l’exemple
suivant, des propriétés sont ajoutées au modèle Starship décrit dans la section Exemple
de formulaire de l’article Composants d’entrée :

C#

[Required]
[Range(typeof(Manufacturer), nameof(Manufacturer.SpaceX),
nameof(Manufacturer.VirginGalactic), ErrorMessage = "Pick a
manufacturer.")]
public Manufacturer Manufacturer { get; set; } = Manufacturer.Unknown;

[Required, EnumDataType(typeof(Color))]
public Color? Color { get; set; } = null;

[Required, EnumDataType(typeof(Engine))]
public Engine? Engine { get; set; } = null;

Mettez à jour le formulaire Starfleet Starship Database ( composant Starship3 ) de la


section Exemple de formulaire de l’article Composants d’entrée. Ajoutez les composants
à produire :

Un groupe de cases d’option pour le fabricant du navire.


Un groupe de cases d’option imbriquées pour la couleur du moteur et du navire.

7 Notes

Les groupes de cases d’option imbriquées ne sont pas souvent utilisés dans les
formulaires, car ils peuvent entraîner une disposition désorganisée des contrôles de
formulaire pouvant perturber les utilisateurs. Toutefois, dans certains cas, elles ont
du sens dans la conception de l’interface utilisateur, comme dans l’exemple suivant
qui associe des recommandations pour deux entrées utilisateur, le moteur du
navire et la couleur du navire. Un moteur et une couleur sont requis par la
validation du formulaire. La disposition du formulaire utilise des
InputRadioGroup<TValue> imbriquées pour coupler les recommandations de
moteur et de couleur. Toutefois, l’utilisateur peut combiner n’importe quel moteur
avec n’importe quelle couleur pour envoyer le formulaire.

7 Notes
Assurez-vous de rendre la classe ComponentEnums disponible pour le composant de
l’exemple suivant :

razor

@using static ComponentEnums

razor

<fieldset>
<legend>Manufacturer</legend>
<InputRadioGroup @bind-Value="Model!.Manufacturer">
@foreach (var manufacturer in (Manufacturer[])Enum
.GetValues(typeof(Manufacturer)))
{
<div>
<label>
<InputRadio Value="@manufacturer" />
@manufacturer
</label>
</div>
}
</InputRadioGroup>
</fieldset>

<fieldset>
<legend>Engine and Color</legend>
<p>
Engine and color pairs are recommended, but any
combination of engine and color is allowed.
</p>
<InputRadioGroup Name="engine" @bind-Value="Model!.Engine">
<InputRadioGroup Name="color" @bind-Value="Model!.Color">
<div style="margin-bottom:5px">
<div>
<label>
<InputRadio Name="engine" Value="@Engine.Ion" />
Ion
</label>
</div>
<div>
<label>
<InputRadio Name="color" Value="@Color.ImperialRed"
/>
Imperial Red
</label>
</div>
</div>
<div style="margin-bottom:5px">
<div>
<label>
<InputRadio Name="engine" Value="@Engine.Plasma" />
Plasma
</label>
</div>
<div>
<label>
<InputRadio Name="color"
Value="@Color.SpacecruiserGreen" />
Spacecruiser Green
</label>
</div>
</div>
<div style="margin-bottom:5px">
<div>
<label>
<InputRadio Name="engine" Value="@Engine.Fusion" />
Fusion
</label>
</div>
<div>
<label>
<InputRadio Name="color" Value="@Color.StarshipBlue"
/>
Starship Blue
</label>
</div>
</div>
<div style="margin-bottom:5px">
<div>
<label>
<InputRadio Name="engine" Value="@Engine.Warp" />
Warp
</label>
</div>
<div>
<label>
<InputRadio Name="color"
Value="@Color.VoyagerOrange" />
Voyager Orange
</label>
</div>
</div>
</InputRadioGroup>
</InputRadioGroup>
</fieldset>

7 Notes

Si Name est omis, les composants InputRadio<TValue> sont regroupés par leur
ancêtre le plus récent.
Si vous avez implémenté le balisage Razor précédent dans le composant Starship3 de
la section Exemple de formulaire de l’article Composants d’entrée, mettez à jour la
journalisation pour la méthode Submit :

C#

Logger.LogInformation("Id = {Id} Description = {Description} " +


"Classification = {Classification} MaximumAccommodation = " +
"{MaximumAccommodation} IsValidatedDesign = " +
"{IsValidatedDesign} ProductionDate = {ProductionDate} " +
"Manufacturer = {Manufacturer}, Engine = {Engine}, " +
"Color = {Color}",
Model?.Id, Model?.Description, Model?.Classification,
Model?.MaximumAccommodation, Model?.IsValidatedDesign,
Model?.ProductionDate, Model?.Manufacturer, Model?.Engine,
Model?.Color);

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Validation des formulaires Blazor
ASP.NET Core
Article • 09/02/2024

Cet article explique comment utiliser la validation dans les formulaires Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Validation de formulaire
Dans les scénarios de validation de formulaire de base, une instance EditForm peut
utiliser les instances déclarées EditContext et ValidationMessageStore pour valider les
champs de formulaire. Un gestionnaire pour l’événement OnValidationRequested du
EditContext exécute la logique de validation personnalisée. Le résultat du gestionnaire
met à jour l’instance ValidationMessageStore.

La validation de base du formulaire est utile dans les cas où le modèle du formulaire est
défini dans le composant hébergeant le formulaire, soit en tant que membres
directement sur le composant, soit dans une sous-classe. L’utilisation d’un composant
validateur est recommandée lorsqu’une classe de modèle indépendante est utilisée sur
plusieurs composants.

Dans le composant suivant, la méthode du gestionnaire HandleValidationRequested


efface tous les messages de validation existants en appelant
ValidationMessageStore.Clear avant de valider le formulaire.

Starship8.razor :

razor

@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger

<h2>Holodeck Configuration</h2>

<EditForm EditContext="editContext" OnValidSubmit="Submit"


FormName="Starship8">
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem1" />
Safety Subsystem
</label>
</div>
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem2" />
Emergency Shutdown Subsystem
</label>
</div>
<div>
<ValidationMessage For="() => Model!.Options" />
</div>
<div>
<button type="submit">Update</button>
</div>
</EditForm>

@code {
private EditContext? editContext;

[SupplyParameterFromForm]
public Holodeck? Model { get; set; }

private ValidationMessageStore? messageStore;

protected override void OnInitialized()


{
Model ??= new();
editContext = new(Model);
editContext.OnValidationRequested += HandleValidationRequested;
messageStore = new(editContext);
}

private void HandleValidationRequested(object? sender,


ValidationRequestedEventArgs args)
{
messageStore?.Clear();

// Custom validation logic


if (!Model!.Options)
{
messageStore?.Add(() => Model.Options, "Select at least one.");
}
}

private void Submit()


{
Logger.LogInformation("Submit called: Processing the form");
}

public class Holodeck


{
public bool Subsystem1 { get; set; }
public bool Subsystem2 { get; set; }
public bool Options => Subsystem1 || Subsystem2;
}

public void Dispose()


{
if (editContext is not null)
{
editContext.OnValidationRequested -= HandleValidationRequested;
}
}
}

Composant validateur d’annotations de


données et validation personnalisée
Le composant DataAnnotationsValidator attache la validation des annotations de
données à une cascade EditContext. L’activation de la validation des annotations de
données nécessite le composant DataAnnotationsValidator. Pour utiliser un système de
validation différent des annotations de données, utilisez une implémentation
personnalisée au lieu du composant DataAnnotationsValidator. Les implémentations de
l’infrastructure pour DataAnnotationsValidator sont disponibles pour inspection dans la
source de référence :

DataAnnotationsValidator
AddDataAnnotationsValidation .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Blazor effectue deux types de validation :

La validation du champ est effectuée lorsque l’utilisateur affiche des onglets hors
d’un champ. Pendant la validation de champ, le composant
DataAnnotationsValidator associe tous les résultats de validation signalés au
champ.
La validation du modèle est exécutée lorsque l’utilisateur envoie le formulaire.
Pendant la validation du modèle, le composant DataAnnotationsValidator tente de
déterminer le champ en fonction du nom de membre que le résultat de la
validation indique. Les résultats de validation qui ne sont pas associés à un
membre individuel sont associés au modèle plutôt qu’à un champ.
Composants validateurs
Les composants validateurs prennent en charge la validation des formulaires en gérant
un ValidationMessageStore pour le EditContext d’un formulaire.

L’infrastructure Blazor fournit le composant DataAnnotationsValidator permettant


d’attacher la prise en charge de la validation aux formulaires en fonction d’attributs de
validation (annotations de données). Vous pouvez créer des composants validateurs
personnalisés pour traiter les messages de validation de différents formulaires sur la
même page ou le même formulaire à différentes étapes de traitement des formulaires
(par exemple, la validation du client suivie de la validation du serveur). L’exemple de
composant validateur indiqué dans cette section, CustomValidation , est utilisé dans les
sections suivantes de cet article :

Validation de logique métier avec un composant validateur


Validation du serveur avec un composant validateur

7 Notes

Dans de nombreux cas, les attributs de validation d’annotation de données


personnalisées peuvent être utilisés au lieu des composants validateurs
personnalisés. Les attributs personnalisés appliqués au modèle du formulaire
s’activent avec l’utilisation du composant DataAnnotationsValidator. Lorsqu’ils sont
utilisés avec la validation du serveur, tous les attributs personnalisés appliqués au
modèle doivent être exécutables sur le serveur. Pour plus d’informations, consultez
Validation de modèle dans ASP.NET Core MVC.

Créez un composant validateur à partir de ComponentBase :

Le formulaire EditContext est un paramètre en cascade du composant.


Lorsque le composant validateur est initialisé, un nouveau ValidationMessageStore
est créé pour conserver une liste actuelle d’erreurs de formulaire.
La banque de messages reçoit des erreurs lorsque le code du développeur dans le
composant du formulaire appelle la méthode DisplayErrors . Les erreurs sont
passées à la méthode DisplayErrors dans un Dictionary<string, List<string>>.
Dans le dictionnaire, la clé est le nom du champ de formulaire qui contient une ou
plusieurs erreurs. La valeur est la liste d’erreurs.
Les messages sont effacés lorsque l’un des éléments suivants s’est produit :
La validation est demandée sur le EditContext lorsque l’événement
OnValidationRequested est déclenché. Toutes les erreurs sont effacées.
Un champ change dans le formulaire lorsque l’événement OnFieldChanged est
déclenché. Seules les erreurs du champ sont effacées.
La méthode ClearErrors est appelée par le code du développeur. Toutes les
erreurs sont effacées.

Mettez à jour l’espace de noms dans la classe suivante pour une correspondance avec
l’espace de noms de votre application.

CustomValidation.cs :

C#

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;

namespace BlazorSample;

public class CustomValidation : ComponentBase


{
private ValidationMessageStore? messageStore;

[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }

protected override void OnInitialized()


{
if (CurrentEditContext is null)
{
throw new InvalidOperationException(
$"{nameof(CustomValidation)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. " +
$"For example, you can use {nameof(CustomValidation)} " +
$"inside an {nameof(EditForm)}.");
}

messageStore = new(CurrentEditContext);

CurrentEditContext.OnValidationRequested += (s, e) =>


messageStore?.Clear();
CurrentEditContext.OnFieldChanged += (s, e) =>
messageStore?.Clear(e.FieldIdentifier);
}

public void DisplayErrors(Dictionary<string, List<string>> errors)


{
if (CurrentEditContext is not null)
{
foreach (var err in errors)
{
messageStore?.Add(CurrentEditContext.Field(err.Key),
err.Value);
}
CurrentEditContext.NotifyValidationStateChanged();
}
}

public void ClearErrors()


{
messageStore?.Clear();
CurrentEditContext?.NotifyValidationStateChanged();
}
}

) Important

La spécification d’un espace de noms est requise lors de la dérivation de


ComponentBase. L’échec de la spécification d’un espace de noms entraîne une
erreur de build :

Les assistants de balise ne peuvent pas cibler le nom de balise '<espace de


noms global>.{NOM DE LA CLASSE}', car il contient un caractère ' '.

L’espace réservé {CLASS NAME} est le nom de la classe de composant. L’exemple de


validateur personnalisé dans cette section spécifie l’exemple d’espace de noms
BlazorSample .

7 Notes

Les expressions lambda anonymes sont des gestionnaires d’événements inscrits


pour OnValidationRequested et OnFieldChanged dans l’exemple précédent. Il n’est
pas nécessaire d’implémenter IDisposable et de désinscrire les délégués
d’événement dans ce scénario. Pour plus d’informations, consultez le cycle de vie
des composants Razor ASP.NET Core.

Validation de logique métier avec un


composant validateur
Pour la validation de logique métier générale, utilisez un composant validateur qui
reçoit des erreurs de formulaire dans un dictionnaire.

La validation de base est utile dans les cas où le modèle du formulaire est défini dans le
composant hébergeant le formulaire, soit en tant que membres directement sur le
composant, soit dans une sous-classe. L’utilisation d’un composant validateur est
recommandée lorsqu’une classe de modèle indépendante est utilisée sur plusieurs
composants.

Dans l’exemple suivant :

Une version abrégée du formulaire Starfleet Starship Database (composant


Starship3 ) de la section Exemple de formulaire de l’article Composants d’entrée est

utilisée, et accepte seulement la classification et la description du vaisseau spatial.


La validation de l’annotation des données n’est pas déclenchée lors de l’envoi du
formulaire, car le composant DataAnnotationsValidator n’est pas inclus dans le
formulaire.
Le composant CustomValidation de la section Composants du validateur de cet
article est utilisé.
La validation nécessite une valeur pour la description du navire ( Description ) si
l’utilisateur sélectionne la classification du navire « Defense » ( Classification ).

Lorsque les messages de validation sont définis dans le composant, ils sont ajoutés au
validateur ValidationMessageStore et affichés dans le résumé de la validation du
EditForm.

Starship9.razor :

razor

@page "/starship-9"
@inject ILogger<Starship9> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship9">


<CustomValidation @ref="customValidation" />
<ValidationSummary />
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.Classification">
<option value="">
Select classification ...
</option>
<option checked="@(Model!.Classification == "Exploration")"
value="Exploration">
Exploration
</option>
<option checked="@(Model!.Classification == "Diplomacy")"
value="Diplomacy">
Diplomacy
</option>
<option checked="@(Model!.Classification == "Defense")"
value="Defense">
Defense
</option>
</InputSelect>
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>

@code {
private CustomValidation? customValidation;

[SupplyParameterFromForm]
public Starship? Model { get; set; }

protected override void OnInitialized() =>


Model ??= new() { ProductionDate = DateTime.UtcNow };

private void Submit()


{
customValidation?.ClearErrors();

var errors = new Dictionary<string, List<string>>();

if (Model!.Classification == "Defense" &&


string.IsNullOrEmpty(Model.Description))
{
errors.Add(nameof(Model.Description),
new() { "For a 'Defense' ship classification, " +
"'Description' is required." });
}

if (errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else
{
Logger.LogInformation("Submit called: Processing the form");
}
}
}
7 Notes

Au lieu d'utiliser des composants de validation, des attributs de validation


d’annotation de données peuvent être utilisés. Les attributs personnalisés appliqués
au modèle du formulaire s’activent avec l’utilisation du composant
DataAnnotationsValidator. Lorsqu’ils sont utilisés avec la validation du serveur, les
attributs doivent être exécutables sur le serveur. Pour plus d’informations, consultez
Validation de modèle dans ASP.NET Core MVC.

Validation du serveur avec un composant


validateur
Cette section est principalement consacrée aux scénarios d’application web Blazor, mais
l’approche pour n’importe quel type d’application utilisant la validation du serveur avec
une API web adopte la même approche générale.

La validation du serveur est prise en charge en plus de la validation du client :

Traitez la validation du client dans le formulaire avec le composant


DataAnnotationsValidator.
Lorsque le formulaire passe la validation du client (OnValidSubmit est appelé),
envoyez le EditContext.Model à une API de serveur principal pour le traitement des
formulaires.
Validation du modèle de processus sur le serveur.
L’API serveur inclut à la fois la validation des annotations de données
d’infrastructure intégrée et la logique de validation personnalisée fournie par le
développeur. Si la validation est réussie sur le serveur, le formulaire est traité et un
code d’état de réussite (200 - OK ) est renvoyé. Si la validation échoue, un code
d’état d’échec (400 - Bad Request ) et les erreurs de validation des champs sont
renvoyés.
Désactivez le formulaire en cas de réussite ou affichez les erreurs.

La validation de base est utile dans les cas où le modèle du formulaire est défini dans le
composant hébergeant le formulaire, soit en tant que membres directement sur le
composant, soit dans une sous-classe. L’utilisation d’un composant validateur est
recommandée lorsqu’une classe de modèle indépendante est utilisée sur plusieurs
composants.

L'exemple suivant est basé sur :


Une application web Blazor avec des composants WebAssembly interactifs créés à
partir du modèle de projet d’application web Blazor.
Le modèle Starship ( Starship.cs ) de la section Exemple de formulaire de l’article
Composants d’entrée.
Le composant CustomValidation indiqué dans la section Composants du
validateur.

Placez le modèle Starship ( Starship.cs ) dans un projet de bibliothèque de classes


partagée pour que les projets client et serveur puissent utiliser le modèle. Ajoutez ou
mettez à jour l’espace de noms pour qu’il corresponde à l’espace de noms de
l’application partagée (par exemple, namespace BlazorSample.Shared ). Comme le modèle
nécessite des annotations de données, vérifiez que la bibliothèque de classes partagée
utilise le framework partagé ou ajoutez le package
System.ComponentModel.Annotations au projet partagé.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Dans le projet principal de l’application web Blazor, ajoutez un contrôleur pour traiter les
demandes de validation des vaisseaux spatiaux et pour retourner des messages pour les
validations ayant échoué. Mettez à jour les espaces de noms dans la dernière instruction
using pour le projet de bibliothèque de classes partagée et le namespace pour la classe

du contrôleur. En plus de la validation des annotations de données du client et du


serveur, le contrôleur vérifie qu’une valeur est fournie pour la description du navire
( Description ) si l’utilisateur sélectionne la classification du navire Defense
( Classification ).

La validation de la classification des navires Defense se produit uniquement sur le


serveur dans le contrôleur, car le formulaire à venir n’effectue pas la même validation
côté client lorsque le formulaire est envoyé au serveur. La validation du serveur sans
validation côté client est courante dans les applications qui nécessitent une validation
de la logique métier privée de l’entrée utilisateur sur le serveur. Par exemple, des
informations privées provenant de données stockées pour un utilisateur peuvent être
requises pour valider l’entrée utilisateur. Les données privées ne peuvent évidemment
pas être envoyées au client pour une validation du client.
7 Notes

Le contrôleur StarshipValidation dans cette section utilise Microsoft Identity 2.0.


L’API web accepte uniquement les jetons pour les utilisateurs qui ont l’étendue
« API.Access » pour cette API. Une personnalisation supplémentaire est requise si
le nom d’étendue de l’API est différent de API.Access .

Pour plus d’informations sur la sécurité, consultez :

Authentification et autorisation Blazor ASP.NET Core (et les autres articles de


la section BlazorSécurité et nœud Identity)
Documentation sur la plateforme d’identités Microsoft

Controllers/StarshipValidation.cs :

C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers;

[Authorize]
[ApiController]
[Route("[controller]")]
public class StarshipValidationController : ControllerBase
{
private readonly ILogger<StarshipValidationController> logger;

public StarshipValidationController(
ILogger<StarshipValidationController> logger)
{
this.logger = logger;
}

static readonly string[] scopeRequiredByApi = new[] { "API.Access" };

[HttpPost]
public async Task<IActionResult> Post(Starship model)
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

try
{
if (model.Classification == "Defense" &&
string.IsNullOrEmpty(model.Description))
{
ModelState.AddModelError(nameof(model.Description),
"For a 'Defense' ship " +
"classification, 'Description' is required.");
}
else
{
logger.LogInformation("Processing the form asynchronously");

// async ...

return Ok(ModelState);
}
}
catch (Exception ex)
{
logger.LogError("Validation Error: {Message}", ex.Message);
}

return BadRequest(ModelState);
}
}

Vérifiez ou mettez à jour l’espace de noms du contrôleur précédent


( BlazorSample.Server.Controllers ) pour qu’il corresponde à l’espace de noms des
contrôleurs de l’application.

Lorsqu’une erreur de validation de la liaison de modèle se produit sur le serveur, un


ApiController (ApiControllerAttribute) renvoie normalement une réponse de requête
incorrecte par défaut avec un ValidationProblemDetails. La réponse contient plus de
données que les erreurs de validation, comme le montre l’exemple suivant lorsque tous
les champs du formulaire Starfleet Starship Database ne sont pas envoyés et que la
validation du formulaire échoue :

JSON

{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Id": ["The Id field is required."],
"Classification": ["The Classification field is required."],
"IsValidatedDesign": ["This form disallows unapproved ships."],
"MaximumAccommodation": ["Accommodation invalid (1-100000)."]
}
}

7 Notes
Pour illustrer la réponse JSON précédente, vous devez soit désactiver la validation
du client du formulaire pour autoriser l’envoi de formulaires avec des champs vides,
soit utiliser un outil pour envoyer une requête directement à l’API du serveur,
comme Firefox Browser Developer ou Postman .

Si l’API serveur renvoie la réponse JSON par défaut précédente, il est possible pour le
client d’analyser la réponse dans le code du développeur pour obtenir les enfants du
nœud errors pour le traitement des erreurs de validation des formulaires. Il n’est pas
pratique d’écrire du code développeur pour analyser le fichier. L’analyse manuelle du
JSON nécessite la production manuelle d’un Dictionary<string, List<string>> d’erreurs
après l’appel du ReadFromJsonAsync. Idéalement, l’API du serveur ne devrait retourner
que les erreurs de validation, comme le montre l’exemple suivant :

JSON

{
"Id": ["The Id field is required."],
"Classification": ["The Classification field is required."],
"IsValidatedDesign": ["This form disallows unapproved ships."],
"MaximumAccommodation": ["Accommodation invalid (1-100000)."]
}

Pour modifier la réponse de l’API serveur afin qu’elle renvoie uniquement les erreurs de
validation, modifiez le délégué invoqué sur les actions annotées avec
ApiControllerAttribute dans le fichier Program . Pour le point de terminaison de l’API
( /StarshipValidation ), retournez un BadRequestObjectResult avec
ModelStateDictionary. Pour tous les autres points de terminaison de l’API, conservez le
comportement par défaut en renvoyant le résultat de l’objet avec un nouveau
ValidationProblemDetails.

Ajoutez l’espace de noms Microsoft.AspNetCore.Mvc en haut du fichier Program dans le


projet principal de l’application web Blazor :

C#

using Microsoft.AspNetCore.Mvc;

Dans le fichier Program , ajoutez ou mettez à jour la méthode d’extension


AddControllersWithViews suivante, et ajoutez l’appel suivant à
ConfigureApiBehaviorOptions :

C#
builder.Services.AddControllersWithViews()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
if (context.HttpContext.Request.Path == "/StarshipValidation")
{
return new BadRequestObjectResult(context.ModelState);
}
else
{
return new BadRequestObjectResult(
new ValidationProblemDetails(context.ModelState));
}
};
});

Si vous ajoutez des contrôleurs au projet principal de l’application web Blazor pour la
première fois, mappez les points de terminaison du contrôleur quand vous placez le
code précédent qui inscrit des services pour les contrôleurs. L’exemple suivant utilise
des routes de contrôleur par défaut :

C#

app.MapDefaultControllerRoute();

7 Notes

L’exemple précédent inscrit explicitement les services de contrôleur en appelant


AddControllersWithViews pour atténuer les attaques XSRF/CSRF (Cross-Site
Request Forgery) automatiquement. Si vous utilisez simplement AddControllers,
l’anti-falsification n’est pas activée automatiquement.

Pour plus d’informations sur les réponses d’erreur d’échec de routage et de validation
de contrôleur, consultez les ressources suivantes :

Routage vers les actions du contrôleur dans ASP.NET Core


Gérer les erreurs dans les API web ASP.NET Core

Dans le projet .Client , ajoutez le composant CustomValidation indiqué dans la section


Composants du validateur. Mettez à jour l’espace de noms pour qu’il corresponde à
l’application (par exemple, namespace BlazorSample.Client ).
Dans le projet .Client , le formulaire Starfleet Starship Database est mis à jour pour
afficher les erreurs de validation du serveur avec l’aide du composant CustomValidation .
Lorsque l’API serveur renvoie des messages de validation, ils sont ajoutés aux
ValidationMessageStore du composant CustomValidation . Les erreurs sont disponibles
dans le formulaire EditContext pour être affichées par le résumé de validation du
formulaire.

Dans le composant suivant, mettez à jour l’espace de noms du projet partagé ( @using
BlazorSample.Shared ) pour qu’il corresponde à l’espace de noms du projet partagé.

Notez que le formulaire nécessite une autorisation. L’utilisateur doit donc être connecté
à l’application pour accéder au formulaire.

Starship10.razor :

7 Notes

Par défaut, les formulaires basés sur EditForm activent automatiquement la prise
en charge de l’anti-falsification. Le contrôleur doit utiliser
AddControllersWithViews pour inscrire les services de contrôleur et activer
automatiquement la prise en charge de l’anti-falsification pour l’API web.

razor

@page "/starship-10"
@rendermode InteractiveWebAssembly
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship10">


<DataAnnotationsValidator />
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" disabled="@disabled" />
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.Classification"
disabled="@disabled">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</div>
<div>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="Model!.MaximumAccommodation"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="Model!.IsValidatedDesign"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Production Date:
<InputDate @bind-Value="Model!.ProductionDate"
disabled="@disabled" />
</label>
</div>
<div>
<button type="submit" disabled="@disabled">Submit</button>
</div>
<div style="@messageStyles">
@message
</div>
</EditForm>

@code {
private CustomValidation? customValidation;
private bool disabled;
private string? message;
private string messageStyles = "visibility:hidden";
[SupplyParameterFromForm]
public Starship? Model { get; set; }

protected override void OnInitialized() =>


Model ??= new() { ProductionDate = DateTime.UtcNow };

private async Task Submit(EditContext editContext)


{
customValidation?.ClearErrors();

try
{
var response = await Http.PostAsJsonAsync<Starship>(
"StarshipValidation", (Starship)editContext.Model);

var errors = await response.Content


.ReadFromJsonAsync<Dictionary<string, List<string>>>() ??
new Dictionary<string, List<string>>();

if (response.StatusCode == HttpStatusCode.BadRequest &&


errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Validation failed. Status Code:
{response.StatusCode}");
}
else
{
disabled = true;
messageStyles = "color:green";
message = "The form has been processed.";
}
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
catch (Exception ex)
{
Logger.LogError("Form processing error: {Message}", ex.Message);
disabled = true;
messageStyles = "color:red";
message = "There was an error processing the form.";
}
}
}

Le projet .Client d’une application web Blazor doit également inscrire un HttpClient
pour les demandes HTTP POST auprès d’un contrôleur d’API web back-end. Vérifiez ou
ajoutez ce qui suit au fichier Program du projet .Client :

C#

builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new
Uri(builder.HostEnvironment.BaseAddress) });

L’exemple précédent définit l’adresse de base avec


builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress).

Cette propriété récupère l’adresse de base de l’application. Elle est généralement


dérivée de la valeur href de la balise <base> dans la page hôte.

7 Notes

Au lieu d'utiliser un composant de validation, des attributs de validation


d’annotation de données peuvent être utilisés. Les attributs personnalisés appliqués
au modèle du formulaire s’activent avec l’utilisation du composant
DataAnnotationsValidator. Lorsqu’ils sont utilisés avec la validation du serveur, les
attributs doivent être exécutables sur le serveur. Pour plus d’informations, consultez
Validation de modèle dans ASP.NET Core MVC.

InputText en fonction de l’événement d’entrée


Utilisez le composant InputText pour créer un composant personnalisé qui utilise
l’événement oninput (input ) au lieu de l’événement onchange (change ). L’utilisation
de la validation de champ des déclencheurs d’événements input à chaque séquence de
touches.

Le composant CustomInputText suivant hérite du composant InputText de


l’infrastructure et définit la liaison d’événement à l’événement oninput (input ).

CustomInputText.razor :

razor

@inherits InputText

<input @attributes="AdditionalAttributes"
class="@CssClass"
@bind="CurrentValueAsString"
@bind:event="oninput" />
Le composant CustomInputText peut être utilisé n’importe où InputText est utilisé. Le
composant suivant utilise le composant CustomInputText partagé.

Starship11.razor :

razor

@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship11">


<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<CustomInputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>

<div>
CurrentValue: @Model?.Id
</div>

@code {
[SupplyParameterFromForm]
public Starship? Model { get; set; }

protected override void OnInitialized() => Model ??= new();

private void Submit()


{
Logger.LogInformation("Submit called: Processing the form");
}

public class Starship


{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}

Composants du résumé de validation et du


message de validation
Le composant ValidationSummary résume tous les messages de validation, ce qui est
similaire à l’Assistant de balise de résumé de validation :

razor

<ValidationSummary />

Messages de validation de sortie pour un modèle spécifique avec le paramètre Model :

razor

<ValidationSummary Model="Model" />

Le composant ValidationMessage<TValue> affiche des messages de validation pour un


champ spécifique, ce qui est similaire à l’Assistant de balise de message de validation.
Spécifiez le champ à valider avec l’attribut For et une expression lambda nommant la
propriété de modèle :

razor

<ValidationMessage For="@(() => Model!.MaximumAccommodation)" />

Les composants ValidationMessage<TValue> et ValidationSummary prennent en charge


les attributs arbitraires. Tout attribut qui ne correspond pas à un paramètre de
composant est ajouté à l’élément <div> ou <ul> généré.

Contrôlez le style des messages de validation dans la feuille de style de l’application


( wwwroot/css/app.css ou wwwroot/css/site.css ). La classe validation-message par
défaut définit la couleur du texte des messages de validation en rouge :

css

.validation-message {
color: red;
}

Déterminer si un champ de formulaire est


valide
Utilisez EditContext.IsValid pour déterminer si un champ est valide sans obtenir de
messages de validation.
❌ pris en charge, mais pas recommandé :

C#

var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

✔️recommandé :

C#

var isValid = editContext.IsValid(fieldIdentifier);

Attributs de validation personnalisés


Pour vous assurer qu’un résultat de validation est correctement associé à un champ lors
de l’utilisation d’un attribut de validation personnalisé, transmettez le MemberName du
contexte de validation lors de la création du ValidationResult.

CustomValidator.cs :

C#

using System;
using System.ComponentModel.DataAnnotations;

public class CustomValidator : ValidationAttribute


{
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
...

return new ValidationResult("Validation message to user.",


new[] { validationContext.MemberName });
}
}

Injectez des services dans des attributs de validation personnalisés via le


ValidationContext. L’exemple suivant illustre un formulaire « salad chef » qui valide
l’entrée de l’utilisateur avec l’injection de dépendance (DI).

La classe SaladChef indique la liste des navires approuvés pour une salade Ten Forward.

SaladChef.cs :
C#

namespace BlazorSample;

public class SaladChef


{
public string[] SaladToppers = { "Horva", "Kanda Root",
"Krintar", "Plomeek", "Syto Bean" };
}

Inscrivez SaladChef dans le conteneur du DI de l’application dans le fichier Program :

C#

builder.Services.AddTransient<SaladChef>();

La méthode IsValid de la classe SaladChefValidatorAttribute suivante obtient le


service SaladChef à partir du DI pour vérifier l’entrée de l’utilisateur.

SaladChefValidatorAttribute.cs :

C#

using System.ComponentModel.DataAnnotations;

namespace BlazorSample;

public class SaladChefValidatorAttribute : ValidationAttribute


{
protected override ValidationResult? IsValid(object? value,
ValidationContext validationContext)
{
var saladChef = validationContext.GetRequiredService<SaladChef>();

if (saladChef.SaladToppers.Contains(value?.ToString()))
{
return ValidationResult.Success;
}

return new ValidationResult("Is that a Vulcan salad topper?! " +


"The following toppers are available for a Ten Forward salad: "
+
string.Join(", ", saladChef.SaladToppers));
}
}

Le composant suivant valide l’entrée utilisateur en appliquant le


SaladChefValidatorAttribute ( [SaladChefValidator] ) à la chaîne d’ingrédients de la
salade ( SaladIngredient ).

Starship12.razor :

razor

@page "/starship-12"
@inject SaladChef SaladChef

<EditForm Model="this" autocomplete="off" FormName="Starship12">


<DataAnnotationsValidator />
<div>
<label>
Salad topper (@saladToppers):
<input @bind="SaladIngredient" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
<ul>
@foreach (var message in context.GetValidationMessages())
{
<li class="validation-message">@message</li>
}
</ul>
</EditForm>

@code {
private string? saladToppers;

[SaladChefValidator]
public string? SaladIngredient { get; set; }

protected override void OnInitialized() =>


saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}

Attributs de classe CSS de validation


personnalisée
Les attributs de classe CSS de validation personnalisée sont utiles lors de l’intégration à
des infrastructures CSS, telles que Bootstrap .

Pour spécifier des attributs de classe CSS de validation personnalisée, commencez par
fournir des styles CSS pour la validation personnalisée. Dans l’exemple suivant, les styles
valides ( validField ) et non valides ( invalidField ) sont spécifiés.
Ajoutez les classes CSS suivantes à la feuille de style de l’application :

css

.validField {
border-color: lawngreen;
}

.invalidField {
background-color: tomato;
}

Créez une classe dérivée de FieldCssClassProvider qui vérifie les messages de validation
de champ et applique le style valide ou non valide approprié.

CustomFieldClassProvider.cs :

C#

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider : FieldCssClassProvider


{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = editContext.IsValid(fieldIdentifier);

return isValid ? "validField" : "invalidField";


}
}

Définissez la classe CustomFieldClassProvider en tant que Fournisseur de classes CSS de


champ sur l’instance EditContext du formulaire avec SetFieldCssClassProvider.

Starship13.razor :

razor

@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit"


FormName="Starship13">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>

@code {
private EditContext? editContext;

[SupplyParameterFromForm]
public Starship? Model { get; set; }

protected override void OnInitialized()


{
Model ??= new();
editContext = new(Model);
editContext.SetFieldCssClassProvider(new
CustomFieldClassProvider());
}

private void Submit()


{
Logger.LogInformation("Submit called: Processing the form");
}

public class Starship


{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}

L’exemple précédent vérifie la validité de tous les champs de formulaire et applique un


style à chaque champ. Si le formulaire doit appliquer uniquement des styles
personnalisés à un sous-ensemble de champs, faites en sorte que
CustomFieldClassProvider applique les styles de manière conditionnelle. L’exemple
CustomFieldClassProvider2 suivant applique uniquement un style au champ Name . Pour

tous les champs dont les noms ne correspondent pas à Name , string.Empty est renvoyé
et aucun style n’est appliqué. À l’aide de la réflexion, le champ est mis en
correspondance avec la propriété ou le nom de champ du membre du modèle, pas avec
un id affecté à l’entité HTML.

CustomFieldClassProvider2.cs :

C#
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider2 : FieldCssClassProvider


{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
if (fieldIdentifier.FieldName == "Name")
{
var isValid = editContext.IsValid(fieldIdentifier);

return isValid ? "validField" : "invalidField";


}

return string.Empty;
}
}

7 Notes

La correspondance du nom de champ dans l’exemple précédent respecte la casse.


Par conséquent, un membre de la propriété du modèle désigné « Name » doit
correspondre à une vérification conditionnelle sur « Name » :

✔️fieldId.FieldName == "Name"

❌ fieldId.FieldName == "name"

❌ fieldId.FieldName == "NAME"

❌ fieldId.FieldName == "nAmE"

Ajoutez une propriété supplémentaire à Model , par exemple :

C#

[StringLength(10, ErrorMessage = "Description is too long.")]


public string? Description { get; set; }

Ajoutez le Description au formulaire du composant CustomValidationForm :

razor

<InputText @bind-Value="Model!.Description" />

Mettez à jour l’instance EditContext dans la méthode OnInitialized du composant pour


utiliser le nouveau Fournisseur de classes CSS de champ :
C#

editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());

Étant donné qu’une classe de validation CSS n’est pas appliquée au champ Description ,
il n’a pas de style. Toutefois, la validation de champ s’exécute normalement. Si plus de
10 caractères sont fournis, le résumé de validation indique l’erreur :

La description est trop longue.

Dans l’exemple suivant :

Le style CSS personnalisé est appliqué au champ Name .

Tous les autres champs appliquent une logique similaire à la logique par défaut de
Blazor et utilisent les styles de validation CSS de champ par défaut de Blazor,
modified avec valid ou invalid . Notez que pour les styles par défaut, vous n’avez

pas besoin de les ajouter à la feuille de style de l’application si l’application est


basée sur un modèle de projet Blazor. Pour les applications non basées sur un
modèle de projet Blazor, les styles par défaut peuvent être ajoutés à la feuille de
style de l’application :

css

.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}

.invalid {
outline: 1px solid red;
}

CustomFieldClassProvider3.cs :

C#

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider3 : FieldCssClassProvider


{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = editContext.IsValid(fieldIdentifier);

if (fieldIdentifier.FieldName == "Name")
{
return isValid ? "validField" : "invalidField";
}
else
{
if (editContext.IsModified(fieldIdentifier))
{
return isValid ? "modified valid" : "modified invalid";
}
else
{
return isValid ? "valid" : "invalid";
}
}
}
}

Mettez à jour l’instance EditContext dans la méthode OnInitialized du composant pour


utiliser le Fournisseur de classes CSS de champ précédent :

C#

editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());

À l’aide de CustomFieldClassProvider3 :

Le champ Name utilise les styles CSS de validation personnalisée de l’application.


Le champ Description utilise une logique similaire à la logique de Blazor et aux
styles de validation CSS de champ par défaut de Blazor.

Package de validation des annotations de


donnéesBlazor
Le Microsoft.AspNetCore.Components.DataAnnotations.Validation est un package qui
comble les lacunes en matière d’expérience de validation à l’aide du composant
DataAnnotationsValidator. Le package est actuellement expérimental.

2 Avertissement

Le package Microsoft.AspNetCore.Components.DataAnnotations.Validation
dispose de la dernière version de la version Release Candidate sur NuGet.org .
Continuez à utiliser le package expérimental de la version Release Candidate pour
l’instant. Des fonctionnalités expérimentales sont fournies pour explorer la viabilité
des fonctionnalités et peuvent ne pas être livrées dans une version stable. Regardez
le référentiel GitHub Annonces , le référentiel GitHub dotnet/aspnetcore , ou
cette section de rubrique pour de plus amples informations.

Modèles imbriqués, types de collection et types


complexes
Blazor prend en charge la validation de l’entrée de formulaire à l’aide d’annotations de
données avec le intégré DataAnnotationsValidator. Toutefois, le
DataAnnotationsValidator valide uniquement les propriétés de niveau supérieur du
modèle liées au formulaire qui ne sont pas des propriétés de type collection ou de type
complexe.

Pour valider l’ensemble du graphe d’objets du modèle lié, y compris les propriétés de
type collection et de type complexe, utilisez le ObjectGraphDataAnnotationsValidator
fourni par le package
expérimentalMicrosoft.AspNetCore.Components.DataAnnotations.Validation :

razor

<EditForm ...>
<ObjectGraphDataAnnotationsValidator />
...
</EditForm>

Annotez les propriétés du modèle avec [ValidateComplexType] . Dans les classes de


modèle suivantes, la classe ShipDescription contient des annotations de données
supplémentaires à valider lorsque le modèle est lié au formulaire :

Starship.cs :

C#

using System;
using System.ComponentModel.DataAnnotations;

public class Starship


{
...

[ValidateComplexType]
public ShipDescription ShipDescription { get; set; } = new();
...
}

ShipDescription.cs :

C#

using System;
using System.ComponentModel.DataAnnotations;

public class ShipDescription


{
[Required]
[StringLength(40, ErrorMessage = "Description too long (40 char).")]
public string? ShortDescription { get; set; }

[Required]
[StringLength(240, ErrorMessage = "Description too long (240 char).")]
public string? LongDescription { get; set; }
}

Activez le bouton d’envoi en fonction de la


validation du formulaire
Pour activer et désactiver le bouton d’envoi en fonction de la validation de formulaire,
l’exemple suivant :

Utilise une version abrégée du formulaire Starfleet Starship Database précédent


(composant Starship3 ) de la section Exemple de formulaire de l’article
Composants d’entrée qui accepte seulement une valeur pour l’ID du vaisseau. Les
autres propriétés Starship reçoivent des valeurs par défaut valides lorsqu’une
instance du type Starship est créée.
Utilise le formulaire EditContext pour affecter le modèle lorsque le composant est
initialisé.
Valide le formulaire dans le rappel OnFieldChanged du contexte pour activer et
désactiver le bouton d’envoi.
Implémente IDisposable et désinscrit le gestionnaire d’événements dans la
méthode Dispose . Pour plus d’informations, consultez le cycle de vie des
composants Razor ASP.NET Core.

7 Notes
Lors de l’affectation à EditForm.EditContext, n’affectez pas non plus un
EditForm.Model au EditForm.

Starship14.razor :

razor

@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit"


FormName="Starship14">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit" disabled="@formInvalid">Submit</button>
</div>
</EditForm>

@code {
private bool formInvalid = false;
private EditContext? editContext;

[SupplyParameterFromForm]
private Starship? Model { get; set; }

protected override void OnInitialized()


{
Model ??=
new()
{
Id = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
editContext = new(Model);
editContext.OnFieldChanged += HandleFieldChanged;
}

private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)


{
if (editContext is not null)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
}

private void Submit()


{
Logger.LogInformation("Submit called: Processing the form");
}

public void Dispose()


{
if (editContext is not null)
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
}

Si un formulaire n’est pas préchargé avec des valeurs valides et que vous souhaitez
désactiver le bouton Submit lors du chargement du formulaire, définissez formInvalid
sur true .

L’un des effets secondaires de l’approche précédente consiste à remplir un résumé de


validation (composant ValidationSummary) avec des champs non valides après que
l’utilisateur a interagi avec un champ. Traitez ce scénario de l’une des manières
suivantes :

N’utilisez pas de composant ValidationSummary sur le formulaire.


Rendez le composant ValidationSummary visible lorsque le bouton d’envoi est
sélectionné (par exemple, dans une méthode Submit ).

razor

<EditForm ... EditContext="editContext" OnValidSubmit="Submit" ...>


<DataAnnotationsValidator />
<ValidationSummary style="@displaySummary" />

...

<button type="submit" disabled="@formInvalid">Submit</button>


</EditForm>

@code {
private string displaySummary = "display:none";

...

private void Submit()


{
displaySummary = "display:block";
}
}

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Résoudre les problèmes liés aux
formulaires Blazor ASP.NET Core
Article • 05/01/2024

Cet article fournit de l’aide pour la résolution des problèmes liés aux formulaires Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Charges utiles volumineuses du formulaire et


limite de taille de message SignalR
Cette section s’applique uniquement aux Web Apps Blazor, aux applications Blazor Server
et aux solutions hébergées Blazor WebAssembly qui implémentent SignalR.

Si le traitement des formulaires échoue, car la charge utile du formulaire du composant


a dépassé la taille maximale autorisée des messages SignalR entrants pour les méthodes
hub, le formulaire peut adopter la diffusion en continu JS de l’interopérabilité sans
augmenter la limite de taille du message. Pour plus d’informations sur la limite de taille
et l’erreur levée, consultez Conseils pour BlazorSignalR ASP.NET Core.

Dans l’exemple suivant, une zone de texte ( <textarea> ) est utilisée avec
l’interopérabilité de la diffusion en continu JS pour passer à 50 000 octets de données
vers le serveur.

Ajouter une fonction JavaScript (JS) getText à l’application :

JavaScript

window.getText = (elem) => {


const textValue = elem.value;
const utf8Encoder = new TextEncoder();
const encodedTextValue = utf8Encoder.encode(textValue);
return encodedTextValue;
};

Pour plus d’informations sur le lieu où disposer JS dans une application Blazor, consultez
Interopérabilité JavaScript BlazorASP.NET Core (interopérabilité JS).

En raison des considérations de sécurité, les streams à longueur nulle ne sont pas
autorisés pour l’interopérabilité de diffusion en continu JS. Par conséquent, le
composant StreamFormData suivant intercepte un JSException et retourne une chaîne
vide si la zone de texte est vide lorsque le formulaire est envoyé.

StreamFormData.razor :

razor
@page "/stream-form-data"
@inject IJSRuntime JS
@inject ILogger<StreamFormData> Logger

<h1>Stream form data with JS interop</h1>

<EditForm Model="@this" OnSubmit="@Submit" FormName="StreamFormData">


<div>
<label>
&lt;textarea&gt; value streamed for assignment to
<code>TextAreaValue (&lt;= 50,000 characters)</code>:
<textarea @ref="largeTextArea" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>

<div>
Length: @TextAreaValue?.Length
</div>

@code {
private ElementReference largeTextArea;

public string? TextAreaValue { get; set; }

protected override void OnInitialized() =>


TextAreaValue ??= string.Empty;

private async Task Submit()


{
TextAreaValue = await GetTextAsync();

Logger.LogInformation("TextAreaValue length: {Length}",


TextAreaValue.Length);
}

public async Task<string> GetTextAsync()


{
try
{
var streamRef =
await JS.InvokeAsync<IJSStreamReference>("getText",
largeTextArea);
var stream = await streamRef.OpenReadStreamAsync(maxAllowedSize:
50_000);
var streamReader = new StreamReader(stream);

return await streamReader.ReadToEndAsync();


}
catch (JSException jsException)
{
if (jsException.InnerException is
ArgumentOutOfRangeException outOfRangeException &&
outOfRangeException.ActualValue is not null &&
outOfRangeException.ActualValue is long actualLength &&
actualLength == 0)
{
return string.Empty;
}

throw;
}
}
}

Erreur du paramètre EditForm


InvalidOperationException : EditForm nécessite un paramètre Model ou un
paramètre EditContext, mais pas les deux.

Vérifiez que le EditForm affecte un Modelou un EditContext. N’utilisez pas les deux pour
le même formulaire.

Lors de l’affectation à Model, vérifiez que le type de modèle est instancié.

Connexion déconnectée
Erreur : Connexion déconnectée avec l’erreur « Erreur : Le serveur a retourné une
erreur à la fermeture : Connexion fermée avec une erreur. ».

System.IO.InvalidDataException : la taille maximale du message de 32768 octets a


été dépassée. La taille du message peut être configurée dans AddHubOptions.

Pour plus d’informations et d’instructions, consultez les ressources suivantes :

Charges utiles volumineuses du formulaire et SignalR limite de taille de message


Conseils pour BlazorSignalR ASP.NET Core

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Chargements de fichiers Blazor ASP.NET
Core
Article • 19/12/2023

Cet article explique comment charger des fichiers dans Blazor avec le composant
InputFile.

Dans cet article, les termes client/côté client et serveur/côté serveur sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.
Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une
application web Blazor.

Pour obtenir des conseils sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Les exemples de composants interactifs présentés dans la documentation n’indiquent


pas de mode d’affichage interactif. Pour rendre les exemples interactifs, vous devez soit
hériter d’un mode d’affichage interactif pour un composant enfant à partir d’un
composant parent, soit appliquer un mode d’affichage interactif à une définition de
composant, soit définir globalement le mode d’affichage pour l’ensemble de
l’application. La meilleure façon d’exécuter le code de démonstration consiste à
télécharger les applications d’exemple BlazorSample_{PROJECT TYPE} à partir du
référentiel GitHub dotnet/blazor-samples .

2 Avertissement

Suivez toujours les bonnes pratiques de sécurité quand il s’agit d’autoriser des
utilisateurs à charger des fichiers. Pour plus d’informations, consultez Charger des
fichiers dans ASP.NET Core.

Utilisez le composant InputFile pour lire des données de fichiers de navigateur dans du
code .NET. Le composant InputFile assure le rendu d’un élément <input> HTML de type
file . Par défaut, l’utilisateur sélectionne des fichiers uniques. Ajoutez l’attribut multiple
pour autoriser l’utilisateur à charger plusieurs fichiers à la fois.
La sélection de fichiers n’est pas cumulative quand un composant InputFile ou son
élément HTML <input type="file"> sous-jacent est utilisé. Vous ne pouvez donc pas
ajouter de fichiers à une sélection de fichiers existante. Le composant remplace toujours
la sélection de fichier initiale de l’utilisateur, si bien que les références de fichiers des
sélections précédentes ne sont pas disponibles.

Le composant InputFile suivant exécute la méthode LoadFiles quand l’événement


OnChange (change ) se produit. Un InputFileChangeEventArgs donne accès à la liste
de fichiers sélectionnée et aux détails sur chaque fichier :

razor

<InputFile OnChange="@LoadFiles" multiple />

@code {
private void LoadFiles(InputFileChangeEventArgs e)
{
...
}
}

HTML rendu :

HTML

<input multiple="" type="file" _bl_2="">

7 Notes

Dans l’exemple précédent, l’attribut <input> de l’élément _bl_2 est utilisé pour le
traitement interne de Blazor.

Pour lire les données d’un fichier sélectionné par l’utilisateur, appelez
IBrowserFile.OpenReadStream sur le fichier et lisez à partir du flux retourné. Pour plus
d’informations, consultez la section Flux de fichiers.

OpenReadStream applique une taille maximale en octets de son Stream. La lecture d’un
ou plusieurs fichiers de plus de 500 Ko provoque une exception. Cette limite empêche
les développeurs de lire accidentellement de gros fichiers en mémoire. Le paramètre
maxAllowedSize de OpenReadStream peut être utilisé pour spécifier une plus grande

taille si nécessaire.
Si vous avez besoin d’accéder à un Stream qui représente les octets du fichier, utilisez
IBrowserFile.OpenReadStream. Évitez de lire le flux de fichiers entrant directement en
mémoire de façon simultanée. Par exemple, ne copiez pas tous les octets du fichier dans
un MemoryStream ou ne lisez pas le flux entier dans un tableau d’octets simultanément.
Ces approches peuvent occasionner des problèmes en termes de niveau de
performance et de sécurité, en particulier pour les composants côté serveur. À la place,
vous pouvez adopter l’une des approches suivantes :

Copiez le flux directement dans un fichier sur disque sans le lire en mémoire.
Notez que les applications Blazor exécutant du code sur le serveur ne peuvent pas
accéder directement au système de fichiers du client.
Chargez les fichiers du client directement dans un service externe. Pour plus
d’informations, consultez la section Charger des fichiers dans un service externe.

Dans les exemples suivants, browserFile représente le fichier chargé et implémente


IBrowserFile. Des implémentations fonctionnelles pour IBrowserFile sont présentées
dans les composants de chargement de fichiers plus loin dans cet article.

❌ L’approche suivante n’est PAS recommandée, car le contenu Stream du fichier est lu
dans un String en mémoire ( reader ) :

C#

var reader =
await new StreamReader(browserFile.OpenReadStream()).ReadToEndAsync();

❌ L’approche suivante n’est PAS recommandée pour Stockage Blob Microsoft Azure,
car le contenu Stream du fichier est copié dans un MemoryStream en mémoire
( memoryStream ) avant d’appeler UploadBlobAsync :

C#

var memoryStream = new MemoryStream();


browserFile.OpenReadStream().CopyToAsync(memoryStream);
await blobContainerClient.UploadBlobAsync(
trustedFileName, memoryStream));

✔️L’approche suivante est recommandée, car le Stream du fichier est fourni


directement au consommateur, un FileStream qui crée le fichier dans le chemin fourni :

C#

await using FileStream fs = new(path, FileMode.Create);


await browserFile.OpenReadStream().CopyToAsync(fs);

✔ L’approche suivante est recommandée pour Stockage Blob Microsoft Azure, car le
Stream du fichier est fourni directement à UploadBlobAsync :

C#

await blobContainerClient.UploadBlobAsync(
trustedFileName, browserFile.OpenReadStream());

Un composant qui reçoit un fichier image peut appeler la méthode pratique


BrowserFileExtensions.RequestImageFileAsync sur le fichier pour redimensionner les
données d’image dans le runtime JavaScript du navigateur avant que l’image soit
diffusée dans l’application. Les cas d’usage pour l’appel de RequestImageFileAsync sont
plus appropriés pour les applications Blazor WebAssembly.

Limites de taille de fichier en lecture et


chargement
Côté serveur ou côté client, il n’existe aucune limite de taille de lecture ou de
chargement de fichier spécifiquement pour le composant InputFile. Toutefois, Blazor
côté client lit les octets du fichier dans une mémoire tampon de taille fixe (Array Buffer)
JavaScript unique pendant le marshaling des données de JavaScript à C#, limité à 2 Go
ou à la mémoire disponible de l’appareil. Les chargements de fichiers volumineux (>
250 Mo) peuvent échouer pour les chargements côté client utilisant le composant
InputFile. Pour plus d’informations, consultez les discussions suivantes :

Le Blazor composant InputFile doit gérer la segmentation lorsque le fichier est


chargé (dotnet/runtime #84685)
Demander le chargement de streaming via le gestionnaire HTTP (dotnet/runtime
#36634)

Pour les chargements de fichiers côté client volumineux qui échouent lors de la tentative
d’utilisation du composant InputFile, nous vous recommandons de segmenter les
fichiers volumineux avec un composant personnalisé à l’aide de plusieurs requêtes de
plage HTTP au lieu d’utiliser le composant InputFile.

Des travaux sont actuellement planifiés pour .NET 9 (fin 2024) afin de répondre à la
limitation du chargement de taille de fichier côté client.

Examples
Les exemples suivants illustrent le chargement de plusieurs fichiers dans un composant.
InputFileChangeEventArgs.GetMultipleFiles permet la lecture de plusieurs fichiers.
Spécifiez le nombre maximal de fichiers pour empêcher un utilisateur malveillant de
charger un nombre de fichiers supérieur à celui attendu par l’application.
InputFileChangeEventArgs.File permet la lecture du seul et unique fichier si le
chargement de fichier ne prend pas en charge plusieurs fichiers.

InputFileChangeEventArgs se trouve dans l’espace de noms


Microsoft.AspNetCore.Components.Forms, qui est généralement l’un des espaces de
noms contenus dans le fichier _Imports.razor de l’application. Quand l’espace de noms
est présent dans le fichier _Imports.razor , les membres de l’API ont accès aux
composants de l’application.

Les espaces de noms contenus dans le fichier _Imports.razor ne s’appliquent pas aux
fichiers C# ( .cs ). Les fichiers C# nécessitent une directive using explicite en haut du
fichier de classe :

razor

using Microsoft.AspNetCore.Components.Forms;

Pour tester les composants de chargement de fichiers, vous pouvez créer des fichiers de
test de toute taille avec PowerShell :

PowerShell

$out = new-object byte[] {SIZE}; (new-object Random).NextBytes($out);


[IO.File]::WriteAllBytes('{PATH}', $out)

Dans la commande précédente :

L’espace réservé {SIZE} correspond à la taille du fichier en octets (par exemple,


2097152 pour un fichier de 2 Mo).

L’espace réservé {PATH} correspond au chemin et au fichier avec l’extension de


fichier (par exemple, D:/test_files/testfile2MB.txt ).

Exemple de chargement de fichier côté serveur


Pour utiliser le code suivant, créez un Development/unsafe_uploads dossier à la racine
de l’application s’exécutant dans l’environnement Development .
Sachant que l’exemple utilise l’environnement de l’application dans le chemin où sont
enregistrés les fichiers, des dossiers supplémentaires sont nécessaires si d’autres
environnements sont utilisés en phase de test et en production. Par exemple, créez un
dossier Staging/unsafe_uploads pour l’environnement Staging . Créez un dossier
Production/unsafe_uploads pour l’environnement Production .

2 Avertissement

L’exemple enregistre des fichiers sans analyser leur contenu, et l’aide fournie dans
cet article ne prend pas en compte les autres bonnes pratiques de sécurité pour les
fichiers chargés. Sur les systèmes de mise en lots et de production, désactivez
l’autorisation d’exécution sur le dossier de chargement et analysez les fichiers avec
une API d’analyseur antivirus/anti-programme malveillant de suite après le
chargement. Pour plus d’informations, consultez Charger des fichiers dans ASP.NET
Core.

FileUpload1.razor :

razor

@page "/file-upload-1"
@rendermode InteractiveServer
@inject ILogger<FileUpload1> Logger
@inject IHostEnvironment Environment

<h3>Upload Files</h3>

<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>

<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>

<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
</p>
@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}

<p>
@message
</p>

@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private string? message;

private async Task LoadFiles(InputFileChangeEventArgs e)


{
isLoading = true;
loadedFiles.Clear();
message = string.Empty;

if (e.FileCount > maxAllowedFiles)


{
message = $"Try again with no more than {maxAllowedFiles}
files.";
}
else
{
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
var trustedFileNameForFileStorage =
Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);
await using FileStream fs = new(path, FileMode.Create);

try
{
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
loadedFiles.Add(file);
}
catch (Exception ex)
{
fs.Close();
File.Delete(path);
Logger.LogError("File: {Filename} Error: {Error}",
file.Name, ex.Message);
message = "Upload error(s). See logs for details.";
}
}
}

isLoading = false;
}
}

Exemple de chargement de fichier côté client


L’exemple suivant traite les octets de fichiers et n’envoie pas de fichiers vers une
destination extérieure à l’application. Pour trouverez un exemple de composant Razor
qui envoie un fichier à un serveur ou un service dans les sections suivantes :

Charger des fichiers sur un serveur


Charger des fichiers dans un service externe

FileUpload1.razor :

razor

@page "/file-upload-1"
@rendermode InteractiveWebAssembly
@inject ILogger<FileUpload1> Logger

<h3>Upload Files</h3>

<p>
<label>
Max file size:
<input type="number" @bind="maxFileSize" />
</label>
</p>

<p>
<label>
Max allowed files:
<input type="number" @bind="maxAllowedFiles" />
</label>
</p>

<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
</p>

@if (isLoading)
{
<p>Uploading...</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}

@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;

private void LoadFiles(InputFileChangeEventArgs e)


{
isLoading = true;
loadedFiles.Clear();

foreach (var file in e.GetMultipleFiles(maxAllowedFiles))


{
try
{
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
}
}

isLoading = false;
}
}

IBrowserFile retourne les métadonnées exposées par le navigateur sous forme de


propriétés. Utilisez ces métadonnées pour la validation préliminaire.

Name
Size
LastModified
ContentType

Ne vous fiez jamais aux valeurs des propriétés précédentes, en particulier celles de la
propriété Name, pour l’affichage dans l’interface utilisateur. Considérez toutes les
données fournies par l’utilisateur comme un risque de sécurité important pour
l’application, le serveur et le réseau. Pour plus d’informations, consultez Charger des
fichiers dans ASP.NET Core.

Charger des fichiers sur un serveur avec rendu


côté serveur
Cette section s’applique aux composants de serveur interactif dans les applications web
Blazor..

L’exemple suivant illustre le chargement de fichiers d’une application côté serveur sur un
contrôleur d’API web back-end dans une application distincte, éventuellement sur un
serveur distinct.

Dans le fichier Program de l’application côté serveur, ajoutez IHttpClientFactory et les


services associés qui permettent à l’application de créer des instances HttpClient :

C#

builder.Services.AddHttpClient();

Pour plus d’informations, consultez Effectuer des requêtes HTTP en utilisant


IHttpClientFactory dans ASP.NET Core.

Pour les exemples de cette section :

L’API web s’exécute à l’URL : https://localhost:5001


L’application côté serveur s’exécute à l’URL : https://localhost:5003
Pour les tests, les URL précédentes sont configurées dans les fichiers
Properties/launchSettings.json des projets.

La classe UploadResult suivante conserve le résultat d’un fichier chargé. Quand le


chargement d’un fichier échoue sur le serveur, un code d’erreur est retourné dans
ErrorCode pour l’afficher à l’utilisateur. Un nom de fichier sécurisé est généré sur le

serveur pour chaque fichier et retourné au client dans StoredFileName pour affichage.
Une clé est ajoutée aux fichiers entre le client et le serveur en utilisant le nom de fichier
non sécurisé/non approuvé dans FileName .

UploadResult.cs :

C#

public class UploadResult


{
public bool Uploaded { get; set; }
public string? FileName { get; set; }
public string? StoredFileName { get; set; }
public int ErrorCode { get; set; }
}

7 Notes

Pour les applications de production, une bonne pratique de sécurité consiste à


éviter d’envoyer des messages d’erreur aux clients qui pourraient divulguer des
informations sensibles sur une application, un serveur ou un réseau. Des messages
d’erreur détaillés entre les mains d’un utilisateur malveillant peuvent l’aider à
préparer des attaques sur une application, un serveur ou un réseau. L’exemple de
code figurant dans cette section renvoie uniquement un numéro de code d’erreur
( int ) qui sera affiché par le composant côté client si une erreur côté serveur se
produit. Si un utilisateur a besoin d’une assistance par rapport à un chargement de
fichier, il fournira le code d’erreur au personnel du support technique pour la
résolution du ticket de support sans jamais connaître la cause exacte de l’erreur.

Le composant FileUpload2 suivant :

Permet aux utilisateurs de charger des fichiers depuis le client.


Affiche le nom de fichier non approuvé/non sécurisé fourni par le client dans
l’interface utilisateur. Le nom de fichier non approuvé/non sécurisé est
automatiquement encodé au format HTML par Razor pour un affichage sécurisé
dans l’interface utilisateur.
2 Avertissement

Ne faites pas confiance aux noms de fichiers fournis par les clients pour :

L’enregistrement du fichier dans un système de fichiers ou un service.


Un affichage dans des interfaces utilisateur qui n’encodent pas les noms de
fichiers automatiquement ou via le code du développeur.

Pour plus d’informations sur les considérations de sécurité lors du chargement de


fichiers sur un serveur, consultez Charger des fichiers dans ASP.NET Core.

FileUpload2.razor :

razor

@page "/file-upload-2"
@rendermode InteractiveServer
@using System.Net.Http.Headers
@using System.Text.Json
@inject IHttpClientFactory ClientFactory
@inject ILogger<FileUpload2> Logger

<h1>Upload Files</h1>

<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="@OnInputFileChange" multiple />
</label>
</p>

@if (files.Count > 0)


{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name

@if (FileUpload(uploadResults, file.Name, out var


result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}

<p>
@message
</p>

@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
private string? message;

protected override bool ShouldRender() => shouldRender;

private async Task OnInputFileChange(InputFileChangeEventArgs e)


{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
uploadResults.Clear();
files.Clear();

using var content = new MultipartFormDataContent();

if (e.FileCount > maxAllowedFiles)


{
message = $"Try again with no more than {maxAllowedFiles}
files.";
}
else
{
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });

var fileContent =
new
StreamContent(file.OpenReadStream(maxFileSize));
fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);

content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);

upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 5): {Message}",
file.Name, ex.Message);

uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 5,
Uploaded = false
});
}
}
}

message = string.Empty;
}

if (upload)
{
var client = ClientFactory.CreateClient();

var response =
await client.PostAsync("https://localhost:7029/Filesave",
content);

if (response.IsSuccessStatusCode)
{
var options =
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};

using var responseStream =


await response.Content.ReadAsStreamAsync();

var newUploadResults = await JsonSerializer


.DeserializeAsync<IList<UploadResult>>(responseStream,
options);

if (newUploadResults is not null)


{
uploadResults =
uploadResults.Concat(newUploadResults).ToList();
}
}
}

shouldRender = true;
}

private static bool FileUpload(IList<UploadResult> uploadResults,


string? fileName, out UploadResult result)
{
result = uploadResults
.SingleOrDefault(f => f.FileName == fileName) ?? new();

return result.Uploaded;
}

private class File


{
public string? Name { get; set; }
}
}

Le contrôleur ci-dessous du projet d’API web enregistre les fichiers chargés depuis le
client.

) Important

Le contrôleur de cette section est destiné à être utilisé dans un projet d’API web
distinct de l’application Blazor. L’API web doit atténuer les attaques XSRF/CSRF
(Cross-Site Request Forgery) si les utilisateurs de chargement de fichiers sont
authentifiés.

Pour utiliser le code suivant, créez un dossier Development/unsafe_uploads à la racine


du projet d’API web pour l’application s’exécutant dans l’environnement Development .

Sachant que l’exemple utilise l’environnement de l’application dans le chemin où sont


enregistrés les fichiers, des dossiers supplémentaires sont nécessaires si d’autres
environnements sont utilisés en phase de test et en production. Par exemple, créez un
dossier Staging/unsafe_uploads pour l’environnement Staging . Créez un dossier
Production/unsafe_uploads pour l’environnement Production .

2 Avertissement
L’exemple enregistre des fichiers sans analyser leur contenu, et l’aide fournie dans
cet article ne prend pas en compte les autres bonnes pratiques de sécurité pour les
fichiers chargés. Sur les systèmes de mise en lots et de production, désactivez
l’autorisation d’exécution sur le dossier de chargement et analysez les fichiers avec
une API d’analyseur antivirus/anti-programme malveillant de suite après le
chargement. Pour plus d’informations, consultez Charger des fichiers dans ASP.NET
Core.

Controllers/FilesaveController.cs :

C#

using System.Net;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("[controller]")]
public class FilesaveController : ControllerBase
{
private readonly IHostEnvironment env;
private readonly ILogger<FilesaveController> logger;

public FilesaveController(IHostEnvironment env,


ILogger<FilesaveController> logger)
{
this.env = env;
this.logger = logger;
}

[HttpPost]
public async Task<ActionResult<IList<UploadResult>>> PostFile(
[FromForm] IEnumerable<IFormFile> files)
{
var maxAllowedFiles = 3;
long maxFileSize = 1024 * 15;
var filesProcessed = 0;
var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
List<UploadResult> uploadResults = new();

foreach (var file in files)


{
var uploadResult = new UploadResult();
string trustedFileNameForFileStorage;
var untrustedFileName = file.FileName;
uploadResult.FileName = untrustedFileName;
var trustedFileNameForDisplay =
WebUtility.HtmlEncode(untrustedFileName);

if (filesProcessed < maxAllowedFiles)


{
if (file.Length == 0)
{
logger.LogInformation("{FileName} length is 0 (Err: 1)",
trustedFileNameForDisplay);
uploadResult.ErrorCode = 1;
}
else if (file.Length > maxFileSize)
{
logger.LogInformation("{FileName} of {Length} bytes is "
+
"larger than the limit of {Limit} bytes (Err: 2)",
trustedFileNameForDisplay, file.Length,
maxFileSize);
uploadResult.ErrorCode = 2;
}
else
{
try
{
trustedFileNameForFileStorage =
Path.GetRandomFileName();
var path = Path.Combine(env.ContentRootPath,
env.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);

await using FileStream fs = new(path,


FileMode.Create);
await file.CopyToAsync(fs);

logger.LogInformation("{FileName} saved at {Path}",


trustedFileNameForDisplay, path);
uploadResult.Uploaded = true;
uploadResult.StoredFileName =
trustedFileNameForFileStorage;
}
catch (IOException ex)
{
logger.LogError("{FileName} error on upload (Err:
3): {Message}",
trustedFileNameForDisplay, ex.Message);
uploadResult.ErrorCode = 3;
}
}

filesProcessed++;
}
else
{
logger.LogInformation("{FileName} not uploaded because the "
+
"request exceeded the allowed {Count} of files (Err:
4)",
trustedFileNameForDisplay, maxAllowedFiles);
uploadResult.ErrorCode = 4;
}
uploadResults.Add(uploadResult);
}

return new CreatedResult(resourcePath, uploadResults);


}
}

Dans le code précédent, GetRandomFileName est appelé pour générer un nom de


fichier sécurisé. N’approuvez jamais le nom de fichier fourni par le navigateur, car un
attaquant peut choisir un nom de fichier existant qui remplace un fichier existant ou
envoyer un chemin qui tente d’écrire en dehors de l’application.

L’application serveur doit inscrire les services de contrôleur et mapper les points de
terminaison du contrôleur. Pour plus d’informations, consultez Routage vers des actions
de contrôleur dans ASP.NET Core.

Charger des fichiers sur un serveur


L’exemple suivant illustre le chargement de fichiers sur un contrôleur d’API web.

La classe UploadResult suivante conserve le résultat d’un fichier chargé. Quand le


chargement d’un fichier échoue sur le serveur, un code d’erreur est retourné dans
ErrorCode pour l’afficher à l’utilisateur. Un nom de fichier sécurisé est généré sur le

serveur pour chaque fichier et retourné au client dans StoredFileName pour affichage.
Une clé est ajoutée aux fichiers entre le client et le serveur en utilisant le nom de fichier
non sécurisé/non approuvé dans FileName .

UploadResult.cs :

C#

public class UploadResult


{
public bool Uploaded { get; set; }
public string? FileName { get; set; }
public string? StoredFileName { get; set; }
public int ErrorCode { get; set; }
}

7 Notes

La classe UploadResult précédente peut être partagée entre les projets basés client
et serveur. Lorsque les projets du client et du serveur partagent la classe, ajoutez
une importation aux fichiers _Imports.razor de chaque projet pour le projet
partagé. Par exemple :

razor

@using BlazorSample.Shared

Le composant FileUpload2 suivant :

Permet aux utilisateurs de charger des fichiers depuis le client.


Affiche le nom de fichier non approuvé/non sécurisé fourni par le client dans
l’interface utilisateur. Le nom de fichier non approuvé/non sécurisé est
automatiquement encodé au format HTML par Razor pour un affichage sécurisé
dans l’interface utilisateur.

Pour les applications de production, une bonne pratique de sécurité consiste à éviter
d’envoyer des messages d’erreur aux clients qui pourraient divulguer des informations
sensibles sur une application, un serveur ou un réseau. Des messages d’erreur détaillés
entre les mains d’un utilisateur malveillant peuvent l’aider à préparer des attaques sur
une application, un serveur ou un réseau. L’exemple de code figurant dans cette section
renvoie uniquement un numéro de code d’erreur ( int ) qui sera affiché par le
composant côté client si une erreur côté serveur se produit. Si un utilisateur a besoin
d’une assistance par rapport à un chargement de fichier, il fournira le code d’erreur au
personnel du support technique pour la résolution du ticket de support sans jamais
connaître la cause exacte de l’erreur.

2 Avertissement

Ne faites pas confiance aux noms de fichiers fournis par les clients pour :

L’enregistrement du fichier dans un système de fichiers ou un service.


Un affichage dans des interfaces utilisateur qui n’encodent pas les noms de
fichiers automatiquement ou via le code du développeur.

Pour plus d’informations sur les considérations de sécurité lors du chargement de


fichiers sur un serveur, consultez Charger des fichiers dans ASP.NET Core.

Dans le projet principal d’application web Blazor, ajoutez IHttpClientFactory et les


services associés dans le fichier Program du projet :

C#
builder.Services.AddHttpClient();

Pour plus d’informations, consultez Effectuer des requêtes HTTP en utilisant


IHttpClientFactory dans ASP.NET Core.

Le projet de client d’une application web Blazor doit également inscrire un HttpClient
pour les requêtes HTTP POST auprès d’un contrôleur d’API web back-end. Validez ou
ajoutez ce qui suit au fichier Program du projet de client :

C#

builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new
Uri(builder.HostEnvironment.BaseAddress) });

Ajoutez l’attribut de rendu du compossant WebAssembly en haut du composant suivant


dans une Web App Blazor :

razor

@rendermode InteractiveWebAssembly

FileUpload2.razor :

razor

@page "/file-upload-2"
@using System.Linq
@using System.Net.Http.Headers
@inject HttpClient Http
@inject ILogger<FileUpload2> Logger

<h1>Upload Files</h1>

<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="@OnInputFileChange" multiple />
</label>
</p>

@if (files.Count > 0)


{
<div class="card">
<div class="card-body">
<ul>
@foreach (var file in files)
{
<li>
File: @file.Name
<br>
@if (FileUpload(uploadResults, file.Name, out var
result))
{
<span>
Stored File Name: @result.StoredFileName
</span>
}
else
{
<span>
There was an error uploading the file
(Error: @result.ErrorCode).
</span>
}
</li>
}
</ul>
</div>
</div>
}

<p>
@message
</p>

@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
private string? message;

protected override bool ShouldRender() => shouldRender;

private async Task OnInputFileChange(InputFileChangeEventArgs e)


{
shouldRender = false;
long maxFileSize = 1024 * 15;
var upload = false;
message = string.Empty;

using var content = new MultipartFormDataContent();

if (e.FileCount > maxAllowedFiles)


{
message = $"Try again with no more than {maxAllowedFiles}
files.";
}
else
{
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
try
{
files.Add(new() { Name = file.Name });

var fileContent =
new
StreamContent(file.OpenReadStream(maxFileSize));

fileContent.Headers.ContentType =
new MediaTypeHeaderValue(file.ContentType);

content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);

upload = true;
}
catch (Exception ex)
{
Logger.LogInformation(
"{FileName} not uploaded (Err: 5): {Message}",
file.Name, ex.Message);

uploadResults.Add(
new()
{
FileName = file.Name,
ErrorCode = 5,
Uploaded = false
});
}
}
}
}

if (upload)
{
var response = await Http.PostAsync("/Filesave", content);

var newUploadResults = await response.Content


.ReadFromJsonAsync<IList<UploadResult>>();

if (newUploadResults is not null)


{
uploadResults =
uploadResults.Concat(newUploadResults).ToList();
}
}

shouldRender = true;
}

private static bool FileUpload(IList<UploadResult> uploadResults,


string? fileName, out UploadResult result)
{
result = uploadResults.SingleOrDefault(f => f.FileName == fileName)
?? new();

return result.Uploaded;
}

private class File


{
public string? Name { get; set; }
}
}

Le contrôleur suivant du projet côté serveur enregistre les fichiers chargés depuis le
client.

Pour utiliser le code suivant, créez un dossier Development/unsafe_uploads à la racine


du projet côté serveur pour l’application s’exécutant dans l’environnement
Development .

Sachant que l’exemple utilise l’environnement de l’application dans le chemin où sont


enregistrés les fichiers, des dossiers supplémentaires sont nécessaires si d’autres
environnements sont utilisés en phase de test et en production. Par exemple, créez un
dossier Staging/unsafe_uploads pour l’environnement Staging . Créez un dossier
Production/unsafe_uploads pour l’environnement Production .

2 Avertissement

L’exemple enregistre des fichiers sans analyser leur contenu, et l’aide fournie dans
cet article ne prend pas en compte les autres bonnes pratiques de sécurité pour les
fichiers chargés. Sur les systèmes de mise en lots et de production, désactivez
l’autorisation d’exécution sur le dossier de chargement et analysez les fichiers avec
une API d’analyseur antivirus/anti-programme malveillant de suite après le
chargement. Pour plus d’informations, consultez Charger des fichiers dans ASP.NET
Core.

Dans l’exemple suivant, mettez à jour l’espace de noms du projet partagé pour qu’il
corresponde au projet partagé si un projet partagé fournit la classe UploadResult .

Controllers/FilesaveController.cs :
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using BlazorSample.Shared;

[ApiController]
[Route("[controller]")]
public class FilesaveController : ControllerBase
{
private readonly IHostEnvironment env;
private readonly ILogger<FilesaveController> logger;

public FilesaveController(IHostEnvironment env,


ILogger<FilesaveController> logger)
{
this.env = env;
this.logger = logger;
}

[HttpPost]
public async Task<ActionResult<IList<UploadResult>>> PostFile(
[FromForm] IEnumerable<IFormFile> files)
{
var maxAllowedFiles = 3;
long maxFileSize = 1024 * 15;
var filesProcessed = 0;
var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/");
List<UploadResult> uploadResults = new();

foreach (var file in files)


{
var uploadResult = new UploadResult();
string trustedFileNameForFileStorage;
var untrustedFileName = file.FileName;
uploadResult.FileName = untrustedFileName;
var trustedFileNameForDisplay =
WebUtility.HtmlEncode(untrustedFileName);

if (filesProcessed < maxAllowedFiles)


{
if (file.Length == 0)
{
logger.LogInformation("{FileName} length is 0 (Err: 1)",
trustedFileNameForDisplay);
uploadResult.ErrorCode = 1;
}
else if (file.Length > maxFileSize)
{
logger.LogInformation("{FileName} of {Length} bytes is "
+
"larger than the limit of {Limit} bytes (Err: 2)",
trustedFileNameForDisplay, file.Length,
maxFileSize);
uploadResult.ErrorCode = 2;
}
else
{
try
{
trustedFileNameForFileStorage =
Path.GetRandomFileName();
var path = Path.Combine(env.ContentRootPath,
env.EnvironmentName, "unsafe_uploads",
trustedFileNameForFileStorage);

await using FileStream fs = new(path,


FileMode.Create);
await file.CopyToAsync(fs);

logger.LogInformation("{FileName} saved at {Path}",


trustedFileNameForDisplay, path);
uploadResult.Uploaded = true;
uploadResult.StoredFileName =
trustedFileNameForFileStorage;
}
catch (IOException ex)
{
logger.LogError("{FileName} error on upload (Err:
3): {Message}",
trustedFileNameForDisplay, ex.Message);
uploadResult.ErrorCode = 3;
}
}

filesProcessed++;
}
else
{
logger.LogInformation("{FileName} not uploaded because the "
+
"request exceeded the allowed {Count} of files (Err:
4)",
trustedFileNameForDisplay, maxAllowedFiles);
uploadResult.ErrorCode = 4;
}

uploadResults.Add(uploadResult);
}

return new CreatedResult(resourcePath, uploadResults);


}
}
Dans le code précédent, GetRandomFileName est appelé pour générer un nom de
fichier sécurisé. N’approuvez jamais le nom de fichier fourni par le navigateur, car un
attaquant peut choisir un nom de fichier existant qui remplace un fichier existant ou
envoyer un chemin qui tente d’écrire en dehors de l’application.

L’application serveur doit inscrire les services de contrôleur et mapper les points de
terminaison du contrôleur. Pour plus d’informations, consultez Routage vers des actions
de contrôleur dans ASP.NET Core.

Annuler un chargement de fichier


Un composant de chargement de fichiers peut détecter qu’un utilisateur a annulé un
chargement à l’aide d’un CancellationToken lors d’un appel dans
IBrowserFile.OpenReadStream ou StreamReader.ReadAsync.

Créez un CancellationTokenSource pour le composant InputFile . Au démarrage de la


méthode OnInputFileChange , vérifiez si un chargement précédent est en cours.

Si un chargement de fichier est en cours :

Appelez Cancel sur le chargement précédent.


Créez un CancellationTokenSource pour le chargement suivant et transmettez le
CancellationTokenSource.Token à OpenReadStream ou ReadAsync.

Charger des fichiers côté serveur avec


progression
L’exemple suivant montre comment charger des fichiers dans une application côté
serveur avec la progression du chargement présentée à l’utilisateur.

Pour utiliser l’exemple suivant dans une application de test :

Créez un dossier pour enregistrer les fichiers chargés pour l’environnement


Development : Development/unsafe_uploads .

Configurez la taille de fichier maximale ( maxFileSize , 15 Ko dans l’exemple suivant)


et le nombre maximal de fichiers autorisés ( maxAllowedFiles , 3 dans l’exemple
suivant).
Attribuez éventuellement à la mémoire tampon une valeur différente (10 Ko dans
l’exemple suivant) si vous voulez que la progression affichée offre une plus grande
granularité. Nous vous déconseillons d’utiliser une mémoire tampon supérieure à
30 Ko en raison des problèmes de performances et de sécurité que cela
occasionne.

2 Avertissement

L’exemple enregistre des fichiers sans analyser leur contenu, et l’aide fournie dans
cet article ne prend pas en compte les autres bonnes pratiques de sécurité pour les
fichiers chargés. Sur les systèmes de mise en lots et de production, désactivez
l’autorisation d’exécution sur le dossier de chargement et analysez les fichiers avec
une API d’analyseur antivirus/anti-programme malveillant de suite après le
chargement. Pour plus d’informations, consultez Charger des fichiers dans ASP.NET
Core.

FileUpload3.razor :

razor

@page "/file-upload-3"
@rendermode InteractiveServer
@inject ILogger<FileUpload3> Logger
@inject IHostEnvironment Environment

<h3>Upload Files</h3>

<p>
<label>
Upload up to @maxAllowedFiles of up to @maxFileSize bytes:
<InputFile OnChange="@LoadFiles" multiple />
</label>
</p>

@if (isLoading)
{
<p>Progress: @string.Format("{0:P0}", progressPercent)</p>
}
else
{
<ul>
@foreach (var file in loadedFiles)
{
<li>
<ul>
<li>Name: @file.Name</li>
<li>Last modified: @file.LastModified.ToString()</li>
<li>Size (bytes): @file.Size</li>
<li>Content type: @file.ContentType</li>
</ul>
</li>
}
</ul>
}

@code {
private List<IBrowserFile> loadedFiles = new();
private long maxFileSize = 1024 * 15;
private int maxAllowedFiles = 3;
private bool isLoading;
private decimal progressPercent;

private async Task LoadFiles(InputFileChangeEventArgs e)


{
isLoading = true;
loadedFiles.Clear();
progressPercent = 0;

foreach (var file in e.GetMultipleFiles(maxAllowedFiles))


{
try
{
var trustedFileName = Path.GetRandomFileName();
var path = Path.Combine(Environment.ContentRootPath,
Environment.EnvironmentName, "unsafe_uploads",
trustedFileName);

await using FileStream writeStream = new(path,


FileMode.Create);
using var readStream = file.OpenReadStream(maxFileSize);
var bytesRead = 0;
var totalRead = 0;
var buffer = new byte[1024 * 10];

while ((bytesRead = await readStream.ReadAsync(buffer)) !=


0)
{
totalRead += bytesRead;

await writeStream.WriteAsync(buffer, 0, bytesRead);

progressPercent = Decimal.Divide(totalRead, file.Size);

StateHasChanged();
}

loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
}
}

isLoading = false;
}
}

Pour plus d’informations, consultez les ressources d’API suivantes :

Stream : Fournit un FileStream pour un fichier, prenant en charge les opérations de


lecture et d’écriture à la fois synchrones et asynchrones.
FileStream.ReadAsync : Le composant FileUpload3 précédent lit le flux de manière
asynchrone avec ReadAsync. La lecture synchrone d’un flux avec Read n’est pas
prise en charge dans les composants Razor.

Flux de fichiers
Avec l’interactivité serveur, les données de fichier sont transmises en continu au code
.NET sur le serveur via la connexion SignalR au fur et à mesure que le fichier est lu.

Pour un composant du rendu WebAssembly, les données de fichier sont transmises


directement au code .NET dans le navigateur.

Charger un aperçu des images


Pour obtenir un aperçu des images en cours de chargement, commencez par ajouter un
composant InputFile avec une référence de composant et un gestionnaire OnChange :

razor

<InputFile @ref="inputFile" OnChange="@ShowPreview" />

Ajoutez un élément image avec une référence d’élément, qui sert d’espace réservé pour
l’aperçu de l’image :

razor

<img @ref="previewImageElem" />

Ajoutez les références associées :

razor

@code {
private InputFile? inputFile;
private ElementReference previewImageElem;
}

En JavaScript, ajoutez une fonction appelée avec un input HTML et un élément img
qui effectue les opérations suivantes :

Extraction du fichier sélectionné.


Création d’une URL d’objet avec createObjectURL .
Définition d’un écouteur d’événements pour révoquer l’URL d’objet avec
revokeObjectURL une fois l’image chargée, de façon à éviter une fuite de
mémoire.
Définition de la source de l’élément img pour afficher l’image.

JavaScript

window.previewImage = (inputElem, imgElem) => {


const url = URL.createObjectURL(inputElem.files[0]);
imgElem.addEventListener('load', () => URL.revokeObjectURL(url), { once:
true });
imgElem.src = url;
}

Enfin, utilisez un IJSRuntime injecté pour ajouter le gestionnaire OnChange qui appelle la
fonction JavaScript :

razor

@inject IJSRuntime JS

...

@code {
...

private async Task ShowPreview() => await JS.InvokeVoidAsync(


"previewImage", inputFile!.Element, previewImageElem);
}

L’exemple précédent concerne le chargement d’une seule image. L’approche peut être
étendue afin de prendre en charge plusieurs ( multiple ) images.

Le composant FileUpload4 ci-dessous montre l’exemple complet.

FileUpload4.razor :

razor
@page "/file-upload-4"
@rendermode InteractiveServer
@inject IJSRuntime JS

<h1>File Upload Example</h1>

<InputFile @ref="inputFile" OnChange="@ShowPreview" />

<img style="max-width:200px;max-height:200px" @ref="previewImageElem" />

@code {
private InputFile? inputFile;
private ElementReference previewImageElem;

private async Task ShowPreview() => await JS.InvokeVoidAsync(


"previewImage", inputFile!.Element, previewImageElem);
}

Charger des fichiers dans un service externe


Au lieu d’avoir une application qui gère les octets de chargement de fichiers et le
serveur de l’application qui reçoit les fichiers chargés, les clients peuvent charger
directement les fichiers dans un service externe. L’application peut traiter de manière
sécurisée les fichiers depuis le service externe à la demande. Cette approche endurcit
l’application et son serveur face aux attaques malveillantes et aux problèmes potentiels
de performances.

Envisagez une approche qui utilise Azure Files , Stockage Blob Azure ou un service
tiers avec les avantages potentiels suivants :

Chargez des fichiers du client directement dans un service externe avec une
bibliothèque de client JavaScript ou une API REST. Par exemple, Azure propose les
bibliothèques de client et les API suivantes :
Bibliothèque de client de partage de fichiers Stockage Azure
API REST Azure Files
Bibliothèque de client Azure Storage Blob pour JavaScript
API REST du service BLOB
Autorisez les chargements utilisateur avec un jeton de signature d’accès partagé
(SAS) déléguée par l’utilisateur généré par l’application (côté serveur) pour chaque
chargement de fichier client. Par exemple, Azure offre les fonctionnalités SAS
suivantes :
Bibliothèque de client de partage de fichiers Stockage Azure pour JavaScript :
avec jeton SAS
Bibliothèque de client de partage Azure Storage Blob pour JavaScript : avec
jeton SAS
Proposez une redondance automatique et une sauvegarde de partage de fichiers.
Limitez les chargements avec des quotas. Notez que les quotas de Stockage Blob
Azure sont définis au niveau du compte, et non au niveau du conteneur.
Cependant, le quotas Azure Files sont définis au niveau du partage de fichiers et
peuvent offrir un meilleur contrôle des limites de chargement. Pour plus
d’informations, consultez les documents Azure indiqués plus haut dans cette liste
sous forme de liens.
Sécurisez les fichiers avec le chiffrement SSE (Storage Service Encryption).

Pour plus d’informations sur le Stockage Blob Azure et Azure Files, consultez la
documentation Stockage Azure.

Limite de taille des messages SignalR côté


serveur
Les chargements de fichiers peuvent échouer même avant d’avoir débuté, au moment
où Blazor récupère des données sur les fichiers qui dépassent la taille de message
SignalR maximale.

SignalR définit une limite de taille de message qui s’applique à chaque message que
reçoit Blazor, et le composant InputFile transmet les fichiers au serveur dans des
messages qui respectent la limite configurée. Cependant, le premier message, qui
indique l’ensemble de fichiers à charger, est envoyé sous la forme d’un seul et même
message. La taille du premier message peut dépasser la limite de taille de message
SignalR. Le problème n’est pas lié à la taille des fichiers, mais à leur nombre.

L’erreur journalisée ressemble à l’erreur suivante :

Erreur : Connexion déconnectée avec l’erreur « Erreur : Le serveur a retourné une


erreur à la fermeture : Connexion fermée avec une erreur. ». e.log @
blazor.server.js:1

Lors d’un chargement de fichiers, il est rare d’atteindre la limite de taille de message
avec le premier message. Si cette limite est atteinte, l’application peut configurer
HubOptions.MaximumReceiveMessageSize avec une valeur supérieure.

Pour plus d’informations sur la configuration de SignalR et sur la définition de


MaximumReceiveMessageSize, consultez l’aide ASP.NET Core BlazorSignalR.
Ressources supplémentaires
Téléchargements de fichiers dans ASP.NET Core Blazor
Charger des fichiers dans ASP.NET Core
Vue d’ensemble des formulaires Blazor ASP.NET Core
Dépôt GitHub d’exemples Blazor (dotnet/blazor-samples)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Téléchargements de fichiers ASP.NET
Core Blazor
Article • 09/02/2024

Cet article explique comment télécharger des fichiers dans Blazor applications.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir des exemples Blazor du référentiel
GitHub correspondant à la version .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Téléchargements de fichiers
Les fichiers peuvent être téléchargés à partir des propres ressources statiques de
l’application ou à partir de n’importe quel autre emplacement :

Les applications ASP.NET Core utilisent le middleware de fichiers statiques pour


fournir des fichiers aux clients des applications côté serveur.
Les instructions fournies dans cet article s’appliquent également à d’autres types
de serveurs de fichiers qui n’utilisent pas .NET, tels que les réseaux de distribution
de contenu (CDN).

Cet article aborde les approches concernant les scénarios suivants :

Diffuser en streaming du contenu de fichier dans une mémoire tampon de


données binaires brutes sur le client : en général, cette approche est utilisée pour
les fichiers relativement petits (< 250 Mo).
Télécharger un fichier par le biais d’une URL sans diffusion en streaming : en
général, cette approche est utilisée pour les fichiers relativement volumineux
(> 250 Mo).

Lors du téléchargement de fichiers à partir d’une origine différente de celle de


l’application, les considérations de partage des ressources cross-origin (CORS, Cross-
Origin Resource Sharing) s’appliquent. Pour plus d’informations, consultez la section
Partage des ressources cross-origin (CORS, Cross-Origin Resource Sharing).

Considérations relatives à la sécurité


Soyez prudent lorsque vous fournissez aux utilisateurs la possibilité de télécharger des
fichiers à partir d’un serveur. Les attaquants peuvent effectuer des attaques par déni de
service (DoS) ou des attaques d’exploitation d’API , ou tenter de compromettre des
réseaux et des serveurs en utilisant d’autres moyens.

Les étapes de sécurité qui réduisent la probabilité d’une attaque réussie sont les
suivantes :

Téléchargez des fichiers à partir d’une zone de téléchargement de fichier dédiée


sur le serveur, de préférence à partir d’un lecteur non système. L’utilisation d’un
emplacement dédié facilite la prise de restrictions de sécurité sur les fichiers
téléchargeables. Désactivez les autorisations d’exécution sur la zone de
téléchargement de fichier.
Les contrôles de sécurité côté client sont faciles à contourner par des utilisateurs
malveillants. De plus, effectuez toujours des vérifications de sécurité côté client sur
le serveur.
Ne recevez pas d’utilisateurs ou d’autres sources non approuvées des fichiers que
vous rendez ensuite disponibles en téléchargement immédiat sans effectuer de
vérifications de sécurité sur ces fichiers. Pour plus d’informations, consultez
Charger des fichiers dans ASP.NET Core.

Télécharger à partir d’un flux


Cette section s’applique aux fichiers dont la taille maximale est généralement de 250 Mo.

L’approche recommandée pour télécharger des fichiers relativement petits (< 250 Mo)
consiste à diffuser en streaming du contenu de fichier dans une mémoire tampon de
données binaires brutes sur le client avec l’interopérabilité JavaScript (JS).

2 Avertissement

L’approche décrite dans cette section lit le contenu du fichier dans un JS


ArrayBuffer . Cette approche charge l’ensemble du fichier dans la mémoire du
client, ce qui peut nuire aux performances. Pour télécharger des fichiers
relativement volumineux (>= 250 Mo), nous vous recommandons de suivre les
instructions fournies dans la section Télécharger à partir d’une URL.

La fonction downloadFileFromStream JS suivante :

Lit le flux fourni dans un ArrayBuffer .


Crée un Blob pour encapsuler le ArrayBuffer .
Crée une URL d’objet pour servir d’adresse de téléchargement du fichier.
Crée un HTMLAnchorElement ( <a> élement).
Attribue le nom ( fileName ) et l’URL ( url ) du fichier pour le téléchargement.
Déclenche le téléchargement en activant un événement click sur l’élément
d’ancrage.
Supprime l’élément d’ancrage.
Révoque l’URL d’objet ( url ) en appelant URL.revokeObjectURL . Il s’agit d’une
étape importante pour garantir qu’il n’y a pas de fuite de mémoire sur le client.

HTML
<script>
window.downloadFileFromStream = async (fileName, contentStreamReference)
=> {
const arrayBuffer = await contentStreamReference.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? '';
anchorElement.click();
anchorElement.remove();
URL.revokeObjectURL(url);
}
</script>

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

Le composant suivant :

Utilise l’interopérabilité de streaming d’octets native pour garantir un transfert


efficace du fichier vers le client.
A une méthode nommée GetFileStream pour récupérer un Stream pour le fichier
qui est téléchargé sur les clients. D’autres approches permettent de récupérer un
fichier à partir du stockage ou de générer de façon dynamique un fichier dans du
code C#. Pour cette démonstration, l’application crée un fichier de données
aléatoires de 50 Ko à partir d’un nouveau tableau d’octets ( new byte[] ). Les octets
sont wrappés avec un MemoryStream pour servir de fichier binaire généré
dynamiquement de l’exemple.
La méthode DownloadFileFromStream :
Récupère le Stream partir de GetFileStream .
Spécifie un nom de fichier lorsque le fichier est enregistré sur la machine de
l’utilisateur. L’exemple suivant nomme le fichier quote.txt .
Envelopper le Stream dans un DotNetStreamReference, ce qui permet de
diffuser en streaming les données du fichier vers le client.
Appelle la fonction JS downloadFileFromStream pour accepter les données sur le
client.

FileDownload1.razor :
razor

@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS

<PageTitle>File Download 1</PageTitle>

<h1>File Download Example 1</h1>

<button @onclick="DownloadFileFromStream">
Download File From Stream
</button>

@code {
private Stream GetFileStream()
{
var randomBinaryData = new byte[50 * 1024];
var fileStream = new MemoryStream(randomBinaryData);

return fileStream;
}

private async Task DownloadFileFromStream()


{
var fileStream = GetFileStream();
var fileName = "log.bin";

using var streamRef = new DotNetStreamReference(stream: fileStream);

await JS.InvokeVoidAsync("downloadFileFromStream", fileName,


streamRef);
}
}

Pour un composant d’une application d’un côté serveur qui doit retourner un Stream
pour un fichier physique, le composant peut appeler File.OpenRead, comme le montre
l’exemple suivant :

C#

private Stream GetFileStream()


{
return File.OpenRead(@"{PATH}");
}

Dans l’exemple précédent, l’espace réservé {PATH} est le chemin du fichier. Le préfixe @
indique que la chaîne est un littéral de chaîne verbatim, ce qui permet d’utiliser des
barres obliques inverses ( \ ) dans un chemin du système d’exploitation Windows et des
guillemets doubles incorporés ( "" ) pour un guillemet simple dans le chemin. Vous
pouvez également éviter le littéral de chaîne ( @ ) et utiliser l’une des approches
suivantes :

Utilisez des barres obliques inverses dans une séquence d’échappement ( \\ ) et


des guillemets ( \" ) dans une séquence d’échappement.
Utilisez des barres obliques ( / ) dans le chemin : elles sont prises en charge sur
toutes les plateformes dans les applications ASP.NET Core, et des guillemets dans
une séquence d’échappement ( \" ).

Télécharger à partir d’une URL


Cette section s’applique aux fichiers relativement volumineux, d’une taille généralement
supérieure ou égale à 250 Mo.

L’exemple de cette section utilise un fichier de téléchargement nommé quote.txt , qui


est placé dans un dossier nommé files à la racine web de l’application (dossier
wwwroot ). Le dossier files est utilisé uniquement à des fins de démonstration. Vous

pouvez organiser les fichiers téléchargeables dans n’importe quelle disposition de


dossier au sein de la racine web (dossier wwwroot ) de votre choix, ce qui inclut de traiter
les fichiers directement à partir du dossier wwwroot .

wwwroot/files/quote.txt :

text

When victory is ours, we'll wipe every trace of the Thals and their city
from the face of this land. We will avenge the deaths of all Kaleds who've
fallen in the cause of right and justice and build a peace which will be a
monument to their sacrifice. Our battle cry will be "Total extermination of
the Thals!"

- General Ravon (Guy Siner, http://guysiner.com/)


Dr. Who: Genesis of the Daleks (https://www.bbc.co.uk/programmes/p00pq2gc)
©1975 BBC (https://www.bbc.co.uk/)

La fonction triggerFileDownload JS suivante :

Crée un HTMLAnchorElement ( <a> élement).


Attribue le nom ( fileName ) et l’URL ( url ) du fichier pour le téléchargement.
Déclenche le téléchargement en activant un événement click sur l’élément
d’ancrage.
Supprime l’élément d’ancrage.
HTML

<script>
window.triggerFileDownload = (fileName, url) => {
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? '';
anchorElement.click();
anchorElement.remove();
}
</script>

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

L’exemple de composant suivant télécharge le fichier à partir de la même origine que


celle utilisée par l’application. Si le téléchargement de fichier est tenté à partir d’une
autre origine, configurez le partage des ressources cross-origin (CORS, Cross-Origin
Resource Sharing). Pour plus d’informations, consultez la section Partage des ressources
cross-origin (CORS).

Modifiez le port dans l’exemple suivant pour qu’il corresponde au port de


développement localhost de votre environnement.

FileDownload2.razor :

razor

@page "/file-download-2"
@inject IJSRuntime JS

<PageTitle>File Download 2</PageTitle>

<h1>File Download Example 2</h1>

<button @onclick="DownloadFileFromURL">
Download File From URL
</button>

@code {
private async Task DownloadFileFromURL()
{
var fileName = "quote.txt";
var fileURL = "/files/quote.txt";
await JS.InvokeVoidAsync("triggerFileDownload", fileName, fileURL);
}
}

CORS (Cross Origin Resource Sharing)


Si vous n’effectuez pas d’étapes supplémentaires pour activer le partage de ressources
cross-origin (CORS) pour les fichiers qui n’ont pas la même origine que l’application,
le téléchargement de fichiers ne réussira pas les vérifications CORS effectuées par le
navigateur.

Pour plus d’informations sur le partage CORS avec des applications ASP.NET Core et
d’autres produits et services Microsoft qui hébergent des fichiers à télécharger,
consultez les ressources suivantes :

Activation des demandes multi-origines (CORS) dans ASP.NET Core


Utilisation d’Azure CDN avec CORS (documentation Azure)
Prise en charge du service Partage des ressources cross-origine (CORS) pour le
Stockage Azure (documentation REST)
Services cloud principaux - Configurer CORS pour votre site web et vos ressources
de stockage (module Learn)
Informations de référence sur la configuration du module IIS CORS
(documentation IIS)

Ressources supplémentaires
Interopérabilité JavaScript ASP.NET Core Blazor (interopérabilité JS)
<a> : Élément d’ancrage : Sécurité et confidentialité (documentation MDN)
Chargements de fichiers Blazor ASP.NET Core
Vue d’ensemble des formulaires Blazor ASP.NET Core
Blazor exemples de référentiel GitHub (dotnet/blazor-samples)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Interopérabilité JavaScript dans ASP.NET
Core Blazor (interopérabilité JS)
Article • 09/02/2024

Une application Blazor peut appeler des fonctions JavaScript (JS) à partir de méthodes
.NET et de méthodes .NET issues de fonctions JS. Ces scénarios sont appelés
interopérabilité JavaScript (interopérabilité JS).

D’autres conseils d’interopérabilité JS sont fournis dans les articles suivants :

Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core
Blazor
Appeler des méthodes .NET à partir de fonctions JavaScript dans ASP.NET Core
Blazor

7 Notes

L’API d’interopérabilité JavaScript [JSImport] / [JSExport] est disponible pour les


composants côté client dans ASP.NET Core dans .NET 7 ou ultérieur.

Pour plus d’informations, consultez interopérabilité JSImport/JSExport JavaScript


avec Blazor ASP.NET Core.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.
Dans une application Blazor WebAssembly autonome, les composants
fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du Blazorréférentiel GitHub
d’exemples qui correspond à la version de .NET que vous ciblez. Pour le moment, tous
les exemples de la documentation ne figurent pas dans les exemples d’applications,
mais nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans
les exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant
du premier trimestre 2024.

Package d’abstractions et de fonctionnalités


JavaScript
Le package @microsoft/dotnet-js-interop (npmjs.com) (package NuGet
Microsoft.JSInterop ) fournit des abstractions et des fonctionnalités pour
l’interopérabilité entre le code .NET et JavaScript (JS). La source de référence est
disponible dans le référentiel GitHub dotnet/aspnetcore (dossier/src/JSInterop ) . Pour
plus d’informations, consultez le fichier README.md référentiel GitHub.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Ressources supplémentaires pour écrire des scripts d’interopérabilité JS dans


TypeScript :

TypeScript
Tutoriel : Créer une application ASP.NET Core avec TypeScript dans Visual Studio
Gérer les packages npm dans Visual Studio

Interaction avec le DOM


Mutez uniquement le modèle DOM avec JavaScript (JS) quand l’objet n’interagit pas
avec Blazor. Blazor gère les représentations du modèle DOM et interagit directement
avec les objets DOM. Si un élément rendu par Blazor est modifié en externe à l’aide de
JS directement ou via l’interopérabilité JS, le modèle DOM peut ne plus correspondre à
la représentation interne de Blazor, ce qui peut entraîner un comportement non défini.
Un comportement non défini peut simplement interférer avec la présentation
d’éléments ou leurs fonctions, mais peut également introduire des risques de sécurité
pour l’application ou le serveur.

Cette recommandation s’applique non seulement à votre propre code d’interopérabilité


JS, mais également à toutes les bibliothèques JS que l’application utilise, y compris tout
ce qui est fourni par une infrastructure tierce, telle que Bootstrap JS et jQuery .

Dans quelques exemples de documentation, l’interopérabilité JS est utilisée pour muter


un élément à des fins d’illustration uniquement dans le cadre d’un exemple. Dans ces cas,
un avertissement apparaît dans le texte.

Pour plus d’informations, consultez Appeler des fonctions JavaScript à partir de


méthodes .NET dans ASP.NET Core Blazor.

Appels JavaScript asynchrones


Les appels d’interopérabilité JS sont asynchrones par défaut, que le code appelé soit
synchrone ou asynchrone. Les appels sont asynchrones par défaut pour garantir que les
composants sont compatibles entre les modes de rendu côté serveur et côté client. Lors
de l’adoption du rendu côté serveur, les JSappels interop doivent être asynchrones car
ils sont envoyés via une connexion réseau. Pour les applications qui adoptent
exclusivement le rendu côté client, les JSappels interop synchrones sont pris en charge.
Pour plus d’informations, consultez les articles suivants :

Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core
Blazor
Appeler des méthodes .NET à partir de fonctions JavaScript dans ASP.NET Core
Blazor

Sérialisation d’objets
Blazor utilise System.Text.Json pour la sérialisation avec les exigences et les
comportements par défaut suivants :

Les types doivent avoir un constructeur par défaut, les accesseurs get/set doivent
être publics et les champs ne sont jamais sérialisés.
La sérialisation par défaut globale n’est pas personnalisable pour éviter un arrêt
des bibliothèques de composants existantes, des répercussions sur les
performances et la sécurité, et des baisses de fiabilité.
La sérialisation des noms de membres .NET entraîne des noms de clés JSON en
minuscules.
JSON est désérialisé en tant qu’instances C# JsonElement, ce qui permet d’utiliser
une casse mixte. Le cast interne pour l’affectation aux propriétés du modèle C#
fonctionne comme prévu malgré les différences de casse entre les noms des clés
JSON et les noms des propriétés C#.
Les types de frameworks complexes, tels que KeyValuePair, peuvent être découpés
par l’outil IL Trimmer lors de la publication et ne pas être présents pour
l’interopérabilité avec JS. Nous vous recommandons de créer des types
personnalisés pour les types découpés par défaut par l’outil IL Trimmer.

L’API JsonConverter est disponible pour la sérialisation personnalisée. Les propriétés


peuvent être annotées avec un attribut [JsonConverter] pour remplacer la sérialisation
par défaut pour un type de données existant.

Pour plus d’informations, consultez les ressources suivantes dans la documentation


.NET :

Sérialisation et désérialisation JSON (marshalling et unmarshalling) dans .NET


Comment personnaliser les noms et valeurs de propriétés avec System.Text.Json
Comment écrire des convertisseurs personnalisés pour la sérialisation JSON
(marshalling) dans .NET

Blazor prend en charge l’interopérabilité JS des tableaux d’octets optimisés, qui évite
l’encodage/décodage des tableaux d’octets en Base64. L’application peut appliquer une
sérialisation personnalisée et transmettre les octets obtenus. Pour plus d’informations,
consultez Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET
Core Blazor.

Tâches de nettoyage de modèle DOM lors de la


suppression des composants
N’exécutez pas de code d’interopérabilité JS pour les tâches de nettoyage DOM
pendant la suppression des composants. Utilisez plutôt le modèle MutationObserver
en JavaScript (JS) sur le client pour les raisons suivantes :

Le composant a peut-être été supprimé du DOM au moment où votre code de


nettoyage s’exécute dans Dispose{Async} .
Lors du rendu côté serveur, le moteur de Blazorrendu peut avoir été éliminé par le
framework au moment où votre code de nettoyage s’exécute dans
Dispose{Async} .

Le modèle MutationObserver vous permet d’exécuter une fonction lorsqu’un élément


est supprimé du DOM.

Dans l’exemple suivant, le composant DOMCleanup :

Contient un <div> avec un id de cleanupDiv . L’élément <div> est supprimé du


DOM, ainsi que le reste du balisage DOM du composant lorsque le composant est
supprimé du DOM.
Charge la classe DOMCleanup JS à partir du fichier DOMCleanup.razor.js et appelle sa
fonction de createObserver pour configurer le rappel MutationObserver . Ces
tâches sont effectuées dans la OnAfterRenderAsyncméthode de cycle de vie.

DOMCleanup.razor :

razor

@page "/dom-cleanup"
@implements IAsyncDisposable
@inject IJSRuntime JS

<h1>DOM Cleanup Example</h1>

<div id="cleanupDiv"></div>

@code {
private IJSObjectReference? module;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>(
"import", "./Components/Pages/DOMCleanup.razor.js");

await module.InvokeVoidAsync("DOMCleanup.createObserver");
}
}

async ValueTask IAsyncDisposable.DisposeAsync()


{
if (module is not null)
{
await module.DisposeAsync();
}
}
}

Dans l’exemple suivant, le rappel MutationObserver est exécuté chaque fois qu’une
modification DOM se produit. Exécutez votre code de nettoyage lorsque l’instruction if
confirme que l’élément cible ( cleanupDiv ) a été supprimé ( if (targetRemoved) { ... } ).
Il est important de déconnecter et de supprimer la MutationObserver pour éviter une
fuite de mémoire après l’exécution de votre code de nettoyage.

DOMCleanup.razor.js placé côte à côte avec le composant DOMCleanup précédent :

JavaScript

export class DOMCleanup {


static observer;

static createObserver() {
const target = document.querySelector('#cleanupDiv');

this.observer = new MutationObserver(function (mutations) {


const targetRemoved = mutations.some(function (mutation) {
const nodes = Array.from(mutation.removedNodes);
return nodes.indexOf(target) !== -1;
});

if (targetRemoved) {
// Cleanup resources here
// ...

// Disconnect and delete MutationObserver


this.observer && this.observer.disconnect();
delete this.observer;
}
});
this.observer.observe(target.parentNode, { childList: true });
}
}

window.DOMCleanup = DOMCleanup;

Appels d’interopérabilité JavaScript sans circuit


Cette section s’applique uniquement aux applications côté serveur.

Les appels d’interopérabilité JavaScript (JS) ne peuvent pas être émis après la
déconnexion d’un circuit SignalR. Sans circuit lors de l’élimination du composant ou à
tout autre moment où un circuit n’existe pas, les appels de méthode suivants échouent
et journalisent un message indiquant que le circuit est déconnecté en tant que
JSDisconnectedException :

Appels de méthode d’interopérabilité JS


IJSRuntime.InvokeAsync
JSRuntimeExtensions.InvokeAsync
JSRuntimeExtensions.InvokeVoidAsync)
Dispose / DisposeAsync appelle sur n’importe quel IJSObjectReference.

Pour éviter la journalisation JSDisconnectedException ou pour journaliser des


informations personnalisées, interceptez l’exception dans une instruction try-catch.

Pour l’exemple de suppression de composant suivant :

Le composant implémente IAsyncDisposable.


objInstance est une IJSObjectReference.

JSDisconnectedException est intercepté et non journalisé.


Si vous le souhaitez, vous pouvez enregistrer des informations personnalisées dans
l’instruction catch au niveau de journal de votre choix. L’exemple suivant ne
journalise pas les informations personnalisées, car il part du principe que le
développeur ne se soucie pas du moment ou de l’endroit où les circuits sont
déconnectés lors de l’élimination des composants.

C#

async ValueTask IAsyncDisposable.DisposeAsync()


{
try
{
if (objInstance is not null)
{
await objInstance.DisposeAsync();
}
}
catch (JSDisconnectedException)
{
}
}

Si vous devez nettoyer vos propres objets JS ou exécuter un autre code JS sur le client
une fois qu’un circuit est perdu, utilisez le modèle MutationObserver dans JS sur le
client. Le modèle MutationObserver vous permet d’exécuter une fonction lorsqu’un
élément est supprimé du DOM.

Pour plus d’informations, consultez les articles suivants :

Gérer les erreurs dans les applications ASP.NET Core Blazor : la section
Interopérabilité JavaScript traite de la gestion des erreurs dans les scénarios
d’interopérabilité JS.
Cycle de vie des composants ASP.NET Core Razor : la section Suppression des
composants avec IDisposable et IAsyncDisposable décrit comment implémenter
des modèles d’élimination dans les composants Razor.

Emplacement JavaScript
Chargez du code JavaScript (JS) à l’aide de l’une des approches suivantes :

Charger un script dans le balisage <head> (pas généralement recommandé)


Charger un script dans le balisage <body>
Charger un script à partir d’un fichier JavaScript externe (.js) colocalisé avec un
composant
Charger un script à partir d’un fichier JavaScript externe (.js)
Injecter un script avant ou après que Blazor démarre

2 Avertissement

Placez une balise <script> dans un fichier de composant ( .razor ) uniquement si


vous avez l’assurance que le composant adoptera le rendu statique côté serveur
(SSR statique), car la balise <script> ne peut pas être mise à jour dynamiquement.

7 Notes

Les exemples de documentation placent généralement des scripts dans une balise
<script> ou chargent des scripts globaux à partir de fichiers externes. Ces
approches polluent le client avec des fonctions globales. Pour les applications de
production, nous vous recommandons de placer le code JavaScript dans des
modules JavaScript distincts qui peuvent être importés si nécessaire. Pour plus
d’informations, consultez la section Isolation JavaScript dans les modules
JavaScript.

Charger un script dans le balisage <head>


L’approche dans cette section n’est généralement pas recommandée.

Placez les (JS) balises JavaScript ( <script>...</script> ) dans le <head>balisage de


l’élément :

HTML

<head>
...

<script>
window.jsMethod = (methodParameter) => {
...
};
</script>
</head>

Le chargement de JS à partir de <head> n’est pas la meilleure approche pour les raisons
suivantes :

L’interopérabilité JS peut échouer si le script dépend de Blazor. Nous vous


recommandons de charger des scripts par une autre approche, et non via le
balisage <head> .
La page peut devenir plus lentement interactive en raison du temps nécessaire à
l’analyse JS du script.

Charger un script dans le balisage <body>


Placez les (JS) balises JavaScript ( <script>...</script> ) à l’intérieur de l’élément de
fermeture</body> après la référence au scriptBlazor :

HTML

<body>
...
<script src="{BLAZOR SCRIPT}"></script>
<script>
window.jsMethod = (methodParameter) => {
...
};
</script>
</body>

Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le chemin d’accès de
script Blazor et le nom de fichier. Pour connaître l’emplacement du script, consultez
ASP.NET Core Blazor structure du projet.

Charger un script à partir d’un fichier JavaScript externe


( .js ) colocalisé avec un composant
La colocation de fichiers JavaScript (JS) pour les composants Razor constitue un moyen
pratique pour organiser des scripts dans une application.

Les composants Razor pour les applications Blazor colocalisent des fichiers JS en
utilisant l’extension .razor.js et sont adressables publiquement en tirant parti du
chemin d’accès au fichier dans le projet :

{PATH}/{COMPONENT}.{EXTENSION}.js

L’espace réservé {PATH} constitue le chemin d’accès au composant.


L’espace réservé {COMPONENT} constitue le composant.
L’espace réservé {EXTENSION} correspond à l’extension du composant ( razor ).

Quand l’application est publiée, l’infrastructure déplace automatiquement le script vers


la racine web. Les scripts sont déplacés vers bin/Release/{TARGET FRAMEWORK
MONIKER}/publish/wwwroot/{PATH}/Pages/{COMPONENT}.razor.js où les espaces réservés

sont :

{TARGET FRAMEWORK MONIKER} constitue le moniker de framework cible (TFM).

{PATH} constitue le chemin d’accès au composant.


{COMPONENT} constitue le nom de composant.

Aucune modification n’est requise pour l’URL relative du script, car Blazor se charge de
placer le fichier JS dans des ressources statiques publiées à votre place.

Cette section et les exemples suivants sont principalement axés sur l’explication de la
colocation de fichiers JS. Le premier exemple illustre un fichier colocalisé JS avec une
fonction ordinaire JS. Le deuxième exemple illustre l’utilisation d’un module pour
charger une fonction, ce qui correspond à l’approche recommandée pour la plupart des
applications de production. L’appel JS à partir de .NET est entièrement couvert dans les
fonctions JavaScript d’appel à partir de méthodes .NET dans ASP.NET CoreBlazor, où il
existe d’autres explications de l’API BlazorJS avec des exemples supplémentaires.
L’élimination des composants, qui est présente dans le deuxième exemple, est abordée
dans Cycle de vie des composants Razor ASP.NET Core .

Le composant JsCollocation1 suivant charge un script via un composant HeadContent


et appelle une fonction JS avec IJSRuntime.InvokeAsync. L’espace réservé {PATH}
constitue le chemin d’accès au composant.

) Important

Si vous utilisez le code suivant pour une démonstration dans une application de
test, remplacez l’espace réservé {PATH} par le chemin d’accès du composant
(exemple : Components/Pages dans .NET 8 ou ultérieur ou Pages dans .NET 7 ou
antérieur). Dans une application web Blazor (.NET 8 ou version ultérieure), le
composant nécessite un mode de rendu interactif appliqué globalement à
l’application ou à la définition du composant.

Ajoutez le script suivant après le script Blazor (emplacement du script de démarrage


Blazor) :

HTML

<script src="{PATH}/JsCollocation1.razor.js"></script>

Composant JsCollocation1 ( {PATH}/JsCollocation1.razor ) :

razor

@page "/js-collocation-1"
@inject IJSRuntime JS

<PageTitle>JS Collocation 1</PageTitle>

<h1>JS Collocation Example 1</h1>

<button @onclick="ShowPrompt">Call showPrompt1</button>

@if (!string.IsNullOrEmpty(result))
{
<p>
Hello @result!
</p>
}

@code {
private string? result;

public async void ShowPrompt()


{
result = await JS.InvokeAsync<string>(
"showPrompt1", "What's your name?");
StateHasChanged();
}
}

Le fichier colocalisé JS est placé en regard du fichier de composant JsCollocation1 avec


le nom de fichier JsCollocation1.razor.js . Dans le composant JsCollocation1 , le script
est référencé au niveau du chemin du dossier colocalisé. Dans l’exemple suivant, la
fonction showPrompt1 accepte le nom de l’utilisateur à partir d’un Window prompt() et
le retourne au composant JsCollocation1 pour l’affichage.

{PATH}/JsCollocation1.razor.js :

JavaScript

function showPrompt1(message) {
return prompt(message, 'Type your name here');
}

L’approche précédente n’est pas recommandée pour une utilisation générale dans des
applications de production, car elle pollue le client avec des fonctions globales. Une
meilleure approche pour les applications de production consiste à utiliser des modules
JS. Ces mêmes principes généraux s’appliquent au chargement d’un module JS à partir
d’un fichier JS colocalisé, tel qu’illustré dans l’exemple suivant.

La méthode OnAfterRenderAsync du composant JsCollocation2 suivant charge un


module JS dans module qui est un IJSObjectReference de la classe de composant.
module est utilisé pour appeler la fonction showPrompt2 . L’espace réservé {PATH}

constitue le chemin d’accès au composant.

) Important

Si vous utilisez le code suivant pour une démonstration dans une application de
test, remplacez l’espace réservé {PATH} par le chemin d’accès du composant. Dans
une application web Blazor (.NET 8 ou version ultérieure), le composant nécessite
un mode de rendu interactif appliqué globalement à l’application ou à la définition
du composant.

Composant JsCollocation2 ( {PATH}/JsCollocation2.razor ) :

razor

@page "/js-collocation-2"
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>JS Collocation 2</PageTitle>

<h1>JS Collocation Example 2</h1>

<button @onclick="ShowPrompt">Call showPrompt2</button>

@if (!string.IsNullOrEmpty(result))
{
<p>
Hello @result!
</p>
}

@code {
private IJSObjectReference? module;
private string? result;

protected async override Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
/*
Change the {PATH} placeholder in the next line to the path
of
the collocated JS file in the app. Examples:

./Components/Pages/JsCollocation2.razor.js (.NET 8 or later)


./Pages/JsCollocation2.razor.js (.NET 7 or earlier)
*/
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./{PATH}/JsCollocation2.razor.js");
}
}

public async void ShowPrompt()


{
if (module is not null)
{
result = await module.InvokeAsync<string>(
"showPrompt2", "What's your name?");
StateHasChanged();
}
}

async ValueTask IAsyncDisposable.DisposeAsync()


{
if (module is not null)
{
await module.DisposeAsync();
}
}
}

{PATH}/JsCollocation2.razor.js :

JavaScript

export function showPrompt2(message) {


return prompt(message, 'Type your name here');
}

Pour les scripts ou modules fournis par une bibliothèque de classes (RCL) Razor, le
chemin d’accès suivant est utilisé :

_content/{PACKAGE ID}/{PATH}/{COMPONENT}.{EXTENSION}.js

L’espace réservé {PACKAGE ID} est l’identificateur de package de la bibliothèque


RCL (ou le nom de la bibliothèque pour une bibliothèque de classes référencée par
l’application).
L’espace réservé {PATH} constitue le chemin d’accès au composant. Si un
composant Razor se trouve à la racine de la bibliothèque RCL, le segment de
chemin n’est pas inclus.
L’espace réservé {COMPONENT} constitue le nom de composant.
L’espace réservé {EXTENSION} correspond à l’extension du composant, razor ou
cshtml .

Dans l’exemple d’application Blazor suivant :

L’identificateur de package de la bibliothèque RCL est AppJS .


Les scripts d’un module sont chargés pour le composant JsCollocation3
( JsCollocation3.razor ).
Le composant JsCollocation3 figure dans le dossier Components/Pages de la
bibliothèque RCL.

C#
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./_content/AppJS/Components/Pages/JsCollocation3.razor.js");

Pour plus d’informations sur les bibliothèques RCL, consultez Consommer des
composants ASP.NET Core Razor à partir d’une bibliothèque de classes (RCL) Razor.

Charger un script à partir d’un fichier JavaScript externe


( .js )
Placez les (JS) balises JavaScript ( <script>...</script> ) avec un chemin source de script
( src ) à l’intérieur de la balise de fermeture</body> après la référence du scriptBlazor :

HTML

<body>
...

<script src="{BLAZOR SCRIPT}"></script>


<script src="{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>

Dans l'exemple précédent :

L’espace réservé {BLAZOR SCRIPT} correspond au chemin d’accès au script et au


nom de fichier Blazor. Pour connaître l’emplacement du script, consultez ASP.NET
Core Blazor structure du projet.
L’espace réservé {SCRIPT PATH AND FILE NAME (.js)} correspond au chemin et au
nom de fichier de script sous wwwroot .

Dans l’exemple suivant de la balise <script> précédente, le fichier scripts.js figure


dans le dossier wwwroot/js de l’application :

HTML

<script src="js/scripts.js"></script>

Vous pouvez également traiter des scripts directement à partir du dossier wwwroot si
vous préférez ne pas conserver tous vos scripts dans un dossier distinct sous wwwroot :

HTML

<script src="scripts.js"></script>
Quand le fichier JS externe est fourni par une bibliothèque de classes Razor, spécifiez le
fichier JS à l’aide de son chemin de ressource web statique stable : ./_content/{PACKAGE
ID}/{SCRIPT PATH AND FILE NAME (.js)} :

Le segment de chemin pour le répertoire actuel ( ./ ) est requis pour créer le


chemin de ressource statique correct vers le fichier JS.
L’espace réservé {PACKAGE ID} est l’ID de package de la bibliothèque. L’ID de
package est défini par défaut sur le nom d’assembly du projet si <PackageId> n’est
pas spécifié dans le fichier projet.
L’espace réservé {SCRIPT PATH AND FILE NAME (.js)} correspond au chemin et au
nom de fichier sous wwwroot .

HTML

<body>
...

<script src="{BLAZOR SCRIPT}"></script>


<script src="./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)}">
</script>
</body>

Dans l’exemple suivant de la balise <script> précédente :

La bibliothèque de classes Razor a le nom d’assembly ComponentLibrary et aucun


<PackageId> n’est spécifié dans le fichier projet de la bibliothèque.

Le fichier scripts.js se trouve dans le dossier wwwroot de la bibliothèque de


classes.

HTML

<script src="./_content/ComponentLibrary/scripts.js"></script>

Pour plus d’informations, consultez Consommer des composants ASP.NET Core Razor à
partir d’une bibliothèque de classes (RCL) Razor.

Injecter un script avant ou après le démarrage de Blazor


Pour garantir le chargement des scripts avant ou après le démarrage de Blazor, utilisez
un initialiseur JavaScript. Pour plus d’informations et d’exemples, consultez ASP.NET
Core Blazor startup.
Isolation JavaScript dans les modules JavaScript
Blazor active l’isolation JavaScript (JS) dans les modules JavaScript standard
(spécification ECMAScript ).

L’isolation JS offre les avantages suivants :

Le code JS importé ne pollue plus l’espace de noms global.


Les consommateurs d’une bibliothèque et de composants ne sont pas tenus
d’importer le code JS associé.

Pour plus d’informations, consultez Appeler des fonctions JavaScript à partir de


méthodes .NET dans ASP.NET Core Blazor.

L’importation dynamique avec l’import()opérateur est prise en charge avec ASP.NET


Core et Blazor :

JavaScript

if ({CONDITION}) import("/additionalModule.js");

Dans l’exemple précédent, l’espace {CONDITION} réservé représente une vérification


conditionnelle pour déterminer si le module doit être chargé.

Pour la compatibilité du navigateur, consultez Puis-je utiliser : modules JavaScript :


importation dynamique .

Fichiers JavaScript mis en cache


Les fichiers JavaScript (JS) et d’autres ressources statiques ne sont généralement pas mis
en cache sur les clients pendant le développement dans l’environnement Development.
Au cours du développement, les demandes de ressources statiques incluent l’en-tête
Cache-Control avec une valeur de no-cache ou max-age avec une valeur nulle
( 0 ).

Pendant la production dans l’environnement Production, les fichiers JS sont


généralement mis en cache par les clients.

Pour désactiver la mise en cache côté client dans les navigateurs, les développeurs
adoptent généralement l’une des approches suivantes :

Désactivez la mise en cache quand la console des outils de développement du


navigateur est ouverte. Vous trouverez des conseils dans la documentation des
outils de développement de chaque chargé de maintenance du navigateur :
Outils de développement de Chrome
Outils de développement de Firefox
Présentation des outils de développement de Microsoft Edge
Effectuez une actualisation manuelle dans le navigateur de n’importe quelle page
web de l’application Blazor pour recharger les fichiers JS à partir du serveur. Le
middleware (intergiciel) de mise en cache HTTP d’ASP.NET Core respecte toujours
un en-tête Cache-Control valide sans cache, envoyé par un client.

Pour plus d'informations, consultez les pages suivantes :

Environnements ASP.NET Core Blazor


Mise en cache des réponses dans ASP.NET Core

Limites de taille sur les appels d’interopérabilité


JavaScript
Cette section s’applique uniquement aux composants interactifs dans les applications côté
serveur. Pour les composants côté client, le cadre n’impose pas de limite à la taille des
entrées et sorties interop JavaScript (JS).

Pour les composants interactifs dans les applications côté serveur, JS appels
d’interopérabilité passant des données du client au serveur sont limités par la taille
maximale des messages entrants SignalR autorisées pour les méthodes hub, appliquées
par HubOptions.MaximumReceiveMessageSize (valeur par défaut : 32 Ko). Les messages
JS vers .NET SignalR supérieurs à MaximumReceiveMessageSize génèrent une erreur. Le
framework n’impose pas de limite à la taille d’un message SignalR du hub vers un client.
Pour plus d’informations sur la limite de taille, les messages d’erreur et les conseils sur la
gestion des limites de taille des messages, consultez ASP.NET guide de BlazorSignalR
Core.

Déterminer où l’application est en cours


d’exécution
S’il est pertinent pour l’application de savoir où le code est en cours d’exécution pour
les appels d’interopérabilité JS, utilisez OperatingSystem.IsBrowser pour déterminer si le
composant s’exécute dans le contexte du navigateur sur WebAssembly.
6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Appeler des fonctions JavaScript à partir
de méthodes .NET dans ASP.NET Core
Blazor
Article • 27/12/2023

Cet article explique comment appeler des fonctions JavaScript (JS) à partir de .NET.

Pour plus d’informations sur l’appel de méthodes .NET à partir de JS, consultez Appeler
des méthodes .NET à partir de fonctions JavaScript dans ASP.NET Core Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Invoquer les fonctions JS


IJSRuntime est inscrit par le framework Blazor. Pour appeler JS à partir de .NET, injectez
l’abstraction IJSRuntime et appelez l’une des méthodes suivantes :

IJSRuntime.InvokeAsync
JSRuntimeExtensions.InvokeAsync
JSRuntimeExtensions.InvokeVoidAsync

Pour les méthodes .NET précédentes qui appellent des fonctions JS :

L’identificateur de fonction ( String ) est relatif à l’étendue globale ( window ). Pour


appeler window.someScope.someFunction , l’identificateur est
someScope.someFunction . Il n’est pas nécessaire d’inscrire la fonction avant de

l’appeler.
Transmettez n’importe quel nombre d’arguments sérialisables JSON dans Object[]
à une fonction JS.
Le jeton d’annulation ( CancellationToken ) propage une notification indiquant que
les opérations doivent être annulées.
TimeSpan représente une limite de temps pour une opération JS.

Le type de retour TValue doit également être sérialisable JSON. TValue doit
correspondre au type .NET qui correspond le mieux au type JSON retourné.
Un JS Promise est retournée pour les méthodes InvokeAsync . InvokeAsync
désenveloppe le Promise et retourne la valeur attendue par le Promise .

Pour les applications Blazor avec le prérendu activé, qui est le comportement par défaut
pour les applications côté serveur, l’appel à JS n’est pas possible lors du prérendu. Pour
plus d’informations, consultez la section Prérendu.

L’exemple suivant est basé sur TextDecoder , un décodeur basé sur JS. L’exemple
montre comment appeler une fonction JS à partir d’une méthode C# qui décharge une
exigence du code du développeur vers une API JS existante. La fonction JS accepte un
tableau d’octets à partir d’une méthode C#, décode le tableau et retourne le texte au
composant à afficher.

HTML

<script>
window.convertArray = (win1251Array) => {
var win1251decoder = new TextDecoder('windows-1251');
var bytes = new Uint8Array(win1251Array);
var decodedArray = win1251decoder.decode(bytes);
console.log(decodedArray);
return decodedArray;
};
</script>

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

Le composant suivant :

Appelle la fonction convertArray JS avec InvokeAsync lors de la sélection d’un


bouton ( Convert Array ).
Une fois la fonction JS appelée, le tableau passé est converti en chaîne. La chaîne
est retournée au composant pour l’affichage ( text ).

CallJs1.razor :

razor

@page "/call-js-1"
@inject IJSRuntime JS

<PageTitle>Call JS 1</PageTitle>

<h1>Call JS Example 1</h1>

<p>
<button @onclick="ConvertArray">Convert Array</button>
</p>

<p>
@text
</p>
<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0472710/">David Krumholtz on
IMDB</a>
</p>

@code {
private MarkupString text;

private uint[] quoteArray =


new uint[]
{
60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112,
32,
116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85,
110,
105, 118, 101, 114, 115, 101, 10, 10,
};

private async Task ConvertArray()


{
text = new(await JS.InvokeAsync<string>("convertArray",
quoteArray));
}
}

API JavaScript limitée aux mouvements de


l’utilisateur
Cette section s’applique aux composants côté serveur.

Certaines API JavaScript (JS) du navigateur ne peuvent être exécutées que dans le
contexte d’un mouvement utilisateur, par exemple à l’aide de Fullscreen API
(documentation MDN) . Ces API ne peuvent pas être appelées par le biais du
mécanisme d’interopérabilité JS dans les composants côté serveur, car la gestion des
événements d’interface utilisateur est effectuée de manière asynchrone et généralement
hors du contexte du mouvement utilisateur. L’application doit gérer complètement
l’événement d’interface utilisateur en JavaScript. Utilisez donc onclick au lieu de
l’attribut de directive @onclick de Blazor.

Appeler des fonctions JavaScript sans lire une


valeur retournée ( InvokeVoidAsync )
Utilisez InvokeVoidAsync quand :

.NET n’est pas nécessaire pour lire le résultat d’un appel JavaScript (JS).
Les fonctions JS retournent void(0)/void 0 ou undefined .

Fournissez une fonction displayTickerAlert1 JS. La fonction est appelée avec


InvokeVoidAsync et ne retourne pas de valeur :

HTML

<script>
window.displayTickerAlert1 = (symbol, price) => {
alert(`${symbol}: $${price}!`);
};
</script>

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

Exemple ( InvokeVoidAsync ) de composant ( .razor )


TickerChanged appelle la méthode handleTickerChanged1 dans le composant suivant.

CallJs2.razor :

razor

@page "/call-js-2"
@inject IJSRuntime JS

<PageTitle>Call JS 2</PageTitle>

<h1>Call JS Example 2</h1>

<p>
<button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)


{
<p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
private Random r = new();
private string? stockSymbol;
private decimal price;

private async Task SetStock()


{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
await JS.InvokeVoidAsync("displayTickerAlert1", stockSymbol, price);
}
}

Exemple ( InvokeVoidAsync ) de classe ( .cs )


JsInteropClasses1.cs :

C#

using Microsoft.JSInterop;

namespace BlazorSample;

public class JsInteropClasses1(IJSRuntime js) : IDisposable


{
private readonly IJSRuntime js = js;

public async ValueTask TickerChanged(string symbol, decimal price)


{
await js.InvokeVoidAsync("displayTickerAlert1", symbol, price);
}

public void Dispose()


{
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
}

TickerChanged appelle la méthode handleTickerChanged1 dans le composant suivant.

CallJs3.razor :

razor

@page "/call-js-3"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 3</PageTitle>

<h1>Call JS Example 3</h1>

<p>
<button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)


{
<p>@stockSymbol price: @price.ToString("c")</p>
}

@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;

protected override void OnInitialized()


{
jsClass = new(JS);
}

private async Task SetStock()


{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0,
26))}";
price = r.Next(1, 101);
await jsClass.TickerChanged(stockSymbol, price);
}
}

public void Dispose() => jsClass?.Dispose();


}

Appeler des fonctions JavaScript et lire une


valeur retournée ( InvokeAsync )
Utilisez InvokeAsync quand .NET doit lire le résultat d’un appel JavaScript (JS).

Fournissez une fonction displayTickerAlert2 JS. L’exemple suivant retourne une chaîne
pour l’affichage par l’appelant :

HTML
<script>
window.displayTickerAlert2 = (symbol, price) => {
if (price < 20) {
alert(`${symbol}: $${price}!`);
return "User alerted in the browser.";
} else {
return "User NOT alerted.";
}
};
</script>

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

Exemple ( InvokeAsync ) de composant ( .razor )


TickerChanged appelle la méthode handleTickerChanged2 et affiche la chaîne retournée

dans le composant suivant.

CallJs4.razor :

razor

@page "/call-js-4"
@inject IJSRuntime JS

<PageTitle>Call JS 4</PageTitle>

<h1>Call JS Example 4</h1>

<p>
<button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)


{
<p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)


{
<p>@result</p>
}
@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
private string? result;

private async Task SetStock()


{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0, 26))}";
price = r.Next(1, 101);
var interopResult =
await JS.InvokeAsync<string>("displayTickerAlert2", stockSymbol,
price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}

Exemple ( InvokeAsync ) de classe ( .cs )


JsInteropClasses2.cs :

C#

using Microsoft.JSInterop;

namespace BlazorSample;

public class JsInteropClasses2(IJSRuntime js) : IDisposable


{
private readonly IJSRuntime js = js;

public async ValueTask<string> TickerChanged(string symbol, decimal


price)
{
return await js.InvokeAsync<string>("displayTickerAlert2", symbol,
price);
}

public void Dispose()


{
// The following prevents derived types that introduce a
// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
}

TickerChanged appelle la méthode handleTickerChanged2 et affiche la chaîne retournée

dans le composant suivant.


CallJs5.razor :

razor

@page "/call-js-5"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call JS 5</PageTitle>

<h1>Call JS Example 5</h1>

<p>
<button @onclick="SetStock">Set Stock</button>
</p>

@if (stockSymbol is not null)


{
<p>@stockSymbol price: @price.ToString("c")</p>
}

@if (result is not null)


{
<p>@result</p>
}

@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;

protected override void OnInitialized()


{
jsClass = new(JS);
}

private async Task SetStock()


{
if (jsClass is not null)
{
stockSymbol =
$"{(char)('A' + r.Next(0, 26))}{(char)('A' + r.Next(0,
26))}";
price = r.Next(1, 101);
var interopResult = await jsClass.TickerChanged(stockSymbol,
price);
result = $"Result of TickerChanged call for {stockSymbol} at " +
$"{price.ToString("c")}: {interopResult}";
}
}
public void Dispose() => jsClass?.Dispose();
}

Scénarios de génération de contenu


dynamique
Pour la génération de contenu dynamique avec BuildRenderTree, utilisez l’attribut
[Inject] :

razor

[Inject]
IJSRuntime JS { get; set; }

Prérendu
Cette section s’applique aux applications côté serveur et hébergées qui effectuent un pré-
rendu des composants Razor. Le prérendu est couvert dans Prérendu des composants
ASP.NET Core Razor.

Lorsqu’une application est en cours de prérendu, certaines actions, comme l’appel à


JavaScript (JS), ne sont pas possibles.

Dans l’exemple suivant, la fonction setElementText1 est appelée avec


JSRuntimeExtensions.InvokeVoidAsync et ne retourne pas de valeur.

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

HTML

<script>
window.setElementText1 = (element, text) => element.innerText = text;
</script>

2 Avertissement
L’exemple précédent modifie le modèle DOM directement à des fins de
démonstration uniquement. La modification directe du DOM avec JS n’est pas
recommandée dans la plupart des scénarios, car JS peut interférer avec le suivi des
modifications de Blazor. Pour plus d’informations, consultez Interopérabilité
JavaScript et ASP.NET Core Blazor (interopérabilité JS).

L’événement de cycle de vie OnAfterRender{Async} n’est pas appelé pendant le


processus de prérendu sur le serveur. Remplacez la méthode OnAfterRender{Async}
pour retarder les appels d’interopérabilité JS jusqu’à ce que le composant soit rendu et
interactif sur le client après le prérendu.

PrerenderedInterop1.razor :

razor

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS

<div @ref="divElement">Text during render</div>

@code {
private ElementReference divElement;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
await JS.InvokeVoidAsync(
"setElementText1", divElement, "Text after render");
}
}
}

7 Notes

L’exemple précédent pollue le client avec des méthodes globales. Pour une
meilleure approche dans les applications de production, consultez Isolation
JavaScript dans les modules JavaScript.

Exemple :

JavaScript

export setElementText1 = (element, text) => element.innerText = text;


Le composant suivant montre comment utiliser l’interopérabilité JS dans le cadre de la
logique d’initialisation d’un composant d’une manière compatible avec le prérendu. Le
composant indique qu’il est possible de déclencher une mise à jour de rendu à partir de
OnAfterRenderAsync. Le développeur doit veiller à éviter de créer une boucle infinie
dans ce scénario.

Dans l’exemple suivant, la fonction setElementText2 est appelée avec


IJSRuntime.InvokeAsync et retourne une valeur.

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

HTML

<script>
window.setElementText2 = (element, text) => {
element.innerText = text;
return text;
};
</script>

2 Avertissement

L’exemple précédent modifie le modèle DOM directement à des fins de


démonstration uniquement. La modification directe du DOM avec JS n’est pas
recommandée dans la plupart des scénarios, car JS peut interférer avec le suivi des
modifications de Blazor. Pour plus d’informations, consultez Interopérabilité
JavaScript et ASP.NET Core Blazor (interopérabilité JS).

Là où JSRuntime.InvokeAsync est appelé, le ElementReference est utilisé uniquement


dans OnAfterRenderAsync et non dans une méthode de cycle de vie antérieure, car il n’y
a aucun élément JS avant le rendu du composant.

StateHasChanged est appelé pour renvoyer le composant avec le nouvel état obtenu à
partir de l’appel d’interopérabilité JS (pour plus d’informations, consultez Rendu de
composants ASP.NET Core Razor). Le code ne crée pas de boucle infinie, car
StateHasChanged est appelé uniquement lorsque data est null .

PrerenderedInterop2.razor :
razor

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<p>
Get value via JS interop call:
<strong id="val-get-by-interop">@(data ?? "No value yet")</strong>
</p>

<p>
Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
private string? data;
private ElementReference divElement;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender && data == null)
{
data = await JS.InvokeAsync<string>(
"setElementText2", divElement, "Hello from interop call!");

StateHasChanged();
}
}
}

7 Notes

L’exemple précédent pollue le client avec des méthodes globales. Pour une
meilleure approche dans les applications de production, consultez Isolation
JavaScript dans les modules JavaScript.

Exemple :

JavaScript

export setElementText2 = (element, text) => {


element.innerText = text;
return text;
};
Interopérabilité JS synchrone dans les
composants côté client
Cette section s’applique uniquement aux composants côté client.

Les appels d’interopérabilité JS sont asynchrones par défaut, que le code appelé soit
synchrone ou asynchrone. Les appels sont asynchrones par défaut pour garantir que les
composants sont compatibles entre les modes de rendu côté serveur et côté client. Sur
le serveur, tous les appels interop JS doivent être asynchrones car ils sont envoyés via
une connexion réseau.

Si vous savez avec certitude que votre composant s'exécute uniquement sur
WebAssembly, vous pouvez choisir d'effectuer des appels interop synchrones JS. Cela
représente un peu moins de surcharge que d’effectuer des appels asynchrones, et peut
entraîner moins de cycles de rendu, car il n’y a pas d’état intermédiaire lors de l’attente
des résultats.

Pour effectuer un appel synchrone de .NET vers JavaScript dans un composant côté
client, convertissez IJSRuntime en IJSInProcessRuntime pour effectuer l'appel
d'interopérabilité JS :

razor

@inject IJSRuntime JS

...

@code {
protected override void HandleSomeEvent()
{
var jsInProcess = (IJSInProcessRuntime)JS;
var value = jsInProcess.Invoke<string>
("javascriptFunctionIdentifier");
}
}

Lorsque vous travaillez avec IJSObjectReference des composants côté client dans
ASP.NET Core 5.0 ou version ultérieure, vous pouvez utiliser
IJSInProcessObjectReference de manière synchrone. IJSInProcessObjectReference
implémente IAsyncDisposable/IDisposable et doit être supprimé à des fins de nettoyage
de la mémoire pour empêcher une fuite de mémoire, comme l’illustre l’exemple suivant :

razor
@inject IJSRuntime JS
@implements IAsyncDisposable

...

@code {
...
private IJSInProcessObjectReference? module;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
module = await JS.InvokeAsync<IJSInProcessObjectReference>
("import",
"./scripts.js");
}
}

...

async ValueTask IAsyncDisposable.DisposeAsync()


{
if (module is not null)
{
await module.DisposeAsync();
}
}
}

Emplacement JavaScript
Chargez du code JavaScript (JS) à l’aide de l’une des approches décrites dans l’article
Vue d’ensemble de l’interopérabilité (interop) JavaScript (JS) :

Charger un script dans le balisage <head> (pas généralement recommandé)


Charger un script dans le balisage <body>
Charger un script à partir d’un fichier JavaScript externe (.js) colocalisé avec un
composant
Charger un script à partir d’un fichier JavaScript externe (.js)
Injecter un script avant ou après que Blazor démarre

Pour plus d’informations sur l’isolation des scripts dans les modules JS , consultez la
section Isolation JavaScript dans les modules JavaScript.

2 Avertissement
Ne placez pas une balise <script> dans un fichier de composant ( .razor ) car la
balise <script> ne peut pas être mise à jour dynamiquement.

Isolation JavaScript dans les modules JavaScript


Blazor active l’isolation JavaScript (JS) dans les modules JavaScript standard
(spécification ECMAScript ). Le chargement de module JavaScript fonctionne de la
même manière dans Blazor que pour d’autres types d’applications web, et vous êtes
libre de personnaliser la façon dont les modules sont définis dans votre application.
Pour obtenir un guide sur l’utilisation des modules JavaScript, consultez MDN Web
Docs : modules JavaScript .

L’isolation JS offre les avantages suivants :

Le code JS importé ne pollue plus l’espace de noms global.


Les consommateurs d’une bibliothèque et de composants ne sont pas tenus
d’importer le code JS associé.

L’importation dynamique avec l’import()opérateur est prise en charge avec ASP.NET


Core et Blazor :

JavaScript

if ({CONDITION}) import("/additionalModule.js");

Dans l’exemple précédent, l’espace {CONDITION} réservé représente une vérification


conditionnelle pour déterminer si le module doit être chargé.

Pour la compatibilité du navigateur, consultez Puis-je utiliser : modules JavaScript :


importation dynamique .

Par exemple, le module JS suivant exporte une fonction JS pour afficher une invite de
fenêtre de navigateur . Placez le code JS suivant dans un fichier JS externe.

wwwroot/scripts.js :

JavaScript

export function showPrompt(message) {


return prompt(message, 'Type anything here');
}
Ajoutez le module JS précédent à une application ou une bibliothèque de classes en
tant que ressource web statique dans le dossier wwwroot , puis importez le module dans
le code .NET en appelant InvokeAsync sur l’instance IJSRuntime.

IJSRuntime importe le module en tant que IJSObjectReference, qui représente une


référence à un objet JS à partir du code .NET. Utilisez IJSObjectReference pour appeler
des fonctions JS exportées à partir du module.

CallJs6.razor :

razor

@page "/call-js-6"
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call JS 6</PageTitle>

<h1>Call JS Example 6</h1>

<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>

<p>
@result
</p>

@code {
private IJSObjectReference? module;
private string? result;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import",
"./scripts.js");
}
}

private async Task TriggerPrompt()


{
result = await Prompt("Provide some text");
}

public async ValueTask<string?> Prompt(string message) =>


module is not null ?
await module.InvokeAsync<string>("showPrompt", message) : null;

async ValueTask IAsyncDisposable.DisposeAsync()


{
if (module is not null)
{
await module.DisposeAsync();
}
}
}

Dans l'exemple précédent :

Par convention, l’identificateur import est un identificateur spécial utilisé


spécifiquement pour importer un module JS.
Spécifiez le fichier JS externe du module à l’aide de son chemin d’accès de
ressource web statique stable : ./{SCRIPT PATH AND FILE NAME (.js)} , où :
Le segment de chemin pour le répertoire actuel ( ./ ) est requis pour créer le
chemin de ressource statique correct vers le fichier JS.
L’espace réservé {SCRIPT PATH AND FILE NAME (.js)} correspond au chemin et
au nom de fichier sous wwwroot .
Supprime le IJSObjectReference pour le nettoyage de la mémoire dans
IAsyncDisposable.DisposeAsync.

L’importation dynamique d’un module nécessite une requête réseau, de sorte qu’elle ne
peut être obtenue de manière asynchrone qu’en appelant InvokeAsync.

IJSInProcessObjectReference représente une référence à un objet JS dont les fonctions

peuvent être appelées de manière synchrone dans les composants côté client. Pour plus
d’informations, consultez la section Interopérabilité JS synchrone dans les composants
côté client.

7 Notes

Quand le fichier JS externe est fourni par une bibliothèque de classes Razor,
spécifiez le fichier JS du module à l’aide de son chemin de ressource web statique
stable : ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILE NAME (.js)} :

Le segment de chemin pour le répertoire actuel ( ./ ) est requis pour créer le


chemin de ressource statique correct vers le fichier JS.
L’espace réservé {PACKAGE ID} est l’ID de package de la bibliothèque. L’ID de
package est défini par défaut sur le nom d’assembly du projet si <PackageId>
n’est pas spécifié dans le fichier projet. Dans l’exemple suivant, le nom de
l’assembly de la bibliothèque est ComponentLibrary , et le fichier projet de la
bibliothèque ne spécifie pas <PackageId> .
L’espace réservé {SCRIPT PATH AND FILE NAME (.js)} correspond au chemin et
au nom de fichier sous wwwroot . Dans l’exemple suivant, le fichier JS externe
( script.js ) est placé dans le dossier wwwroot de la bibliothèque de classes.

C#

var module = await js.InvokeAsync<IJSObjectReference>(


"import", "./_content/ComponentLibrary/scripts.js");

Pour plus d’informations, consultez Consommer des composants ASP.NET Core


Razor à partir d’une bibliothèque de classes (RCL) Razor.

Dans la documentation Blazor, les exemples utilisent l’extension de fichier .js pour les
fichiers de module, et non l’extension de fichier .mjs plus récente (RFC 9239) . Notre
documentation continue d’utiliser l’extension de fichier .js pour les mêmes raisons que
la documentation de Mozilla Foundation continue d’utiliser l’extension de fichier .js .
Pour plus d’informations, consultez Aparté : mjs ou .js (documentation MDN) .

Capturer des références à des éléments


Certains scénarios d’interopérabilité JavaScript (JS) nécessitent des références à des
éléments HTML. Par exemple, une bibliothèque d’interface utilisateur peut nécessiter
une référence d’élément pour l’initialisation, ou vous devrez peut-être appeler des API
de type commande sur un élément, comme click ou play .

Capturez des références à des éléments HTML dans un composant à l’aide de l’approche
suivante :

Ajoutez un attribut @ref à l’élément HTML.


Définissez un champ de type ElementReference dont le nom correspond à la valeur
de l’attribut @ref .

L’exemple suivant montre la capture d’une référence à l’élément username <input> :

razor

<input @ref="username" ... />

@code {
private ElementReference username;
}
2 Avertissement

Utilisez uniquement une référence d’élément pour muter le contenu d’un élément
vide qui n’interagit pas avec Blazor. Ce scénario est utile lorsqu’une API tierce
fournit du contenu à l’élément. Étant donné que Blazor n’interagit pas avec
l’élément, il n’existe aucun risque de conflit entre la représentation de Blazor de
l’élément et le modèle DOM.

Dans l’exemple suivant, il est dangereux de muter le contenu de la liste non triée
( ul ) à l’aide de MyList via l’interopérabilité JS, car Blazor interagit avec le DOM
pour remplir les éléments de liste de cet élément ( <li> ) à partir de l’objet Todos :

razor

<ul @ref="MyList">
@foreach (var item in Todos)
{
<li>@item.Text</li>
}
</ul>

L’utilisation de la référence d’élément MyList pour simplement lire le contenu


DOM ou déclencher un événement est prise en charge.

Si l’interopérabilité JSmute le contenu de l’élément MyList et que Blazor tente


d’appliquer des différences à l’élément, les différences ne correspondent pas au
DOM. La modification du contenu de la liste via l’interopérabilité JS avec la
référence d’élément MyList n’est pas prise en charge.

Pour plus d’informations, consultez Interopérabilité JavaScript et ASP.NET Core


Blazor (interopérabilité JS).

Un ElementReference est transmis au code JS via l’interopérabilité JS. Le code JS reçoit


une instance HTMLElement , qu’elle peut utiliser avec des API DOM normales. Par
exemple, le code suivant définit une méthode d’extension .NET ( TriggerClickEvent ) qui
permet d’envoyer un clic de souris à un élément.

La fonction JS clickElement crée un événement click sur l’élément HTML passé


( element ) :

JavaScript
window.interopFunctions = {
clickElement : function (element) {
element.click();
}
}

Pour appeler une fonction JS qui ne retourne pas de valeur, utilisez


JSRuntimeExtensions.InvokeVoidAsync. Le code suivant déclenche un événement click
côté client en appelant la fonction JS précédente avec le ElementReference capturé :

razor

@inject IJSRuntime JS

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
Trigger click event on <code>Example Button</code>
</button>

@code {
private ElementReference exampleButton;

public async Task TriggerClick()


{
await JS.InvokeVoidAsync(
"interopFunctions.clickElement", exampleButton);
}
}

Pour utiliser une méthode d’extension, créez une méthode d’extension statique qui
reçoit l’instance IJSRuntime :

C#

public static async Task TriggerClickEvent(this ElementReference elementRef,


IJSRuntime js)
{
await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

La méthode clickElement est appelée directement sur l’objet. L’exemple suivant


suppose que la méthode TriggerClickEvent est disponible à partir de l’espace de noms
JsInteropClasses :

razor
@inject IJSRuntime JS
@using JsInteropClasses

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
Trigger click event on <code>Example Button</code>
</button>

@code {
private ElementReference exampleButton;

public async Task TriggerClick()


{
await exampleButton.TriggerClickEvent(JS);
}
}

) Important

La variable exampleButton est renseignée uniquement après le rendu du


composant. Si un ElementReference non rempli est passé au code JS, le code JS
reçoit une valeur de null . Pour manipuler les références d’élément une fois le
rendu du composant terminé, utilisez les méthodes de cycle de vie de composant
OnAfterRenderAsync ou OnAfterRender.

Lorsque vous utilisez des types génériques et retournez une valeur, utilisez
ValueTask<TResult> :

C#

public static ValueTask<T> GenericMethod<T>(this ElementReference


elementRef,
IJSRuntime js)
{
return js.InvokeAsync<T>("{JAVASCRIPT FUNCTION}", elementRef);
}

L’espace réservé {JAVASCRIPT FUNCTION} est l’identificateur de fonction JS.

GenericMethod est appelé directement sur l’objet avec un type. L’exemple suivant

suppose que le GenericMethod est disponible à partir de l’espace de noms


JsInteropClasses :

razor
@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />

<button @onclick="OnClickMethod">Do something generic</button>

<p>
returnValue: @returnValue
</p>

@code {
private ElementReference username;
private string? returnValue;

private async Task OnClickMethod()


{
returnValue = await username.GenericMethod<string>(JS);
}
}

Référencer des éléments entre composants


Un ElementReference ne peut pas être passé entre composants pour les raisons
suivantes :

L’existence de l’instance n’est garantie qu’une fois que le composant a été rendu,
c’est-à-dire pendant ou après l’exécution de la méthode
OnAfterRender/OnAfterRenderAsync d’un composant.
Un ElementReference est un struct, qui ne peut pas être passé en tant que
paramètre de composant.

Pour qu’un composant parent rende une référence d’élément disponible pour d’autres
composants, le composant parent peut :

Autoriser les composants enfants à inscrire des rappels.


Appeler les rappels inscrits pendant l’événement OnAfterRender avec la référence
d’élément passée. Indirectement, cette approche permet aux composants enfants
d’interagir avec la référence d’élément parent.

HTML

<style>
.red { color: red }
</style>
HTML

<script>
function setElementClass(element, className) {
var myElement = element;
myElement.classList.add(className);
}
</script>

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

CallJs7.razor (composant parent) :

razor

@page "/call-js-7"

<PageTitle>Call JS 7</PageTitle>

<h1>Call JS Example 7</h1>

<h2 @ref="title">Hello, world!</h2>

Welcome to your new app.

<SurveyPrompt Parent="@this" Title="How is Blazor working for you?" />

CallJs7.razor.cs :

C#

using Microsoft.AspNetCore.Components;

namespace BlazorSample.Pages;

public partial class CallJs7 :


ComponentBase, IObservable<ElementReference>, IDisposable
{
private bool disposing;
private readonly List<IObserver<ElementReference>> subscriptions = [];
private ElementReference title;

protected override void OnAfterRender(bool firstRender)


{
base.OnAfterRender(firstRender);

foreach (var subscription in subscriptions)


{
try
{
subscription.OnNext(title);
}
catch (Exception)
{
throw;
}
}
}

public void Dispose()


{
disposing = true;

foreach (var subscription in subscriptions)


{
try
{
subscription.OnCompleted();
}
catch (Exception)
{
}
}

subscriptions.Clear();

// The following prevents derived types that introduce a


// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}

public IDisposable Subscribe(IObserver<ElementReference> observer)


{
if (disposing)
{
throw new InvalidOperationException("Parent being disposed");
}

subscriptions.Add(observer);

return new Subscription(observer, this);


}

private class Subscription(IObserver<ElementReference> observer,


CallJs7 self) : IDisposable
{
public IObserver<ElementReference> Observer { get; } = observer;
public CallJs7 Self { get; } = self;
public void Dispose() => Self.subscriptions.Remove(Observer);
}
}

Dans l’exemple précédent, l’espace de noms de l’application est BlazorSample . Si vous


testez le code localement, mettez à jour l’espace de noms.

SurveyPrompt.razor (composant enfant) :

razor

<div class="alert alert-secondary mt-4">


<span class="oi oi-pencil me-2" aria-hidden="true"></span>
<strong>@Title</strong>

<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold link-dark"
href="https://go.microsoft.com/fwlink/?linkid=2186158">brief survey</a>
</span>
and tell us what you think.
</div>

@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string? Title { get; set; }
}

SurveyPrompt.razor.cs :

C#

using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;

namespace BlazorSample.Components;

public partial class SurveyPrompt :


ComponentBase, IObserver<ElementReference>, IDisposable
{
private IDisposable? subscription = null;

[Parameter]
public IObservable<ElementReference>? Parent { get; set; }

[Inject]
public IJSRuntime? JS {get; set;}

protected override void OnParametersSet()


{
base.OnParametersSet();

subscription?.Dispose();
subscription = Parent?.Subscribe(this);
}

public void OnCompleted()


{
subscription = null;
}

public void OnError(Exception error)


{
subscription = null;
}

public void OnNext(ElementReference value)


{
_ = (JS?.InvokeAsync<object>(
"setElementClass", [value, "red"]));
}

public void Dispose()


{
subscription?.Dispose();

// The following prevents derived types that introduce a


// finalizer from needing to re-implement IDisposable.
GC.SuppressFinalize(this);
}
}

Dans l’exemple précédent, l’espace de noms de l’application est BlazorSample avec des
composants partagés dans le dossier Shared . Si vous testez le code localement, mettez
à jour l’espace de noms.

Renforcer les appels d’interopérabilité


JavaScript
Cette section s’applique seulement aux composants de serveur interactif, mais les
composants côté client peuvent également définir des délais d’expiration d’interopérabilité
JS si les conditions le justifient.

Dans les applications côté serveur avec interactivité de serveur, l’interopérabilité


JavaScript (JS) peut échouer en raison d’erreurs de mise en réseau, et doit être traitée
comme non fiable. Par défaut, les applications Blazor utilisent un délai d’expiration d’une
minute pour les appels d’interopérabilité JS. Si une application peut tolérer un délai
d’expiration plus agressif, définissez le délai d’expiration à l’aide de l’une des approches
suivantes.

Définissez un délai global dans le Program.cs avec


CircuitOptions.JSInteropDefaultCallTimeout :

C#

builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(options =>
options.JSInteropDefaultCallTimeout = {TIMEOUT});

L’espace réservé {TIMEOUT} est un TimeSpan (par exemple, TimeSpan.FromSeconds(80) ).

Définissez un délai d’expiration par appel dans le code du composant. Le délai


d’expiration spécifié remplace le délai d’attente global défini par
JSInteropDefaultCallTimeout :

C#

var result = await JS.InvokeAsync<string>("{ID}", {TIMEOUT}, new[] { "Arg1"


});

Dans l'exemple précédent :

L’espace réservé {TIMEOUT} est un TimeSpan (par exemple,


TimeSpan.FromSeconds(80) ).
L’espace réservé {ID} est l’identificateur de la fonction à appeler. Par exemple, la
valeur someScope.someFunction appelle la fonction window.someScope.someFunction .

Bien qu’une cause courante d’échecs d’interopérabilité JS soit des défaillances réseau
avec des composants côté serveur, les délais d’expiration par appel peuvent être définis
pour les appels d’interopérabilité JS pour les composants côté client. Bien qu’aucun
circuit SignalR n’existe pour un composant côté client, les appels d’interopérabilité JS
peuvent échouer pour d’autres raisons qui s’appliquent.

Pour plus d’informations sur l’épuisement des ressources, voir Threat mitigation
guidance for ASP.NET CoreBlazor interactive server-side rendering.

Éviter les références d’objets circulaires


Les objets qui contiennent des références circulaires ne peuvent pas être sérialisés sur le
client pour :
Les appels de méthode .NET.
Les appels de méthode JavaScript à partir de C# lorsque le type de retour a des
références circulaires.

Bibliothèques JavaScript qui effectuent le


rendu de l’interface utilisateur
Parfois, vous souhaiterez peut-être utiliser des bibliothèques JavaScript (JS) qui
produisent des éléments d’interface utilisateur visibles dans le modèle DOM du
navigateur. À première vue, cela peut sembler difficile, car le système de fonctions
différentielles de Blazor s’appuie sur le contrôle sur l’arborescence des éléments DOM et
rencontre des erreurs si un code externe mute l’arborescence DOM et invalide son
mécanisme pour appliquer les différences. Il ne s’agit pas d’une limitation propre à
Blazor. Le même problème se présente avec n’importe quel framework d’interface
utilisateur basé sur l’analyse différentielle.

Heureusement, il est facile d’incorporer l’interface utilisateur générée en externe dans


une interface utilisateur de composant Razor de manière fiable. La technique
recommandée consiste à ce que le code du composant (fichier .razor ) produise un
élément vide. En ce qui concerne le système d’analyse des différences de Blazor,
l’élément est toujours vide, de sorte que le convertisseur n’itère pas dans l’élément et
laisse son contenu inchangé. Cela permet de remplir l’élément avec du contenu
arbitraire géré en externe.

L’exemple suivant illustre le concept. Dans l’instruction if lorsque firstRender est


true , interagissez avec unmanagedElement en dehors de Blazor à l’aide de

l’interopérabilité JS. Par exemple, appelez une bibliothèque JS externe pour remplir
l’élément. Blazor laisse le contenu de l’élément inchangé jusqu’à ce que ce composant
soit supprimé. Lorsque le composant est supprimé, l’ensemble de la sous-arborescence
DOM du composant est également supprimé.

razor

<h1>Hello! This is a Razor component rendered at @DateTime.Now</h1>

<div @ref="unmanagedElement"></div>

@code {
private ElementReference unmanagedElement;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
...
}
}
}

Prenons l’exemple suivant qui affiche une carte interactive à l’aide des API Mapbox open
source .

Le module JS suivant est placé dans l’application ou mis à disposition à partir d’une
bibliothèque de classes Razor.

7 Notes

Pour créer la carte Mapbox , obtenez un jeton d’accès à partir de Mapbox Sign
in et fournissez-le là où {ACCESS TOKEN} apparaît le dans le code suivant.

wwwroot/mapComponent.js :

JavaScript

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {


return new mapboxgl.Map({
container: element,
style: 'mapbox://styles/mapbox/streets-v11',
center: [-74.5, 40],
zoom: 9
});
}

export function setMapCenter(map, latitude, longitude) {


map.setCenter([longitude, latitude]);
}

Pour produire un style correct, ajoutez la balise de feuille de style suivante à la page
HTML hôte.

Ajoutez l’élément <link> suivant au balisage d’élément <head> (emplacement de


<head>contenu) :

HTML
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css"
rel="stylesheet" />

CallJs8.razor :

razor

@page "/call-js-8"
@implements IAsyncDisposable
@inject IJSRuntime JS

<PageTitle>Call JS 8</PageTitle>

<HeadContent>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css"
rel="stylesheet" />
</HeadContent>

<h1>Call JS Example 8</h1>

<div @ref="mapElement" style='width:400px;height:300px'></div>

<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol,


UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo,
Japan</button>

@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
mapModule = await JS.InvokeAsync<IJSObjectReference>(
"import", "./mapComponent.js");
mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
"addMapToElement", mapElement);
}
}

private async Task ShowAsync(double latitude, double longitude)


{
if (mapModule is not null && mapInstance is not null)
{
await mapModule.InvokeVoidAsync("setMapCenter", mapInstance,
latitude, longitude).AsTask();
}
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (mapInstance is not null)
{
await mapInstance.DisposeAsync();
}

if (mapModule is not null)


{
await mapModule.DisposeAsync();
}
}
}

L’exemple précédent génère une interface utilisateur de carte interactive. L’utilisateur :

Possibilité de faire glisser pour faire défiler ou zoomer.


Sélection avec des boutons pour accéder à des emplacements prédéfinis.

Dans l'exemple précédent :

<div> avec @ref="mapElement" est laissé vide en ce qui concerne Blazor. Le script

mapbox-gl.js peut remplir en toute sécurité l’élément et modifier son contenu.

Utilisez cette technique avec n’importe quelle bibliothèque JS qui restitue


l’interface utilisateur. Vous pouvez incorporer des composants à partir d’un
framework SPA JS tiers à l’intérieur de composants Razor, tant qu’ils n’essaient pas
d’atteindre et de modifier d’autres parties de la page. Il n’est pas sécurisé pour le
code JS externe de modifier les éléments que Blazor ne considère pas comme
vides.
Lorsque vous utilisez cette approche, gardez à l’esprit les règles relatives à la façon
dont Blazor conserve ou détruit les éléments DOM. Le composant gère en toute
sécurité les événements de clic de bouton et met à jour l’instance de carte
existante, car les éléments DOM sont conservés dans la mesure du possible par
défaut. Si vous affichez une liste d’éléments de carte à partir d’une boucle
@foreach , vous souhaitez utiliser @key pour garantir la conservation des instances

de composant. Sinon, les modifications apportées aux données de liste peuvent


entraîner la conservation de l’état des instances de composant précédentes de
manière indésirable par les instances précédentes. Pour plus d’informations,
découvrez comment utiliser l’attribut de directive @key pour préserver la relation
entre les éléments, les composants et les objets de modèle.
L’exemple encapsule la logique et les dépendances JS dans un module ES6 et
charge le module dynamiquement à l’aide de l’identificateur import . Pour plus
d’informations, consultez Isolation JavaScript dans les modules JavaScript.

Prise en charge des tableaux d’octets


Blazor prend en charge l’interopérabilité JavaScript (JS) des tableaux d’octets optimisés,
qui évite l’encodage/décodage des tableaux d’octets en Base64. L’exemple suivant
utilise l’interopérabilité JS pour passer un tableau d’octets à JavaScript.

Fournissez une fonction receiveByteArray JS. La fonction est appelée avec


InvokeVoidAsync et ne retourne pas de valeur :

HTML

<script>
window.receiveByteArray = (bytes) => {
let utf8decoder = new TextDecoder();
let str = utf8decoder.decode(bytes);
return str;
};
</script>

7 Notes
Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations
pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

CallJs9.razor :

razor

@page "/call-js-9"
@inject IJSRuntime JS

<h1>Call JS Example 9</h1>

<p>
<button @onclick="SendByteArray">Send Bytes</button>
</p>

<p>
@result
</p>

<p>
Quote &copy;2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
private string? result;

private async Task SendByteArray()


{
var bytes = new byte[] { 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68,
0x69,
0x6e, 0x67, 0x27, 0x73, 0x20, 0x73, 0x68, 0x69, 0x6e, 0x79,
0x2c,
0x20, 0x43, 0x61, 0x70, 0x74, 0x69, 0x61, 0x6e, 0x2e, 0x20,
0x4e,
0x6f, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x72, 0x65, 0x74, 0x2e
};

result = await JS.InvokeAsync<string>("receiveByteArray", bytes);


}
}

Diffuser en continu de .NET vers JavaScript


Blazor prend en charge la diffusion en continu de données directement de .NET vers
JavaScript. Les flux sont créés à l’aide de DotNetStreamReference.
DotNetStreamReference représente un flux .NET et utilise les paramètres suivants :

stream : le flux envoyé à JavaScript.

leaveOpen : détermine si le flux est laissé ouvert après la transmission. Si aucune

valeur n’est fournie, leaveOpen prend la valeur par défaut false .

En JavaScript, utilisez une mémoire tampon de tableau ou un flux lisible pour recevoir
les données :

Utilisation d’un ArrayBuffer :

JavaScript

async function streamToJavaScript(streamRef) {


const data = await streamRef.arrayBuffer();
}

Utilisation d’un ReadableStream :

JavaScript

async function streamToJavaScript(streamRef) {


const stream = await streamRef.stream();
}

Dans le code C# :

C#

using var streamRef = new DotNetStreamReference(stream: {STREAM}, leaveOpen:


false);
await JS.InvokeVoidAsync("streamToJavaScript", streamRef);

Dans l'exemple précédent :

L’espace réservé {STREAM} représente le Stream envoyé à JavaScript.


JS est un instance IJSRuntime injectée.

Appeler des méthodes .NET à partir de fonctions JavaScript dans ASP.NET Core Blazor
couvre l’opération inverse, la diffusion en continu de JavaScript vers .NET.

Téléchargements de fichiers ASP.NET Core Blazor explique comment télécharger un


fichier dans Blazor.
Intercepter les exceptions JavaScript
Pour intercepter les exceptions JS, encapsulez l’interopérabilité JS dans un bloc try-catch
et interceptez un JSException.

Dans l’exemple suivant, la fonction nonFunction JS n’existe pas. Lorsque la fonction est
introuvable, la JSException est interceptée par un Message, qui indique l’erreur
suivante :

Could not find 'nonFunction' ('nonFunction' was undefined).

CallJs11.razor :

razor

@page "/call-js-11"
@inject IJSRuntime JS

<PageTitle>Call JS 11</PageTitle>

<h1>Call JS Example 11</h1>

<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>

<p>
@result
</p>

<p>
@errorMessage
</p>

@code {
private string? errorMessage;
private string? result;

private async Task CatchUndefinedJSFunction()


{
try
{
result = await JS.InvokeAsync<string>("nonFunction");
}
catch (JSException e)
{
errorMessage = $"Error Message: {e.Message}";
}
}
}

Abandonner une fonction JavaScript de longue


durée
Utilisez JSAbortController avec CancellationTokenSource dans le composant pour
abandonner une fonction JavaScript de longue durée à partir du code C#.

La classe JS Helpers suivante contient une fonction de longue durée simulée,


longRunningFn , pour compter en continu jusqu’à ce que AbortController.signal

indique l’appel à AbortController.abort . La fonction sleep sert à des fins de


démonstration pour simuler l’exécution lente de la fonction longue, et ne serait pas
présente dans le code de production. Lorsqu’un composant appelle stopFn ,
longRunningFn est signalé pour abandonner via la vérification conditionnelle de la

boucle while sur AbortSignal.aborted .

HTML

<script>
class Helpers {
static #controller = new AbortController();

static async #sleep(ms) {


return new Promise(resolve => setTimeout(resolve, ms));
}

static async longRunningFn() {


var i = 0;
while (!this.#controller.signal.aborted) {
i++;
console.log(`longRunningFn: ${i}`);
await this.#sleep(1000);
}
}

static stopFn() {
this.#controller.abort();
console.log('longRunningFn aborted!');
}
}

window.Helpers = Helpers;
</script>
7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

Le composant suivant :

Appelle la fonction JS longRunningFn lorsque le bouton Start Task est sélectionné.


Un CancellationTokenSource est utilisé pour gérer l’exécution de la fonction de
longue durée. CancellationToken.Register définit un délégué d’appel
d’interopérabilité JS pour exécuter la fonction JS stopFn lorsque le
CancellationTokenSource.Token est annulé.
Lorsque le bouton Cancel Task est sélectionné, le CancellationTokenSource.Token
est annulé avec un appel à Cancel.
Le CancellationTokenSource est supprimé dans la méthode Dispose .

CallJs12.razor :

razor

@page "/call-js-12"
@inject IJSRuntime JS

<h1>Cancel long-running JS interop</h1>

<p>
<button @onclick="StartTask">Start Task</button>
<button @onclick="CancelTask">Cancel Task</button>
</p>

@code {
private CancellationTokenSource? cts;

private async Task StartTask()


{
cts = new CancellationTokenSource();
cts.Token.Register(() => JS.InvokeVoidAsync("Helpers.stopFn"));

await JS.InvokeVoidAsync("Helpers.longRunningFn");
}

private void CancelTask()


{
cts?.Cancel();
}

public void Dispose()


{
cts?.Cancel();
cts?.Dispose();
}
}

La console d’outils de développement d’un navigateur indique l’exécution de la


fonction JS de longue durée après la sélection du bouton Start Task , et lorsque la
fonction est abandonnée une fois le bouton Cancel Task sélectionné :

Console

longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!

JavaScript [JSImport] / [JSExport] interop


Cette section s’applique aux composants côté client.

En guise d’alternative à l’interaction avec JavaScript (JS) dans les composants côté client
à l’aide du mécanisme d’interopérabilité JS de Blazor basé sur l’interface IJSRuntime, une
API d’interopérabilité JS [JSImport] / [JSExport] est disponible pour les applications
ciblant .NET 7 ou version ultérieure.

Pour plus d’informations, consultez interopérabilité JSImport/JSExport JavaScript avec


Blazor ASP.NET Core.

Interopérabilité JavaScript démarshalée


Cette section s’applique aux composants côté client.

L’interopérabilité démarshalée à l’aide de l’interface IJSUnmarshalledRuntime est


obsolète et doit être remplacée par l’interopérabilité [JSImport] / [JSExport] JavaScript.

Pour plus d’informations, consultez interopérabilité JSImport/JSExport JavaScript avec


Blazor ASP.NET Core.

Suppression des références d’objets


d’interopérabilité JavaScript
Les exemples des articles d’interopérabilité JavaScript (JS) illustrent des modèles de
suppression d’objets classiques :

Lorsque vous appelez JS à partir de .NET, comme décrit dans cet article, supprimez
les IJSObjectReference/IJSInProcessObjectReference/ JSObjectReference créés à
partir de .NET ou de JS pour éviter la fuite de mémoire JS.

Lors de l’appel de .NET à partir de JS, comme décrit dans Appeler des méthodes
.NET à partir de fonctions JavaScript dans ASP.NET Core Blazor, supprimez un
DotNetObjectReference créé à partir de .NET ou de JS pour éviter la fuite de
mémoire .NET.

Les références d’objet d’interopérabilité JS sont implémentées en tant que carte avec
pour clé un identificateur sur le côté de l’appel d’interopérabilité JS qui crée la référence.
Lorsque l’élimination de l’objet est lancée du côté .NET ou JS, Blazor supprime l’entrée
de la carte, et l’objet peut être récupéré en mémoire tant qu’aucune autre référence
forte à l’objet n’est présente.

Au minimum, éliminez toujours les objets créés côté .NET pour éviter les fuites de
mémoire managée .NET.

Tâches de nettoyage de modèle DOM lors de la


suppression des composants
Pour plus d’informations, consultez Interopérabilité JavaScript et ASP.NET Core Blazor
(interopérabilité JS).

Appels d’interopérabilité JavaScript sans circuit


Pour plus d’informations, consultez Interopérabilité JavaScript et ASP.NET Core Blazor
(interopérabilité JS).

Ressources supplémentaires
Appeler des méthodes .NET à partir de fonctions JavaScript dans ASP.NET Core
Blazor
Exemple InteropComponent.razor (branche main du dépôt GitHub
dotnet/AspNetCore) : la branche main représente le développement actuel de
l’unité de produit pour la prochaine version d’ASP.NET Core. Pour sélectionner la
branche d’une autre version (par exemple, release/5.0 ), utilisez la liste déroulante
Changer de branche ou d’étiquette pour sélectionner la branche.
Blazor échantillons de référentiel GitHub (dotnet/blazor-samples)
Gérer les erreurs dans les applications ASP.NET Core Blazor (section interopérabilité
JavaScript)
Atténuation des menaces : fonctions JavaScript appelées à partir de .NET

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Appeler des méthodes .NET à partir de
fonctions JavaScript dans ASP.NET Core
Blazor
Article • 26/12/2023

Cet article explique comment appeler des méthodes .NET à partir de JavaScript (JS).

Pour plus d’informations sur l’appel de fonctions JS à partir de .NET, consultez Appeler
des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Appeler une méthode .NET statique


Pour appeler une méthode .NET statique à partir de JavaScript (JS), utilisez les fonctions
JS :

DotNet.invokeMethodAsync (recommandé) : asynchrone pour les composants côté

serveur et côté client.


DotNet.invokeMethod : synchrone uniquement pour les composants côté client

uniquement.

Transmettez le nom de l’assembly contenant la méthode, l’identificateur de la méthode


.NET statique et les arguments éventuels.

Dans l’exemple suivant :

L’espace réservé {ASSEMBLY NAME} est le nom de l’assembly de l’application.


L’espace réservé {.NET METHOD ID} est l’identificateur de méthode .NET.
L’espace réservé {ARGUMENTS} reçoit des arguments facultatifs séparés par des
virgules à passer à la méthode, chacun d’entre eux doit être sérialisable en JSON.

JavaScript

DotNet.invokeMethodAsync('{ASSEMBLY NAME}', '{.NET METHOD ID}',


{ARGUMENTS});

DotNet.invokeMethodAsync retourne un JS Promise représentant le résultat de


l’opération. DotNet.invokeMethod (composants côté client) retourne le résultat de
l’opération.

) Important
Pour les composants côté serveur, nous recommandons la fonction asynchrone
( invokeMethodAsync ) plutôt que la version synchrone ( invokeMethod ).

La méthode .NET doit être publique, statique et avoir l’attribut [JSInvokable].

Dans l’exemple suivant :

L’espace réservé {<T>} indique le type de retour, qui est uniquement requis pour
les méthodes qui retournent une valeur.
L’espace réservé {.NET METHOD ID} est l’identificateur de méthode.

razor

@code {
[JSInvokable]
public static Task{<T>} {.NET METHOD ID}()
{
...
}
}

7 Notes

L’appel de méthodes génériques ouvertes n’est pas pris en charge avec les
méthodes .NET statiques, mais l’est avec les méthodes d’instance. Pour plus
d’informations, consultez la section Appeler des méthodes de classe génériques
.NET.

Dans le composant suivant, la méthode C# ReturnArrayAsync retourne un tableau int .


L’attribut [JSInvokable] est appliqué à la méthode, ce qui rend la méthode disponible
pour JS.

CallDotnet1.razor :

razor

@page "/call-dotnet-1"

<PageTitle>Call .NET 1</PageTitle>

<h1>Call .NET Example 1</h1>

<p>
<button onclick="returnArrayAsync()">
Trigger .NET static method
</button>
</p>

<p>
See the result in the developer tools console.
</p>

@code {
[JSInvokable]
public static Task<int[]> ReturnArrayAsync()
{
return Task.FromResult(new int[] { 1, 2, 3 });
}
}

L’attribut HTML onclick de l’élément <button> est l’attribution de gestionnaire


d’événements onclick JavaScript pour le traitement des événements click , et non pas
l’attribut de directive @onclick de Blazor. La fonction returnArrayAsync JS est affectée en
tant que gestionnaire.

La fonction returnArrayAsync JS suivante appelle la méthode .NET ReturnArrayAsync du


composant précédent et enregistre le résultat dans la console des outils de
développement web du navigateur. BlazorSample est le nom de l’assembly de
l’application.

HTML

<script>
window.returnArrayAsync = () => {
DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
.then(data => {
console.log(data);
});
};
</script>

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

Lorsque le bouton Trigger .NET static method est sélectionné, la sortie de la console
des outils de développement du navigateur affiche les données du tableau. Le format de
la sortie diffère légèrement d’un navigateur à l’autre. La sortie suivante montre le format
utilisé par Microsoft Edge :

Console

Array(3) [ 1, 2, 3 ]

Transmettez des données à une méthode .NET lors de l’appel de la fonction


invokeMethodAsync en passant les données en tant qu’arguments.

Pour illustrer la transmission de données à .NET, faites en sorte que la fonction


précédente returnArrayAsync JS reçoive une position de départ lorsque la fonction est
appelée, et passez la valeur en tant qu’argument à la fonction invokeMethodAsync :

HTML

<script>
window.returnArrayAsync = (startPosition) => {
DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync',
startPosition)
.then(data => {
console.log(data);
});
};
</script>

Dans le composant , modifiez l’appel de fonction pour inclure une position de départ.
L’exemple suivant utilise une valeur de 5 :

razor

<button onclick="returnArrayAsync(5)">
...
</button>

La méthode ReturnArrayAsync disponible du composant reçoit la position de départ et


construit le tableau à partir de celle-ci. Le tableau est retourné pour la journalisation
dans la console :

C#

[JSInvokable]
public static Task<int[]> ReturnArrayAsync(int startPosition)
{
return Task.FromResult(Enumerable.Range(startPosition, 3).ToArray());
}

Une fois l’application recompilée et le navigateur actualisé, la sortie suivante s’affiche


dans la console du navigateur lorsque le bouton est sélectionné :

Console

Array(3) [ 5, 6, 7 ]

Par défaut, l’identificateur de méthode .NET pour l’appel JS est le nom de la méthode
.NET, mais vous pouvez spécifier un identificateur différent à l’aide du constructeur
d’attribut [JSInvokable]. Dans l’exemple suivant, DifferentMethodName est l’identificateur
de méthode attribué pour la méthode ReturnArrayAsync :

C#

[JSInvokable("DifferentMethodName")]

Dans l’appel à DotNet.invokeMethodAsync (composants côté serveur ou côté client) ou


DotNet.invokeMethod (composants côté client uniquement), appelez
DifferentMethodName pour exécuter la méthode .NET ReturnArrayAsync :

DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');

DotNet.invokeMethod('BlazorSample', 'DifferentMethodName'); (composants côté


client uniquement)

7 Notes

L’exemple de méthode ReturnArrayAsync de cette section retourne le résultat d’un


Task sans utiliser de mots clés C# async et await explicites. Le codage de méthodes
avec async et await est typique des méthodes qui utilisent le mot clé await pour
retourner la valeur des opérations asynchrones.

Méthode composée ReturnArrayAsync avec des mots clés async et await :

C#

[JSInvokable]
public static async Task<int[]> ReturnArrayAsync()
{
return await Task.FromResult(new int[] { 1, 2, 3 });
}
Pour plus d’informations, consultez Programmation asynchrone avec async et
await dans le guide C#.

Créer des références d’objet et de données


JavaScript à passer à .NET
Appelez DotNet.createJSObjectReference(jsObject) pour construire une référence
d’objet JS afin qu’elle puisse être transmise à .NET, où jsObject est le JS Object utilisé
pour créer la référence d’objet JS. L’exemple suivant transmet une référence à l’objet
window non sérialisable à .NET, qui le reçoit dans la méthode C# ReceiveWindowObject en

tant que IJSObjectReference :

JavaScript

DotNet.invokeMethodAsync('{ASSEMBLY NAME}', 'ReceiveWindowObject',


DotNet.createJSObjectReference(window));

C#

[JSInvokable]
public static void ReceiveWindowObject(IJSObjectReference objRef)
{
...
}

Dans l’exemple précédent, l’espace réservé {ASSEMBLY NAME} est l’espace de noms de
l’application.

7 Notes

L’exemple précédent ne nécessite pas de suppression du JSObjectReference , car


aucune référence à l’objet window n’est conservée dans JS.

La conservation d’une référence à un JSObjectReference nécessite la suppression de


celle-ci pour éviter les fuites de mémoire JS sur le client. L’exemple suivant refactorise le
code précédent pour capturer une référence au JSObjectReference , suivi d’un appel à
DotNet.disposeJSObjectReference() pour supprimer la référence :

JavaScript
var jsObjectReference = DotNet.createJSObjectReference(window);

DotNet.invokeMethodAsync('{ASSEMBLY NAME}', 'ReceiveWindowObject',


jsObjectReference);

DotNet.disposeJSObjectReference(jsObjectReference);

Dans l’exemple précédent, l’espace réservé {ASSEMBLY NAME} est l’espace de noms de
l’application.

Appelez DotNet.createJSStreamReference(streamReference) pour construire une


référence de flux JS afin qu’elle puisse être transmise à .NET, où streamReference est un
ArrayBuffer , Blob ou tout tableau typé , comme Uint8Array ou Float32Array ,
utilisé pour créer la référence de flux JS.

Appeler une méthode .NET d’instance


Pour appeler une méthode .NET d’instance à partir de JavaScript (JS) :

Transmettez l’instance .NET par référence à JS en encapsulant l’instance dans un


DotNetObjectReference et en appelant Create dessus.

Appelez une méthode d’instance .NET à partir de JS en utilisant invokeMethodAsync


(recommandé) ou invokeMethod (composants côté client uniquement) à partir de
l’instance DotNetObjectReference passée. Transmettez l’identificateur de la
méthode .NET d’instance et les arguments éventuels. L’instance .NET peut
également être passée en tant qu’argument lors de l’appel d’autres méthodes .NET
à partir de JS.

Dans l’exemple suivant :


dotNetHelper est un DotNetObjectReference.

L’espace réservé {.NET METHOD ID} est l’identificateur de méthode .NET.


L’espace réservé {ARGUMENTS} reçoit des arguments facultatifs séparés par des
virgules à passer à la méthode, chacun d’entre eux doit être sérialisable en
JSON.

JavaScript

dotNetHelper.invokeMethodAsync('{.NET METHOD ID}', {ARGUMENTS});

7 Notes
invokeMethodAsync et invokeMethod n’acceptent pas de paramètre de nom

d’assembly lors de l’appel d’une méthode d’instance.

invokeMethodAsync retourne un JS Promise représentant le résultat de


l’opération. invokeMethod (composants côté client uniquement) retourne le résultat
de l’opération.

) Important

Pour les composants côté serveur, nous recommandons la fonction


asynchrone ( invokeMethodAsync ) plutôt que la version synchrone
( invokeMethod ).

Supprimez le DotNetObjectReference.

Les sections suivantes de cet article illustrent différentes approches pour appeler une
méthode .NET d’instance :

Passer une DotNetObjectReference à une fonction JavaScript individuelle


Passer une DotNetObjectReference à une classe avec plusieurs fonctions JavaScript
Appeler des méthodes de classe génériques .NET
Exemples d’instance de classe
Classe d’assistance de méthode .NET d’instance de composant

Évitez de découper les méthodes .NET


disponibles pour JavaScript
Cette section s’applique aux applications côté client avec la compilation à l’avance (AOT) et
la liaison dynamique à l’exécution activées.

Plusieurs des exemples des sections suivantes sont basés sur une approche d’instance
de classe, où la méthode .NET disponible pour JavaScript marquée avec l’attribut
[JSInvokable] est membre d’une classe qui n’est pas un composant Razor. Lorsque ces
méthodes .NET se trouvent dans un composant Razor, elles sont protégées contre la
nouvelle liaison/le découpage du runtime. Pour protéger les méthodes .NET contre le
découpage en dehors des composants Razor, implémentez les méthodes avec l’attribut
DynamicDependency sur le constructeur de la classe, comme le montre l’exemple
suivant :

C#
using System.Diagnostics.CodeAnalysis;
using Microsoft.JSInterop;

public class ExampleClass {

[DynamicDependency(nameof(ExampleJSInvokableMethod))]
public ExampleClass()
{
}

[JSInvokable]
public string ExampleJSInvokableMethod()
{
...
}
}

Pour plus d’informations, consultez Préparer des bibliothèques .NET pour le découpage :
DynamicDependency.

Passer une DotNetObjectReference à une


fonction JavaScript individuelle
L’exemple de cette section montre comment passer une DotNetObjectReference à une
fonction JavaScript individuelle (JS).

La fonction sayHello1 JS suivante reçoit DotNetObjectReference et appelle


invokeMethodAsync pour appeler la méthode .NET GetHelloMessage d’un composant :

HTML

<script>
window.sayHello1 = (dotNetHelper) => {
return dotNetHelper.invokeMethodAsync('GetHelloMessage');
};
</script>

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).
Dans l’exemple précédent, le nom de variable dotNetHelper est arbitraire et peut être
remplacé par n’importe quel nom de votre choix.

Pour le composant suivant :

Le composant a une méthode .NET disponible pour JS nommée GetHelloMessage .


Lorsque le bouton Trigger .NET instance method est sélectionné, la fonction
JS sayHello1 est appelée avec la DotNetObjectReference.
sayHello1 :

Appelle GetHelloMessage et reçoit le résultat du message.


Retourne le résultat du message à la méthode TriggerDotNetInstanceMethod
appelante.
Le message retourné à partir de sayHello1 dans result s’affiche à l’utilisateur.
Pour éviter une fuite de mémoire et autoriser le nettoyage de la mémoire, la
référence d’objet .NET créée par DotNetObjectReference est supprimée dans la
méthode Dispose .

CallDotnet2.razor :

razor

@page "/call-dotnet-2"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 2</PageTitle>

<h1>Call .NET Example 2</h1>

<p>
<label>
Name: <input @bind="name" />
</label>
</p>

<p>
<button @onclick="TriggerDotNetInstanceMethod">
Trigger .NET instance method
</button>
</p>

<p>
@result
</p>

@code {
private string? name;
private string? result;
private DotNetObjectReference<CallDotnet2>? objRef;
protected override void OnInitialized()
{
objRef = DotNetObjectReference.Create(this);
}

public async Task TriggerDotNetInstanceMethod()


{
result = await JS.InvokeAsync<string>("sayHello1", objRef);
}

[JSInvokable]
public string GetHelloMessage() => $"Hello, {name}!";

public void Dispose() => objRef?.Dispose();


}

Dans l’exemple précédent, le nom de variable dotNetHelper est arbitraire et peut être
remplacé par n’importe quel nom de votre choix.

Utilisez les instructions suivantes pour passer des arguments à une méthode d’instance :

Ajoutez des paramètres à l’appel de méthode .NET. Dans l’exemple suivant, un nom est
passé à la méthode. Ajoutez des paramètres supplémentaires à la liste en fonction de
vos besoins.

HTML

<script>
window.sayHello2 = (dotNetHelper, name) => {
return dotNetHelper.invokeMethodAsync('GetHelloMessage', name);
};
</script>

Dans l’exemple précédent, le nom de variable dotNetHelper est arbitraire et peut être
remplacé par n’importe quel nom de votre choix.

Fournissez la liste des paramètres à la méthode .NET.

CallDotnet3.razor :

razor

@page "/call-dotnet-3"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET 3</PageTitle>


<h1>Call .NET Example 3</h1>

<p>
<label>
Name: <input @bind="name" />
</label>
</p>

<p>
<button @onclick="TriggerDotNetInstanceMethod">
Trigger .NET instance method
</button>
</p>

<p>
@result
</p>

@code {
private string? name;
private string? result;
private DotNetObjectReference<CallDotnet3>? objRef;

protected override void OnInitialized()


{
objRef = DotNetObjectReference.Create(this);
}

public async Task TriggerDotNetInstanceMethod()


{
result = await JS.InvokeAsync<string>("sayHello2", objRef, name);
}

[JSInvokable]
public string GetHelloMessage(string passedName) => $"Hello,
{passedName}!";

public void Dispose() => objRef?.Dispose();


}

Dans l’exemple précédent, le nom de variable dotNetHelper est arbitraire et peut être
remplacé par n’importe quel nom de votre choix.

Passer une DotNetObjectReference à une classe


avec plusieurs fonctions JavaScript
L’exemple de cette section montre comment passer une DotNetObjectReference à une
classe JavaScript (JS) avec plusieurs fonctions.
Créez et passez une DotNetObjectReference de la méthode de cycle de vie
OnAfterRenderAsync à une classe JS que plusieurs fonctions utiliseront. Assurez-vous
que le code .NET supprime DotNetObjectReference, comme le montre l’exemple suivant.

Dans le composant suivant, les boutons Trigger JS function appellent des fonctions JS
en définissant la propriété JS onclick , et pas l’attribut de directive @onclick de Blazor.

CallDotNetExampleOneHelper.razor :

razor

@page "/call-dotnet-example-one-helper"
@implements IDisposable
@inject IJSRuntime JS

<PageTitle>Call .NET Example</PageTitle>

<h1>Pass <code>DotNetObjectReference</code> to a JavaScript class</h1>

<p>
<label>
Message: <input @bind="name" />
</label>
</p>

<p>
<button onclick="GreetingHelpers.sayHello()">
Trigger JS function <code>sayHello</code>
</button>
</p>

<p>
<button onclick="GreetingHelpers.welcomeVisitor()">
Trigger JS function <code>welcomeVisitor</code>
</button>
</p>

@code {
private string? name;
private DotNetObjectReference<CallDotNetExampleOneHelper>? dotNetHelper;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
dotNetHelper = DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync("GreetingHelpers.setDotNetHelper",
dotNetHelper);
}
}

[JSInvokable]
public string GetHelloMessage() => $"Hello, {name}!";

[JSInvokable]
public string GetWelcomeMessage() => $"Welcome, {name}!";

public void Dispose()


{
dotNetHelper?.Dispose();
}
}

Dans l'exemple précédent :

JS est un instance IJSRuntime injectée. IJSRuntime est inscrit par le framework

Blazor.
Le nom de variable dotNetHelper est arbitraire et peut être remplacé par n’importe
quel nom de votre choix.
Le composant doit explicitement éliminer DotNetObjectReference pour autoriser le
nettoyage de la mémoire et empêcher une fuite de mémoire.

HTML

<script>
class GreetingHelpers {
static dotNetHelper;

static setDotNetHelper(value) {
GreetingHelpers.dotNetHelper = value;
}

static async sayHello() {


const msg =
await
GreetingHelpers.dotNetHelper.invokeMethodAsync('GetHelloMessage');
alert(`Message from .NET: "${msg}"`);
}

static async welcomeVisitor() {


const msg =
await
GreetingHelpers.dotNetHelper.invokeMethodAsync('GetWelcomeMessage');
alert(`Message from .NET: "${msg}"`);
}
}

window.GreetingHelpers = GreetingHelpers;
</script>

7 Notes
Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations
pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

Dans l’exemple précédent :

La classe GreetingHelpers est ajoutée à l’objet window pour définir globalement la


classe, ce qui permet à Blazor de localiser la classe pour l’interopérabilité JS.
Le nom de variable dotNetHelper est arbitraire et peut être remplacé par n’importe
quel nom de votre choix.

Appeler des méthodes de classe génériques


.NET
Les fonctions JavaScript (JS) peuvent appeler des méthodes de classe générique .NET,
où une fonction JS appelle une méthode .NET d’une classe générique.

Dans la classe de type générique suivante ( GenericType<TValue> ) :

La classe a un paramètre de type unique ( TValue ) avec une propriété générique


Value unique.

La classe a deux méthodes non génériques marquées avec l’attribut [JSInvokable],


chacune avec un paramètre de type générique nommé newValue :
Update met à jour de manière synchrone la valeur de Value à partir de
newValue .

UpdateAsync met à jour de manière asynchrone la valeur de Value à partir de


newValue après la création d’une tâche pouvant être attendue, avec Task.Yield

qui retourne de façon asynchrone au contexte actuel en cas d’attente.


Chacune des méthodes de la classe écrit le type de TValue et la valeur de Value
dans la console. L’écriture dans la console est effectuée uniquement à des fins de
démonstration. Les applications de production évitent généralement d’écrire dans
la console et préfèrent la journalisation de l’application. Pour plus d’informations,
consultez Journalisation dans ASP.NET Core Blazor et Journalisation dans .NET
Core et ASP.NET Core.

7 Notes

Les types et méthodes génériques ouverts ne spécifient pas de types pour les
espaces réservés de type. À l’inverse, les génériques fermés fournissent des types
pour tous les espaces réservés de type. Les exemples de cette section illustrent les
génériques fermés, mais l’appel de méthodes d’instance d’interopérabilité JS avec
des génériques ouverts est pris en charge. L’utilisation de génériques ouverts n’est
pas prise en charge pour les appels de méthode .NET statiques, qui ont été décrits
plus haut dans cet article.

Pour plus d’informations, consultez les articles suivants :

Classes et méthodes génériques (documentation C#)


Classes génériques (guide de programmation C#)
Génériques dans .NET (documentation .NET)

GenericType.cs :

C#

using Microsoft.JSInterop;

public class GenericType<TValue>


{
public TValue? Value { get; set; }

[JSInvokable]
public void Update(TValue newValue)
{
Value = newValue;

Console.WriteLine($"Update: GenericType<{typeof(TValue)}>:
{Value}");
}

[JSInvokable]
public async void UpdateAsync(TValue newValue)
{
await Task.Yield();
Value = newValue;

Console.WriteLine($"UpdateAsync: GenericType<{typeof(TValue)}>:
{Value}");
}
}

Dans la fonction invokeMethodsAsync suivante :

Les méthodes Update et UpdateAsync de la classe de type générique sont appelées


avec des arguments représentant des chaînes et des nombres.
Les composants côté client prennent en charge l’appel de méthodes .NET de
manière synchrone avec invokeMethod . syncInterop reçoit une valeur booléenne
indiquant si l’interopérabilité JS se produit dans le client. Quand syncInterop est
true , invokeMethod est appelé de façon sécurisée. Si la valeur de syncInterop est
false , seule la fonction asynchrone invokeMethodAsync est appelée, car

l’interopérabilité JS s’exécute dans un composant côté serveur.


À des fins de démonstration, l’appel de fonction DotNetObjectReference
( invokeMethod ou invokeMethodAsync ), la méthode .NET appelée ( Update ou
UpdateAsync ) et l’argument sont écrits dans la console. Les arguments utilisent un

nombre aléatoire pour permettre la correspondance de l’appel de fonction JS à


l’appel de méthode .NET (également écrit dans la console côté .NET). Le code de
production n’écrit généralement pas dans la console, ni sur le client ni sur le
serveur. Les applications de production s’appuient généralement sur la
journalisation de l’application. Pour plus d’informations, consultez Journalisation
dans ASP.NET Core Blazor et Journalisation dans .NET Core et ASP.NET Core.

HTML

<script>
const randomInt = () => Math.floor(Math.random() * 99999);

window.invokeMethodsAsync = async (syncInterop, dotNetHelper1,


dotNetHelper2) => {
var n = randomInt();
console.log(`JS: invokeMethodAsync:Update('string ${n}')`);
await dotNetHelper1.invokeMethodAsync('Update', `string ${n}`);

n = randomInt();
console.log(`JS: invokeMethodAsync:UpdateAsync('string ${n}')`);
await dotNetHelper1.invokeMethodAsync('UpdateAsync', `string ${n}`);

if (syncInterop) {
n = randomInt();
console.log(`JS: invokeMethod:Update('string ${n}')`);
dotNetHelper1.invokeMethod('Update', `string ${n}`);
}

n = randomInt();
console.log(`JS: invokeMethodAsync:Update(${n})`);
await dotNetHelper2.invokeMethodAsync('Update', n);

n = randomInt();
console.log(`JS: invokeMethodAsync:UpdateAsync(${n})`);
await dotNetHelper2.invokeMethodAsync('UpdateAsync', n);

if (syncInterop) {
n = randomInt();
console.log(`JS: invokeMethod:Update(${n})`);
dotNetHelper2.invokeMethod('Update', n);
}
};
</script>
7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

Dans le composant GenericsExample suivant :

La fonction JS invokeMethodsAsync est appelée lorsque le bouton Invoke Interop


est sélectionné.
Une paire de types DotNetObjectReference est créée et transmise à la fonction JS
pour les instances de GenericType en tant que string et int .

GenericsExample.razor :

razor

@page "/generics-example"
@using System.Runtime.InteropServices
@implements IDisposable
@inject IJSRuntime JS

<p>
<button @onclick="InvokeInterop">Invoke Interop</button>
</p>

<ul>
<li>genericType1: @genericType1?.Value</li>
<li>genericType2: @genericType2?.Value</li>
</ul>

@code {
private GenericType<string> genericType1 = new() { Value = "string 0" };
private GenericType<int> genericType2 = new() { Value = 0 };
private DotNetObjectReference<GenericType<string>>? objRef1;
private DotNetObjectReference<GenericType<int>>? objRef2;

protected override void OnInitialized()


{
objRef1 = DotNetObjectReference.Create(genericType1);
objRef2 = DotNetObjectReference.Create(genericType2);
}

public async Task InvokeInterop()


{
var syncInterop =
RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));

await JS.InvokeVoidAsync(
"invokeMethodsAsync", syncInterop, objRef1, objRef2);
}

public void Dispose()


{
objRef1?.Dispose();
objRef2?.Dispose();
}
}

Dans l’exemple précédent, JS est une instance IJSRuntime injectée. IJSRuntime est
inscrit par le framework Blazor.

L’exemple suivant illustre la sortie classique de l’exemple précédent lorsque le bouton


Invoke Interop est sélectionné dans un composant côté client :

JS: invokeMethodAsync:Update(’string 37802’)


.NET: Update: GenericType<System.String>: string 37802
JS: invokeMethodAsync:UpdateAsync(’string 53051’)
JS: invokeMethod:Update(’string 26784’)
.NET: Update: GenericType<System.String>: string 26784
JS: invokeMethodAsync:Update(14107)
.NET: Update: GenericType<System.Int32>: 14107
JS: invokeMethodAsync:UpdateAsync(48995)
JS: invokeMethod:Update(12872)
.NET: Update: GenericType<System.Int32>: 12872
.NET: UpdateAsync: GenericType<System.String>: string 53051
.NET: UpdateAsync: GenericType<System.Int32>: 48995

Si l’exemple précédent est implémenté dans un composant côté serveur, les appels
synchrones avec invokeMethod sont évités. Pour les composants côté serveur, nous
recommandons la fonction asynchrone ( invokeMethodAsync ) plutôt que la version
synchrone ( invokeMethod ).

Sortie classique d’un composant côté serveur :

JS: invokeMethodAsync:Update(’string 34809’)


.NET: Update: GenericType<System.String>: string 34809
JS: invokeMethodAsync:UpdateAsync(’string 93059’)
JS: invokeMethodAsync:Update(41997)
.NET: Update: GenericType<System.Int32>: 41997
JS: invokeMethodAsync:UpdateAsync(24652)
.NET: UpdateAsync: GenericType<System.String>: string 93059
.NET: UpdateAsync: GenericType<System.Int32>: 24652

Les exemples de sortie précédents montrent que les méthodes asynchrones s’exécutent
et se terminent dans un ordre arbitraire en fonction de plusieurs facteurs, notamment la
planification des threads et la vitesse d’exécution de la méthode. Il n’est pas possible de
prédire de manière fiable l’ordre d’achèvement des appels de méthode asynchrones.

Exemples d’instance de classe


La fonction sayHello1 JS suivante :

Appelle la méthode GetHelloMessage .NET sur la DotNetObjectReference passée.


Retourne le message de GetHelloMessage à l’appelant sayHello1 .

HTML

<script>
window.sayHello1 = (dotNetHelper) => {
return dotNetHelper.invokeMethodAsync('GetHelloMessage');
};
</script>

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

Dans l’exemple précédent, le nom de variable dotNetHelper est arbitraire et peut être
remplacé par n’importe quel nom de votre choix.

La classe HelloHelper suivante a une méthode .NET disponible pour JS nommée


GetHelloMessage . Quand HelloHelper est créé, le nom de la propriété Name est utilisé

pour renvoyer un message à partir de GetHelloMessage .

HelloHelper.cs :

C#

using Microsoft.JSInterop;
namespace BlazorSample;

public class HelloHelper(string? name)


{
public string? Name { get; set; } = name ?? "No Name";

[JSInvokable]
public string GetHelloMessage() => $"Hello, {Name}!";
}

La méthode CallHelloHelperGetHelloMessage de la classe JsInteropClasses3 suivante


appelle la fonction JS sayHello1 avec une nouvelle instance de HelloHelper .

JsInteropClasses3.cs :

C#

using Microsoft.JSInterop;

namespace BlazorSample;

public class JsInteropClasses3(IJSRuntime js)


{
private readonly IJSRuntime js = js;

public async ValueTask<string> CallHelloHelperGetHelloMessage(string?


name)
{
using var objRef = DotNetObjectReference.Create(new
HelloHelper(name));
return await js.InvokeAsync<string>("sayHello1", objRef);
}
}

Pour éviter une fuite de mémoire et autoriser le nettoyage de la mémoire, la référence


d’objet .NET créée par DotNetObjectReference est supprimée lorsque la référence
d’objet sort de l’étendue avec la syntaxe using var.

Lorsque le bouton Trigger .NET instance method est sélectionné dans le composant
suivant, JsInteropClasses3.CallHelloHelperGetHelloMessage est appelé avec la valeur de
name .

CallDotnet4.razor :

razor

@page "/call-dotnet-4"
@inject IJSRuntime JS
<PageTitle>Call .NET 4</PageTitle>

<h1>Call .NET Example 4</h1>

<p>
<label>
Name: <input @bind="name" />
</label>
</p>

<p>
<button @onclick="TriggerDotNetInstanceMethod">
Trigger .NET instance method
</button>
</p>

<p>
@result
</p>

@code {
private string? name;
private string? result;
private JsInteropClasses3? jsInteropClasses;

protected override void OnInitialized()


{
jsInteropClasses = new JsInteropClasses3(JS);
}

private async Task TriggerDotNetInstanceMethod()


{
if (jsInteropClasses is not null)
{
result = await
jsInteropClasses.CallHelloHelperGetHelloMessage(name);
}
}
}

L’image suivante montre le composant rendu avec le nom Amy Pond dans le champ
Name . Une fois le bouton sélectionné, Hello, Amy Pond! s’affiche dans l’interface

utilisateur :
Le modèle précédent indiqué dans la classe JsInteropClasses3 peut également être
implémenté entièrement dans un composant.

CallDotnet5.razor :

razor

@page "/call-dotnet-5"
@inject IJSRuntime JS

<PageTitle>Call .NET 5</PageTitle>

<h1>Call .NET Example 5</h1>

<p>
<label>
Name: <input @bind="name" />
</label>
</p>

<p>
<button @onclick="TriggerDotNetInstanceMethod">
Trigger .NET instance method
</button>
</p>

<p>
@result
</p>

@code {
private string? name;
private string? result;

public async Task TriggerDotNetInstanceMethod()


{
using var objRef = DotNetObjectReference.Create(new
HelloHelper(name));
result = await JS.InvokeAsync<string>("sayHello1", objRef);
}
}

Pour éviter une fuite de mémoire et autoriser le nettoyage de la mémoire, la référence


d’objet .NET créée par DotNetObjectReference est supprimée lorsque la référence
d’objet sort de l’étendue avec la syntaxe using var.

La sortie affichée par le composant est Hello, Amy Pond! lorsque le nom Amy Pond est
fourni dans le champ name .

Dans le composant précédent, la référence d’objet .NET est supprimée. Si une classe ou
un composant ne supprime pas le DotNetObjectReference, supprimez-le du client en
appelant dispose sur le DotNetObjectReference passé :

JavaScript

window.{JS FUNCTION NAME} = (dotNetHelper) => {


dotNetHelper.invokeMethodAsync('{.NET METHOD ID}');
dotNetHelper.dispose();
}

Dans l’exemple précédent :

L’espace réservé {JS FUNCTION NAME} est le nom de la fonction JS.


Le nom de variable dotNetHelper est arbitraire et peut être remplacé par n’importe
quel nom de votre choix.
L’espace réservé {.NET METHOD ID} est l’identificateur de méthode .NET.

Classe d’assistance de méthode .NET d’instance


de composant
Une classe d’assistance peut appeler une méthode d’instance .NET en tant que Action.
Les classes d’assistance sont utiles dans les scénarios suivants :

Lorsque plusieurs composants du même type sont rendus sur la même page.
Dans les applications côté serveur avec plusieurs utilisateurs utilisant
simultanément le même composant.

Dans l’exemple suivant :


Le composant contient plusieurs composants ListItem1 , qui est un composant
partagé dans le dossier Shared de l’application.
Chaque composant ListItem1 est composé d’un message et d’un bouton.
Lorsqu’un bouton de composant ListItem1 est sélectionné, la méthode
UpdateMessage de ce ListItem1 modifie le texte de l’élément de liste et masque le

bouton.

La classe MessageUpdateInvokeHelper suivante gère une méthode .NET disponible pour


JS, UpdateMessageCaller , pour appeler le Action spécifié lorsque la classe est instanciée.

MessageUpdateInvokeHelper.cs :

C#

using Microsoft.JSInterop;

namespace BlazorSample;

public class MessageUpdateInvokeHelper(Action action)


{
private readonly Action action = action;

[JSInvokable]
public void UpdateMessageCaller()
{
action.Invoke();
}
}

La fonction updateMessageCaller JS suivante appelle la méthode .NET


UpdateMessageCaller .

HTML

<script>
window.updateMessageCaller = (dotNetHelper) => {
dotNetHelper.invokeMethodAsync('UpdateMessageCaller');
dotNetHelper.dispose();
}
</script>

7 Notes
Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations
pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

Dans l’exemple précédent, le nom de variable dotNetHelper est arbitraire et peut être
remplacé par n’importe quel nom de votre choix.

Le composant ListItem1 suivant est un composant partagé qui peut être utilisé
n’importe quel nombre de fois dans un composant parent, et qui crée des éléments de
liste ( <li>...</li> ) pour une liste HTML ( <ul>...</ul> ou <ol>...</ol> ). Chaque
instance de composant ListItem1 établit une instance de MessageUpdateInvokeHelper
avec une Action définie sur sa méthode UpdateMessage .

Lorsqu’un bouton InteropCall d’un composant ListItem1 est sélectionné,


updateMessageCaller est appelé avec un DotNetObjectReference créé pour l’instance de

MessageUpdateInvokeHelper . Cela permet au framework d’appeler UpdateMessageCaller

sur le MessageUpdateInvokeHelper de cette instance ListItem1 . Le


DotNetObjectReference passé est supprimé dans JS ( dotNetHelper.dispose() ).

ListItem1.razor :

razor

@inject IJSRuntime JS

<li>
@message
<button @onclick="InteropCall"
style="display:@display">InteropCall</button>
</li>

@code {
private string message = "Select one of these list item buttons.";
private string display = "inline-block";
private MessageUpdateInvokeHelper? messageUpdateInvokeHelper;

protected override void OnInitialized()


{
messageUpdateInvokeHelper = new
MessageUpdateInvokeHelper(UpdateMessage);
}

protected async Task InteropCall()


{
if (messageUpdateInvokeHelper is not null)
{
await JS.InvokeVoidAsync("updateMessageCaller",
DotNetObjectReference.Create(messageUpdateInvokeHelper));
}
}

private void UpdateMessage()


{
message = "UpdateMessage Called!";
display = "none";
StateHasChanged();
}
}

StateHasChanged est appelé pour mettre à jour l’interface utilisateur lorsque message
est défini dans UpdateMessage . Si StateHasChanged n’est pas appelé, Blazor n’a aucun
moyen de savoir que l’interface utilisateur doit être mise à jour lorsque Action est
appelé.

Le composant parent suivant comprend quatre éléments de liste, chacun étant une
instance du composant ListItem1 .

CallDotnet6.razor :

razor

@page "/call-dotnet-6"

<PageTitle>Call .NET 6</PageTitle>

<h1>Call .NET Example 6</h1>

<ul>
<ListItem1 />
<ListItem1 />
<ListItem1 />
<ListItem1 />
</ul>

L’image suivante montre le composant parent rendu une fois le deuxième bouton
InteropCall sélectionné :

Le deuxième composant ListItem1 a affiché le message UpdateMessage Called! .


Le bouton InteropCall du deuxième composant ListItem1 n’est pas visible, car la
propriété CSS display du bouton est définie sur none .
Méthode .NET d’instance de composant
appelée à partir de DotNetObjectReference
affectée à une propriété d’élément
L’affectation d’un DotNetObjectReference à une propriété d’un élément HTML permet
d’appeler des méthodes .NET sur une instance de composant :

Une référence d’élément est capturée (ElementReference).


Dans la méthode OnAfterRender{Async} du composant, une fonction JavaScript
(JS) est appelée avec la référence d’élément et l’instance de composant en tant que
DotNetObjectReference. La fonction JS attache DotNetObjectReference à l’élément
d’une propriété.
Lorsqu’un événement d’élément est appelé dans JS (par exemple, onclick ), la
DotNetObjectReference attachée de l’élément est utilisée pour appeler une
méthode .NET.

Comme pour ce qui est décrit dans la section Classe d’assistance de méthode .NET
d’instance de composant, cette approche est utile dans les scénarios suivants :

Lorsque plusieurs composants du même type sont rendus sur la même page.
Dans les applications côté serveur avec plusieurs utilisateurs utilisant
simultanément le même composant.
La méthode .NET est appelée à partir d’un événement JS (par exemple, onclick ), et
non à partir d’un événement Blazor (par exemple, @onclick ).

Dans l’exemple suivant :


Le composant contient plusieurs composants ListItem2 , qui est un composant
partagé dans le dossier Shared de l’application.
Chaque composant ListItem2 est constitué d’un message d’élément de liste
<span> et d’un deuxième <span> avec une propriété CSS display définie sur

inline-block pour l’affichage.

Lorsqu’un élément de liste de composants ListItem2 est sélectionné, la méthode


UpdateMessage de ce ListItem2 modifie le texte de l’élément de liste dans le

premier <span> et masque la deuxième <span> en définissant sa propriété display


sur none .

La fonction assignDotNetHelper JS suivante affecte la DotNetObjectReference à un


élément d’une propriété nommée dotNetHelper :

HTML

<script>
window.assignDotNetHelper = (element, dotNetHelper) => {
element.dotNetHelper = dotNetHelper;
}
</script>

La fonction interopCall JS suivante utilise la DotNetObjectReference de l’élément passé


pour appeler une méthode .NET nommée UpdateMessage :

HTML

<script>
window.interopCall = async (element) => {
await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
}
</script>

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

Dans l’exemple précédent, le nom de variable dotNetHelper est arbitraire et peut être
remplacé par n’importe quel nom de votre choix.
Le composant ListItem2 suivant est un composant partagé qui peut être utilisé
n’importe quel nombre de fois dans un composant parent, et qui crée des éléments de
liste ( <li>...</li> ) pour une liste HTML ( <ul>...</ul> ou <ol>...</ol> ).

Chaque instance de composant ListItem2 appelle la fonction assignDotNetHelper JS


dans OnAfterRenderAsync avec une référence d’élément (premier élément <span> de
l’élément de liste) et l’instance de composant en tant que DotNetObjectReference.

Lorsqu’un <span> de message d’un composant ListItem2 est sélectionné, interopCall


est appelé en passant l’élément <span> en tant que paramètre ( this ), ce qui appelle la
méthode .NET UpdateMessage . Dans UpdateMessage , StateHasChanged est appelé pour
mettre à jour l’interface utilisateur lorsque message est défini et que la propriété
display du deuxième <span> est mise à jour. Si StateHasChanged n’est pas appelé,

Blazor n’a aucun moyen de savoir que l’interface utilisateur doit être mise à jour lorsque
la méthode est appelée.

La DotNetObjectReference est supprimée lorsque le composant est supprimé.

ListItem2.razor :

razor

@inject IJSRuntime JS

<li>
<span style="font-weight:bold;color:@color" @ref="elementRef"
onclick="interopCall(this)">
@message
</span>
<span style="display:@display">
Not Updated Yet!
</span>
</li>

@code {
private DotNetObjectReference<ListItem2>? objRef;
private ElementReference elementRef;
private string display = "inline-block";
private string message = "Select one of these list items.";
private string color = "initial";

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
objRef = DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync("assignDotNetHelper", elementRef,
objRef);
}
}

[JSInvokable]
public void UpdateMessage()
{
message = "UpdateMessage Called!";
display = "none";
color = "MediumSeaGreen";
StateHasChanged();
}

public void Dispose() => objRef?.Dispose();


}

Le composant parent suivant comprend quatre éléments de liste, chacun étant une
instance du composant ListItem2 .

CallDotnet7.razor :

razor

@page "/call-dotnet-7"

<PageTitle>Call .NET 7</PageTitle>

<h1>Call .NET Example 7</h1>

<ul>
<ListItem2 />
<ListItem2 />
<ListItem2 />
<ListItem2 />
</ul>

Interopérabilité JS synchrone dans les


composants côté client
Cette section s’applique uniquement aux composants côté client.

Les appels d’interopérabilité JS sont asynchrones par défaut, que le code appelé soit
synchrone ou asynchrone. Les appels sont asynchrones par défaut pour garantir que les
composants sont compatibles entre les modes de rendu côté serveur et côté client. Sur
le serveur, tous les appels interop JS doivent être asynchrones car ils sont envoyés via
une connexion réseau.

Si vous savez avec certitude que votre composant s'exécute uniquement sur
WebAssembly, vous pouvez choisir d'effectuer des appels interop synchrones JS. Cela
représente un peu moins de surcharge que d’effectuer des appels asynchrones, et peut
entraîner moins de cycles de rendu, car il n’y a pas d’état intermédiaire lors de l’attente
des résultats.

Pour effectuer un appel synchrone de JavaScript vers .NET dans un composant côté
client, utilisez DotNet.invokeMethod à la place de DotNet.invokeMethodAsync .

Les appels synchrones fonctionnent si :

Le composant est uniquement rendu pour exécution sur WebAssembly.


La fonction appelée retourne une valeur de manière synchrone. La fonction n’est
pas une méthode async et ne retourne pas de Task .NET ou Promise JavaScript.

Emplacement JavaScript
Chargez le code JavaScript (JS) à l’aide de l’une des approches décrites par l’JSarticle de
vue d’ensemble de l’interopérabilité :

Charger un script dans le balisage <head> (pas généralement recommandé)


Charger un script dans le balisage <body>
Charger un script à partir d’un fichier JavaScript externe (.js) colocalisé avec un
composant
Charger un script à partir d’un fichier JavaScript externe (.js)
Injecter un script avant ou après que Blazor démarre

L’utilisation de modules JS pour charger JS est décrite dans cet article dans la section
Isolation JavaScript dans les modules JavaScript.

2 Avertissement

Ne placez pas une balise <script> dans un fichier de composant ( .razor ) car la
balise <script> ne peut pas être mise à jour dynamiquement.

Isolation JavaScript dans les modules JavaScript


Blazor active l’isolation JavaScript (JS) dans les modules JavaScript standard
(spécification ECMAScript ). Le chargement de module JavaScript fonctionne de la
même manière dans Blazor que pour d’autres types d’applications web, et vous êtes
libre de personnaliser la façon dont les modules sont définis dans votre application.
Pour obtenir un guide sur l’utilisation des modules JavaScript, consultez MDN Web
Docs : modules JavaScript .
L’isolation JS offre les avantages suivants :

Le code JS importé ne pollue plus l’espace de noms global.


Les consommateurs d’une bibliothèque et de composants ne sont pas tenus
d’importer le code JS associé.

Pour plus d’informations, consultez Appeler des fonctions JavaScript à partir de


méthodes .NET dans ASP.NET Core Blazor.

L’importation dynamique avec l’import()opérateur est prise en charge avec ASP.NET


Core et Blazor :

JavaScript

if ({CONDITION}) import("/additionalModule.js");

Dans l’exemple précédent, l’espace {CONDITION} réservé représente une vérification


conditionnelle pour déterminer si le module doit être chargé.

Pour la compatibilité du navigateur, consultez Puis-je utiliser : modules JavaScript :


importation dynamique .

Éviter les références d’objets circulaires


Les objets qui contiennent des références circulaires ne peuvent pas être sérialisés sur le
client pour :

Les appels de méthode .NET.


Les appels de méthode JavaScript à partir de C# lorsque le type de retour a des
références circulaires.

Prise en charge des tableaux d’octets


Blazor prend en charge l’interopérabilité JavaScript (JS) des tableaux d’octets optimisés,
qui évite l’encodage/décodage des tableaux d’octets en Base64. L’exemple suivant
utilise l’interopérabilité JS pour passer un tableau d’octets à .NET.

Fournissez une fonction sendByteArray JS. La fonction est appelée statiquement, en


incluant le paramètre de nom d’assembly dans l’appel invokeMethodAsync , par un
bouton du composant et ne retourne pas de valeur :

HTML
<script>
window.sendByteArray = () => {
const data = new Uint8Array([0x45,0x76,0x65,0x72,0x79,0x74,0x68,0x69,
0x6e,0x67,0x27,0x73,0x20,0x73,0x68,0x69,0x6e,0x79,0x2c,
0x20,0x43,0x61,0x70,0x74,0x61,0x69,0x6e,0x2e,0x20,0x4e,
0x6f,0x74,0x20,0x74,0x6f,0x20,0x66,0x72,0x65,0x74,0x2e]);
DotNet.invokeMethodAsync('BlazorSample', 'ReceiveByteArray', data)
.then(str => {
alert(str);
});
};
</script>

7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

CallDotnet8.razor :

razor

@page "/call-dotnet-8"
@using System.Text

<PageTitle>Call .NET 8</PageTitle>

<h1>Call .NET Example 8</h1>

<p>
<button onclick="sendByteArray()">Send Bytes</button>
</p>

<p>
Quote ©2005 <a href="https://www.uphe.com">Universal Pictures</a>:
<a href="https://www.uphe.com/movies/serenity-2005">Serenity</a><br>
<a href="https://www.imdb.com/name/nm0821612/">Jewel Staite on IMDB</a>
</p>

@code {
[JSInvokable]
public static Task<string> ReceiveByteArray(byte[] receivedBytes)
{
return Task.FromResult(
Encoding.UTF8.GetString(receivedBytes, 0,
receivedBytes.Length));
}
}

Pour plus d’informations sur l’utilisation d’un tableau d’octets lors de l’appel de
JavaScript à partir de .NET, consultez Appeler des fonctions JavaScript à partir de
méthodes .NET dans ASP.NET Core Blazor.

Diffuser en continu de JavaScript vers .NET


Blazor prend en charge la diffusion en continu de données directement de JavaScript
vers .NET. Les flux sont demandés à l’aide de l’interface
Microsoft.JSInterop.IJSStreamReference .

Microsoft.JSInterop.IJSStreamReference.OpenReadStreamAsync retourne un Stream et

utilise les paramètres suivants :

maxAllowedSize : nombre maximal d’octets autorisé pour l’opération de lecture à

partir de JavaScript, 512 000 octets par défaut si non spécifié.


cancellationToken : CancellationToken pour annuler la lecture.

En JavaScript :

JavaScript

function streamToDotNet() {
return new Uint8Array(10000000);
}

Dans le code C# :

C#

var dataReference =
await JS.InvokeAsync<IJSStreamReference>("streamToDotNet");
using var dataReferenceStream =
await dataReference.OpenReadStreamAsync(maxAllowedSize: 10_000_000);

var outputPath = Path.Combine(Path.GetTempPath(), "file.txt");


using var outputFileStream = File.OpenWrite(outputPath);
await dataReferenceStream.CopyToAsync(outputFileStream);

Dans l'exemple précédent :

JS est un instance IJSRuntime injectée. IJSRuntime est inscrit par le framework

Blazor.
Le dataReferenceStream est écrit sur le disque ( file.txt ) au chemin du dossier
temporaire de l’utilisateur actuel (GetTempPath).

Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core Blazor
couvre l’opération inverse, la diffusion en continu de .NET vers JavaScript à l’aide d’une
DotNetStreamReference.

Chargements de fichiers ASP.NET Core Blazor explique comment charger un fichier dans
Blazor. Pour obtenir un exemple de formulaire qui diffuse en continu des données
<textarea> dans un composant côté serveur, consultez Résoudre les problèmes liés aux

formulaires Blazor ASP.NET Core.

JavaScript [JSImport] / [JSExport] interop


Cette section s’applique aux composants côté client.

En guise d’alternative à l’interaction avec JavaScript (JS) dans les composants côté client
à l’aide du mécanisme d’interopérabilité JS de Blazor basé sur l’interface IJSRuntime, une
API d’interopérabilité JS [JSImport] / [JSExport] est disponible pour les applications
ciblant .NET 7 ou version ultérieure.

Pour plus d’informations, consultez interopérabilité JSImport/JSExport JavaScript avec


Blazor ASP.NET Core.

Suppression des références d’objets


d’interopérabilité JavaScript
Les exemples des articles d’interopérabilité JavaScript (JS) illustrent des modèles de
suppression d’objets classiques :

Lors de l’appel de .NET à partir de JS, comme décrit dans cet article, disposez d’un
DotNetObjectReference créé à partir de .NET ou de JS pour éviter la fuite de
mémoire .NET.

Lors de l’appel JS à partir de .NET, comme décrit dans Appeler des fonctions
JavaScript à partir de méthodes .NET dans ASP.NET Core Blazor, éliminez les
fonctions créées
IJSObjectReference/IJSInProcessObjectReference/ JSObjectReference à partir de
.NET ou de JS pour éviter toute fuite de JS mémoire.

Les références d’objet d’interopérabilité JS sont implémentées en tant que carte avec
pour clé un identificateur sur le côté de l’appel d’interopérabilité JS qui crée la référence.
Lorsque l’élimination de l’objet est lancée du côté .NET ou JS, Blazor supprime l’entrée
de la carte, et l’objet peut être récupéré en mémoire tant qu’aucune autre référence
forte à l’objet n’est présente.

Au minimum, éliminez toujours les objets créés côté .NET pour éviter les fuites de
mémoire managée .NET.

Tâches de nettoyage de modèle DOM lors de la


suppression des composants
Pour plus d’informations, consultez Interopérabilité JavaScript et ASP.NET Core Blazor
(interopérabilité JS).

Appels d’interopérabilité JavaScript sans circuit


Pour plus d’informations, consultez Interopérabilité JavaScript et ASP.NET Core Blazor
(interopérabilité JS).

Ressources supplémentaires
Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core
Blazor
Exemple InteropComponent.razor (branche main du dépôt GitHub
dotnet/AspNetCore) : la branche main représente le développement actuel de
l’unité de produit pour la prochaine version d’ASP.NET Core. Pour sélectionner la
branche d’une autre version (par exemple, release/5.0 ), utilisez la liste déroulante
Changer de branche ou d’étiquette pour sélectionner la branche.
Interaction avec le DOM
Blazor exemples de référentiel GitHub (dotnet/blazor-samples)
Gérer les erreurs dans les applications ASP.NET Core Blazor (section interopérabilité
JavaScript)
Atténuation des menaces : méthodes .NET appelées à partir du navigateur

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Interopérabilité [JSImport] / [JSExport]
JavaScript avec ASP.NET Core Blazor
Article • 30/11/2023

Cet article explique comment interagir avec JavaScript (JS) dans les composants côté
client en utilisant l’API d’interopérabilité JavaScript (JS) [JSImport] / [JSExport] publiée
pour les applications adoptant .NET 7 (ou une version ultérieure).

Blazor fournit son propre mécanisme d’interopérabilité JS basé sur l’interface


IJSRuntime, qui est uniformément prise en charge dans les modes de rendu Blazor et
décrite dans les articles suivants :

Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core
Blazor
Appeler des méthodes .NET à partir de fonctions JavaScript dans ASP.NET Core
Blazor

IJSRuntime permet aux auteurs de bibliothèques de créer des bibliothèques


d’interopérabilité JS qui peuvent être partagées entre l’écosystème Blazor et reste
l’approche recommandée pour l’interopérabilité JS dans Blazor.

Cet article décrit une autre approche d’interopérabilité JS spécifique aux composants
côté client exécutés sur WebAssembly. Ces approches sont appropriées lorsque vous
comptez uniquement exécuter le code dans un environnement WebAssembly côté
client. Les auteurs de bibliothèque peuvent utiliser ces approches pour optimiser
l’interopérabilité JS en vérifiant au moment de l’exécution si l’application s’exécute sur
WebAssembly dans un navigateur (OperatingSystem.IsBrowser). Les approches décrites
dans cet article doivent être utilisées pour remplacer l’API d’interopérabilité JS
démarshalée obsolète lors de la migration vers .NET 7 (ou une version ultérieure).

7 Notes

Cet article se concentre sur l’interopérabilité JS dans les composants côté client.
Pour obtenir des conseils sur l’appel de .NET dans les applications JavaScript,
consultez Exécuter .NET à partir de JavaScript.

API d’interopérabilité JavaScript obsolète


L’interopérabilité JS démarshalée avec l’API IJSUnmarshalledRuntime est obsolète dans
ASP.NET Core 7.0 (ou une version ultérieure). Suivez les instructions de cet article pour
remplacer l’API obsolète.

Prérequis
Téléchargez et installez .NET 7.0 (ou une version plus récente) , s’il n’est pas déjà
installé sur le système ou si la dernière version n’est pas installée sur le système.

Espace de noms
L’API d’interopérabilité JS décrite dans cet article est contrôlée par des attributs de
l’espace de noms System.Runtime.InteropServices.JavaScript.

Activer les blocs non sécurisés


Activez la propriété AllowUnsafeBlocks dans le fichier projet de l’application, ce qui
permet au générateur de code dans le compilateur Roslyn d’utiliser des pointeurs pour
l’interopérabilité JS :

XML

<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

2 Avertissement

L’API d’interopérabilité JS nécessite l’activation de AllowUnsafeBlocks. Soyez


vigilant quand vous implémentez votre propre code non sécurisé dans des
applications .NET, car cela peut présenter des risques pour la sécurité et la stabilité.
Pour plus d’informations, consultez Code non sécurisé, types de pointeur et
pointeurs de fonction.

Appeler JavaScript à partir de .NET


Cette section explique comment appeler des fonctions JS à partir de .NET.

Dans le composant CallJavaScript1 suivant :


Le module CallJavaScript1 est importé de manière asynchrone à partir du fichier
JS colocalisé avec JSHost.ImportAsync.
La fonction getMessage JS importée est appelée par GetWelcomeMessage .
La chaîne de message d’accueil retournée s’affiche dans l’interface utilisateur via le
champ message .

CallJavaScript1.razor :

razor

@page "/call-javascript-1"
@rendermode InteractiveWebAssembly
@using System.Runtime.InteropServices.JavaScript

<h1>
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call JS Example 1)
</h1>

@(message is not null ? message : string.Empty)

@code {
private string? message;

protected override async Task OnInitializedAsync()


{
await JSHost.ImportAsync("CallJavaScript1",
"../Components/Pages/CallJavaScript1.razor.js");

message = GetWelcomeMessage();
}
}

7 Notes

Incluez un code de vérification conditionnel avec OperatingSystem.IsBrowser pour


vous assurer que l’interopérabilité JS est appelée uniquement par un composant
rendu sur le client. Cela est important pour les bibliothèques/packages NuGet qui
ciblent les composants côté serveur ne pouvant pas exécuter le code fourni par
cette API d’interopérabilité JS.

Pour importer une fonction JS pour l’appeler à partir de C#, utilisez l’attribut [JSImport]
sur une signature de méthode C# qui correspond à la signature de la fonction JS. Le
premier paramètre de l’attribut [JSImport] est le nom de la fonction JS à importer, et le
deuxième paramètre est le nom du module JS.
Dans l’exemple suivant, getMessage est une fonction JS qui retourne un string pour un
module nommé CallJavaScript1 . La signature de méthode C# correspond : aucun
paramètre n’est passé à la fonction JS, et la fonction JS retourne un string . La fonction
JS est appelée par GetWelcomeMessage dans le code C#.

CallJavaScript1.razor.cs :

C#

using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;

namespace BlazorSample.Components.Pages;

[SupportedOSPlatform("browser")]
public partial class CallJavaScript1
{
[JSImport("getMessage", "CallJavaScript1")]
internal static partial string GetWelcomeMessage();
}

L’espace de noms de l’application pour la classe partielle CallJavaScript1 précédente


est BlazorSample . L’espace de noms du composant est BlazorSample.Components.Pages .
Si vous utilisez le composant précédent dans une application de test locale, mettez à
jour l’espace de noms pour qu’il corresponde à l’application. Par exemple, l’espace de
noms est ContosoApp.Components.Pages si l’espace de noms de l’application est
ContosoApp . Pour plus d’informations, consultez Composants ASP.NET Core Razor.

Dans la signature de méthode importée, vous pouvez utiliser des types .NET pour les
paramètres et les valeurs de retour, qui sont marshalés automatiquement par le runtime.
Utilisez JSMarshalAsAttribute<T> pour contrôler la façon dont les paramètres de
méthode importés sont marshalés. Par exemple, vous pouvez choisir de marshaler long
en tant que System.Runtime.InteropServices.JavaScript.JSType.Number ou
System.Runtime.InteropServices.JavaScript.JSType.BigInt. Vous pouvez passer des
rappels Action/Func<TResult> en tant que paramètres, qui sont marshalés en tant que
fonctions JS pouvant être appelées. Vous pouvez passer à la fois des références d’objets
JS et des références d’objets managés. Elles sont marshalées en tant qu’objets proxy, ce
qui permet de conserver l’objet actif entre les environnements jusqu’à ce que le proxy
soit traité pour un nettoyage de la mémoire. Vous pouvez également importer et
exporter des méthodes asynchrones avec un résultat Task, qui sont marshalées en tant
que promesses JS . La plupart des types marshalés fonctionnent dans les deux sens, en
tant que paramètres et valeurs de retour, sur les méthodes importées et exportées, qui
sont couvertes dans la section Appeler .NET à partir de JavaScript plus loin dans cet
article.

Le tableau suivant indique les mappages de types pris en charge.

.NET JavaScript Nullable Task ➔ JSMarshalAs Array


Promise facultatif of

Boolean Boolean ✅ ✅ ✅

Byte Number ✅ ✅ ✅ ✅

Char String ✅ ✅ ✅

Int16 Number ✅ ✅ ✅

Int32 Number ✅ ✅ ✅ ✅

Int64 Number ✅ ✅

Int64 BigInt ✅ ✅

Single Number ✅ ✅ ✅

Double Number ✅ ✅ ✅ ✅

IntPtr Number ✅ ✅ ✅

DateTime Date ✅ ✅

DateTimeOffset Date ✅ ✅

Exception Error ✅ ✅

JSObject Object ✅ ✅ ✅

String String ✅ ✅ ✅

Object Any ✅ ✅

Span<Byte> MemoryView

Span<Int32> MemoryView

Span<Double> MemoryView

ArraySegment<Byte> MemoryView

ArraySegment<Int32> MemoryView

ArraySegment<Double> MemoryView
.NET JavaScript Nullable Task ➔ JSMarshalAs Array
Promise facultatif of

Task Promise ✅

Action Function

Action<T1> Function

Action<T1, T2> Function

Action<T1, T2, T3> Function

Func<TResult> Function

Func<T1, TResult> Function

Func<T1, T2, TResult> Function

Func<T1, T2, T3, Function


TResult>

Les conditions suivantes s’appliquent au mappage de type et aux valeurs marshalées :

La colonne Array of indique si le type .NET peut être marshalé en tant que
JSArray . Exemple : C# int[] ( Int32 ) mappé à JS Array de Number .
Quand une valeur JS est passée en C# avec un type incorrect, le framework lève
une exception dans la plupart des cas. Le framework n’effectue pas de contrôle de
type au moment de la compilation en JS.
JSObject , Exception , Task et ArraySegment créent GCHandle et un proxy. Vous
pouvez déclencher la suppression dans le code de développeur, ou autoriser le GC
(nettoyage de la mémoire) .NET à supprimer les objets plus tard. Ces types
entraînent une surcharge importante au niveau des performances.
Array : Le marshaling d’un tableau crée une copie du tableau en JS ou dans .NET.
MemoryView

MemoryView est une classe JS qui permet au runtime .NET WebAssembly de

marshaler Span et ArraySegment .


Contrairement au marshaling d’un tableau, le marshaling de Span ou
ArraySegment ne crée pas de copie de la mémoire sous-jacente.
MemoryView peut uniquement être correctement instancié par le runtime .NET

WebAssembly. Il n’est donc pas possible d’importer une fonction JS en tant que
méthode .NET qui a un paramètre Span ou ArraySegment .
Un MemoryView créé pour un Span est uniquement valide pour la durée de
l’appel d’interopérabilité. Dans la mesure où Span est alloué sur la pile des
appels, qui ne persiste pas après l’appel d’interopérabilité, il n’est pas possible
d’exporter une méthode .NET qui retourne Span .
Un MemoryView créé pour un ArraySegment survit après l’appel d’interopérabilité,
et est utile pour partager de la mémoire tampon. L’appel de dispose() sur un
MemoryView créé pour ArraySegment supprime le proxy, et dissocie le tableau

.NET sous-jacent. Nous vous recommandons d’appeler dispose() dans un bloc


try-finally pour MemoryView .

Le nom du module dans l’attribut [JSImport] et l’appel pour charger le module dans le
composant avec JSHost.ImportAsync doivent correspondre et être uniques dans
l’application. Lors de la création d’une bibliothèque pour le déploiement dans un
package NuGet, nous vous recommandons d’utiliser l’espace de noms de package
NuGet comme préfixe dans les noms de modules. Dans l’exemple suivant, le nom du
module reflète le package Contoso.InteropServices.JavaScript et un dossier de classes
d’interopérabilité des messages utilisateur ( UserMessages ) :

C#

[JSImport("getMessage",
"Contoso.InteropServices.JavaScript.UserMessages.CallJavaScript1")]

Les fonctions accessibles sur l’espace de noms global peuvent être importées à l’aide du
préfixe globalThis dans le nom de la fonction et à l’aide de l’attribut [JSImport] sans
fournir de nom de module. Dans l’exemple suivant, console.log est précédé de
globalThis . La fonction importée est appelée par la méthode C# Log , qui accepte un

message de chaîne C# ( message ) et marshale la chaîne C# en JSString pour


console.log :

C#

[JSImport("globalThis.console.log")]
internal static partial void Log([JSMarshalAs<JSType.String>] string
message);

Exportez des scripts à partir d’un module JavaScript ES6 standard colocalisé avec un
composant ou placé avec d’autres ressources statiques JavaScript dans un fichier JS (par
exemple, wwwroot/js/{FILE NAME}.js , où des ressources statiques JS sont conservées
dans un dossier nommé js dans le dossier wwwroot de l’application, et où l’espace
réservé {FILE NAME} est le nom de fichier).
Dans l’exemple suivant, une fonction JS nommée getMessage est exportée à partir d’un
fichier JS colocalisé qui retourne un message de bienvenue, « Hello from Blazor! » en
portugais :

CallJavaScript1.razor.js :

JavaScript

export function getMessage() {


return 'Olá do Blazor!';
}

Appeler .NET à partir de JavaScript


Cette section explique comment appeler des méthodes .NET à partir de JS.

Le composant CallDotNet1 suivant appelle JS, qui interagit directement avec le DOM
pour afficher la chaîne de message d’accueil :

Le CallDotNet module JS est importé de manière asynchrone à partir du fichier JS


colocalisé pour ce composant.
La fonction setMessage JS importée est appelée par SetWelcomeMessage .
Le message de bienvenue retourné est affiché par setMessage dans l’interface
utilisateur via le champ message .

) Important

Dans l’exemple de cette section, l’interopérabilité JS est utilisée pour muter un


élément DOM uniquement à des fins de démonstration après le rendu du
composant dans OnAfterRender. En règle générale, vous devez uniquement muter
le DOM avec JS lorsque l’objet n’interagit pas avec Blazor. L’approche décrite dans
cette section est similaire aux cas où une bibliothèque tierce JS est utilisée dans un
composant Razor, où le composant interagit avec la bibliothèque JS via
l’interopérabilité JS, la bibliothèque JS tierce interagit avec une partie du DOM et
Blazor n’est pas impliqué directement avec les mises à jour DOM de cette partie du
DOM. Pour plus d’informations, consultez Interopérabilité JavaScript et ASP.NET
Core Blazor (interopérabilité JS).

CallDotNet1.razor :

razor
@page "/call-dotnet-1"
@rendermode InteractiveWebAssembly
@using System.Runtime.InteropServices.JavaScript

<h1>
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call .NET Example 1)
</h1>

<p>
<span id="result">.NET method not executed yet</span>
</p>

@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSHost.ImportAsync("CallDotNet1",
"../Components/Pages/CallDotNet1.razor.js");

SetWelcomeMessage();
}
}
}

Pour exporter une méthode .NET afin qu’elle puisse être appelée à partir de JS, utilisez
l’attribut [JSExport].

Dans l’exemple suivant :

SetWelcomeMessage appelle une fonction JS nommée setMessage . La fonction JS

appelle .NET pour recevoir le message d’accueil de GetMessageFromDotnet et affiche


le message dans l’interface utilisateur.
GetMessageFromDotnet est une méthode .NET avec l’attribut [JSExport] , qui

retourne un message de bienvenue, « Hello from Blazor! » en portugais.

CallDotNet1.razor.cs :

C#

using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;

namespace BlazorSample.Components.Pages;

[SupportedOSPlatform("browser")]
public partial class CallDotNet1
{
[JSImport("setMessage", "CallDotNet1")]
internal static partial void SetWelcomeMessage();

[JSExport]
internal static string GetMessageFromDotnet()
{
return "Olá do Blazor!";
}
}

L’espace de noms de l’application pour la classe partielle CallDotNet1 précédente est


BlazorSample . L’espace de noms du composant est BlazorSample.Components.Pages . Si

vous utilisez le composant précédent dans une application de test locale, mettez à jour
l’espace de noms de l’application pour qu’il corresponde à l’application. Par exemple,
l’espace de noms du composant est ContosoApp.Components.Pages si l’espace de noms
de l’application est ContosoApp . Pour plus d’informations, consultez Composants
ASP.NET Core Razor.

Dans l’exemple suivant, une fonction JS nommée setMessage est importée à partir d’un
fichier JS colocalisé.

La méthode setMessage :

Appelle globalThis.getDotnetRuntime(0) pour exposer l’instance de runtime


WebAssembly .NET pour appeler des méthodes .NET exportées.
Obtient les exportations JS de l’assembly d’application. Le nom de l’assembly
d’application dans l’exemple suivant est BlazorSample .
Appelle la méthode
BlazorSample.Components.Pages.CallDotNet1.GetMessageFromDotnet à partir des

exportations ( exports ). La valeur retournée, qui est le message d’accueil, est


affectée au texte <span> du composant CallDotNet1 . L’espace de noms de
l’application est BlazorSample , et l’espace de noms du composant CallDotNet1 est
BlazorSample.Components.Pages .

CallDotNet1.razor.js :

JavaScript

export async function setMessage() {


const { getAssemblyExports } = await globalThis.getDotnetRuntime(0);
var exports = await getAssemblyExports("BlazorSample.dll");

document.getElementById("result").innerText =
exports.BlazorSample.Components.Pages.CallDotNet1.GetMessageFromDotnet();
}

7 Notes

L’appel de getAssemblyExports pour obtenir les exportations peut se produire dans


un initialiseur JavaScript pour la disponibilité dans l’application.

Appels d’importation de plusieurs modules


Une fois qu’un module JS est chargé, les fonctions JS du module sont disponibles pour
les composants et classes de l’application tant que l’application s’exécute dans la fenêtre
ou l’onglet du navigateur, sans que l’utilisateur recharge manuellement l’application.
JSHost.ImportAsync peut être appelé plusieurs fois sur le même module sans pénalité
de performances significative lorsque :

L’utilisateur visite un composant qui appelle JSHost.ImportAsync pour importer un


module, s’éloigne du composant, puis retourne au composant, où
JSHost.ImportAsync est appelé à nouveau pour la même importation de module.
Le même module est utilisé par différents composants et chargé par
JSHost.ImportAsync dans chacun des composants.

Utilisation d’un seul module JavaScript entre


les composants
Avant de suivre les instructions de cette section, lisez les sections Appeler JavaScript à
partir de .NET et Appeler .NET à partir de JavaScript de cet article, qui fournissent des
conseils généraux sur l’interopérabilité [JSImport] / [JSExport] .

L’exemple de cette section montre comment utiliser l’interopérabilité JS à partir d’un


module JS partagé dans une application côté client. Les instructions fournies dans cette
section ne s’appliquent pas aux bibliothèques de classes Razor.

Les composants, classes, méthodes C# et fonctions JS qui suivent sont utilisés :

Classe Interop ( Interop.cs ) : configure l’interopérabilité JS d’importation et


d’exportation avec les attributs [JSImport] et [JSExport] pour un module nommé
Interop .

GetWelcomeMessage : méthode .NET qui appelle la fonction getMessage JS

importée.
SetWelcomeMessage : méthode .NET qui appelle la fonction setMessage JS

importée.
GetMessageFromDotnet : méthode C# exportée qui retourne une chaîne de

message d’accueil lorsqu’elle est appelée à partir de JS.


Fichier wwwroot/js/interop.js : contient les fonctions JS.
getMessage : retourne un message de bienvenue lorsqu’il est appelé par du

code C# dans un composant.


setMessage : appelle la GetMessageFromDotnet méthode C# et affecte le message

d’accueil retourné à un élément <span> DOM.


Program.cs appelle JSHost.ImportAsync pour charger le module à partir de

wwwroot/js/interop.js .

Composant CallJavaScript2 ( CallJavaScript2.razor ) : appelle GetWelcomeMessage


et affiche le message de bienvenue retourné dans l’interface utilisateur du
composant.
Composant CallDotNet2 ( CallDotNet2.razor ) : appelle SetWelcomeMessage .

Interop.cs :

C#

using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;

namespace BlazorSample.JavaScriptInterop;

[SupportedOSPlatform("browser")]
public partial class Interop
{
[JSImport("getMessage", "Interop")]
internal static partial string GetWelcomeMessage();

[JSImport("setMessage", "Interop")]
internal static partial void SetWelcomeMessage();

[JSExport]
internal static string GetMessageFromDotnet()
{
return "Olá do Blazor!";
}
}

Dans l’exemple précédent, l’espace de noms de l’application est BlazorSample , et


l’espace de noms complet pour les classes d’interopérabilité C# est
BlazorSample.JavaScriptInterop .
wwwroot/js/interop.js :

JavaScript

export function getMessage() {


return 'Olá do Blazor!';
}

export async function setMessage() {


const { getAssemblyExports } = await globalThis.getDotnetRuntime(0);
var exports = await getAssemblyExports("BlazorSample.dll");

document.getElementById("result").innerText =
exports.BlazorSample.JavaScriptInterop.Interop.GetMessageFromDotnet();
}

Rendez l’espace de noms System.Runtime.InteropServices.JavaScript disponible en haut


du fichier Program.cs :

C#

using System.Runtime.InteropServices.JavaScript;

Chargez le module dans Program.cs avant l’appel à WebAssemblyHost.RunAsync :

C#

if (OperatingSystem.IsBrowser())
{
await JSHost.ImportAsync("Interop", "../js/interop.js");
}

CallJavaScript2.razor :

razor

@page "/call-javascript-2"
@rendermode InteractiveWebAssembly
@using BlazorSample.JavaScriptInterop

<h1>
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call JS Example 2)
</h1>

@(message is not null ? message : string.Empty)

@code {
private string? message;
protected override void OnInitialized()
{
message = Interop.GetWelcomeMessage();
}
}

CallDotNet2.razor :

razor

@page "/call-dotnet-2"
@rendermode InteractiveWebAssembly
@using BlazorSample.JavaScriptInterop

<h1>
JS <code>[JSImport]</code>/<code>[JSExport]</code> Interop
(Call .NET Example 2)
</h1>

<p>
<span id="result">.NET method not executed</span>
</p>

@code {
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Interop.SetWelcomeMessage();
}
}
}

) Important

Dans l’exemple de cette section, l’interopérabilité JS est utilisée pour muter un


élément DOM uniquement à des fins de démonstration après le rendu du
composant dans OnAfterRender. En règle générale, vous devez uniquement muter
le DOM avec JS lorsque l’objet n’interagit pas avec Blazor. L’approche décrite dans
cette section est similaire aux cas où une bibliothèque tierce JS est utilisée dans un
composant Razor, où le composant interagit avec la bibliothèque JS via
l’interopérabilité JS, la bibliothèque JS tierce interagit avec une partie du DOM et
Blazor n’est pas impliqué directement avec les mises à jour DOM de cette partie du
DOM. Pour plus d’informations, consultez Interopérabilité JavaScript et ASP.NET
Core Blazor (interopérabilité JS).
Ressources supplémentaires
Documentation de l’API
Attribut [JSImport]
[JSExport], attribut
Exécuter .NET à partir de JavaScript
Dans le dépôt GitHub dotnet/runtime :
Runtime .NET WebAssembly
Fichier dotnet.d.ts (configuration du runtime .NET WebAssembly)

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
JavaScript Blazor ASP.NET Core avec
rendu côté serveur statique
Article • 09/02/2024

Cet article explique comment charger JavaScript (JS) dans une application web Blazor
avec le rendu côté serveur statique et la navigation améliorée.

Certaines applications dépendent de JS pour effectuer des tâches d’initialisation


spécifiques à chaque page. Lorsque vous utilisez la fonctionnalité de navigation
améliorée de Blazor, qui permet à l’utilisateur d’éviter le rechargement de la page
entière, il est possible que l’action JS spécifique à la page ne soit pas réexécutée comme
prévu chaque fois qu’une navigation de page améliorée se produit.

Pour éviter ce problème, nous ne recommandons pas de s’appuyer sur des éléments
<script> spécifiques à la page placés en dehors du fichier de disposition appliqué au

composant. Les scripts doivent plutôt inscrire un initialiseur pour effectuer une logique
afterWebStartedJSd’initialisation et utiliser un écouteur d’événements
( blazor.addEventListener("enhancedload", callback) ) pour écouter les mises à jour de
page provoquées par une navigation améliorée.

L’exemple suivant montre une façon de configurer le code JS à exécuter lorsqu’une page
rendue statiquement avec une navigation améliorée est initialement chargée ou mise à
jour.

Ajoutez le composant PageWithScript suivant.

Components/Pages/PageWithScript.razor :

razor

@page "/page-with-script"
@using BlazorPageScript

<PageTitle>Enhanced Load Script Example</PageTitle>

<PageScript Src="./Components/Pages/PageWithScript.razor.js" />

Welcome to my page.

Dans l’application web Blazor, ajoutez le fichier JS colocalisé suivant :

onLoad est appelé lorsque le script est ajouté à la page.


onUpdate est appelé lorsque le script existe toujours sur la page après une mise à

jour améliorée.
onDispose est appelé lorsque le script est supprimé de la page après une mise à

jour améliorée.

Components/Pages/PageWithScript.razor.js :

JavaScript

export function onLoad() {


console.log('Loaded');
}

export function onUpdate() {


console.log('Updated');
}

export function onDispose() {


console.log('Disposed');
}

Dans une bibliothèque de classes Razor (RCL) (l’exemple RCL est nommé
BlazorPageScript ), ajoutez le module suivant.

wwwroot/BlazorPageScript.lib.module.js :

JavaScript

const pageScriptInfoBySrc = new Map();

function registerPageScriptElement(src) {
if (!src) {
throw new Error('Must provide a non-empty value for the "src"
attribute.');
}

let pageScriptInfo = pageScriptInfoBySrc.get(src);

if (pageScriptInfo) {
pageScriptInfo.referenceCount++;
} else {
pageScriptInfo = { referenceCount: 1, module: null };
pageScriptInfoBySrc.set(src, pageScriptInfo);
initializePageScriptModule(src, pageScriptInfo);
}
}

function unregisterPageScriptElement(src) {
if (!src) {
return;
}

const pageScriptInfo = pageScriptInfoBySrc.get(src);


if (!pageScriptInfo) {
return;
}

pageScriptInfo.referenceCount--;
}

async function initializePageScriptModule(src, pageScriptInfo) {


if (src.startsWith("./")) {
src = new URL(src.substr(2), document.baseURI).toString();
}

const module = await import(src);

if (pageScriptInfo.referenceCount <= 0) {
return;
}

pageScriptInfo.module = module;
module.onLoad?.();
module.onUpdate?.();
}

function onEnhancedLoad() {
for (const [src, { module, referenceCount }] of pageScriptInfoBySrc) {
if (referenceCount <= 0) {
module?.onDispose?.();
pageScriptInfoBySrc.delete(src);
}
}

for (const { module } of pageScriptInfoBySrc.values()) {


module?.onUpdate?.();
}
}

export function afterWebStarted(blazor) {


customElements.define('page-script', class extends HTMLElement {
static observedAttributes = ['src'];

attributeChangedCallback(name, oldValue, newValue) {


if (name !== 'src') {
return;
}

this.src = newValue;
unregisterPageScriptElement(oldValue);
registerPageScriptElement(newValue);
}

disconnectedCallback() {
unregisterPageScriptElement(this.src);
}
});

blazor.addEventListener('enhancedload', onEnhancedLoad);
}

Ajoutez le composant PageScript suivant à la RCL.

PageScript.razor :

razor

<page-script src="@Src"></page-script>

@code {
[Parameter]
[EditorRequired]
public string Src { get; set; } = default!;
}

Le composant PageScript fonctionne normalement au niveau supérieur d’une page.

Si vous placez le composant PageScript dans la présentation de l’application (par


exemple, Components/Layout/MainLayout.razor ), ce qui se traduit par un PageScript
partagé entre les pages qui utilisent la présentation, le composant ne s’exécute onLoad
qu’après un rechargement complet de la page et onUpdate lors de toute mise à jour
améliorée de la page, y compris la navigation améliorée.

Pour réutiliser le même module d’une page à l’autre, mais en invoquant les rappels
onLoad et onDispose à chaque changement de page, ajoutez une chaîne de requête à la

fin du script afin qu’il soit reconnu comme un module différent. Une application peut
adopter la convention d’utilisation du nom du composant comme valeur de chaîne de
requête. Dans l’exemple suivant, la chaîne de requête est « counter », car cette
PageScript référence de composant est placée dans un composant Counter . Il s’agit

simplement d’une suggestion, et vous pouvez utiliser le schéma de chaîne de requête


que vous préférez.

razor

<PageScript Src="./Components/Pages/PageWithScript.razor.js?counter" />

Pour surveiller les modifications dans des éléments DOM spécifiques, utilisez le modèle
MutationObserver dans JS sur le client. Pour plus d’informations, consultez
Interopérabilité JavaScript et ASP.NET Core Blazor (interopérabilité JS).
6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Appeler une API web à partir d’ASP.NET
Core Blazor
Article • 09/02/2024

Cet article explique comment appeler une API web à partir d’une application Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

7 Notes

Les exemples de code de cet article adoptent les types référence null (NRT) et
l'analyse statique de l'état null du compilateur .NET, qui sont pris en charge dans
ASP.NET Core 6.0 ou une version ultérieure. Lorsque vous ciblez ASP.NET Core 5.0
ou version antérieure, supprimez la désignation de type Null ( ? ) des types string? ,
TodoItem[]? , WeatherForecast[]? et IEnumerable<GitHubBranch>? dans les exemples

de l’article.

7 Notes

Cet article a chargé la couverture du rendu côté serveur interactif (SSR interactif)
pour appeler des API web. La couverture du rendu côté client (CSR) WebAssembly
traite les sujets suivants :

Exemples côté client qui appellent une API web pour créer, lire, mettre à jour
et supprimer des éléments de liste de tâches.
Package System.Net.Http.Json .
Configuration du service HttpClient .
HttpClient et assistants JSON ( GetFromJsonAsync , PostAsJsonAsync ,

PutAsJsonAsync , DeleteAsync ).

Services IHttpClientFactory et configuration d’un HttpClient nommé.


HttpClient typé.

HttpClient et HttpRequestMessage pour personnaliser les requêtes.

Exemple d’appel d’API web avec le partage de ressources Cross-Origin (CORS)


et la façon dont CORS se rapporte aux composants côté client.
Comment gérer les erreurs de réponse de l’API web dans le code du
développeur.
Exemples de composants de framework Blazor pour tester l’accès à l’API web.
Ressources supplémentaires pour le développement de composants côté
client qui appellent une API web.
Les composants basés sur le serveur appellent des API web à l’aide d’instances
HttpClient, généralement créées en utilisant IHttpClientFactory. Pour obtenir des
conseils qui s’appliquent aux applications côté serveur, consultez Effectuer des requêtes
HTTP en utilisant IHttpClientFactory dans ASP.NET Core.

Une application côté serveur n’inclut pas de service HttpClient par défaut. Fournissez un
HttpClient à l’application à l’aide du framework de fabrique HttpClient.

Dans le fichier Program :

C#

builder.Services.AddHttpClient();

Le composant Razor suivant effectue une requête auprès d’une API web pour les
branches GitHub, comme dans l’exemple d’Utilisation de base de l’article Effectuer des
requêtes HTTP en utilisant IHttpClientFactory dans ASP.NET Core.

CallWebAPI.razor :

razor

@page "/call-web-api"
@using System.Text.Json
@using System.Text.Json.Serialization
@inject IHttpClientFactory ClientFactory

<h1>Call web API from a Blazor Server Razor component</h1>

@if (getBranchesError || branches is null)


{
<p>Unable to get branches from GitHub. Please try again later.</p>
}
else
{
<ul>
@foreach (var branch in branches)
{
<li>@branch.Name</li>
}
</ul>
}

@code {
private IEnumerable<GitHubBranch>? branches = Array.Empty<GitHubBranch>
();
private bool getBranchesError;
private bool shouldRender;

protected override bool ShouldRender() => shouldRender;


protected override async Task OnInitializedAsync()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

var client = ClientFactory.CreateClient();

var response = await client.SendAsync(request);

if (response.IsSuccessStatusCode)
{
using var responseStream = await
response.Content.ReadAsStreamAsync();
branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
getBranchesError = true;
}

shouldRender = true;
}

public class GitHubBranch


{
[JsonPropertyName("name")]
public string? Name { get; set; }
}
}

Pour obtenir un exemple de travail supplémentaire, consultez l’exemple de chargement


de fichiers côté serveur qui charge des fichiers sur un contrôleur d’API web dans l’article
Chargements de fichiersBlazor ASP.NET Core.

CORS (Cross Origin Resource Sharing)


La sécurité du navigateur empêche une page web d’envoyer des requêtes à un domaine
différent de celui qui a servi la page web. Cette restriction est appelée stratégie de même
origine. La stratégie de même origine restreint (mais n’empêche pas) un site malveillant
de lire des données sensibles à partir d’un autre site. Pour effectuer des requêtes à partir
du navigateur vers un point de terminaison avec une origine différente, le point de
terminaison doit activer le partage de ressources Cross-Origin (CORS) .

Pour plus d’informations, consultez Activer les requêtes cross-origin (CORS) dans
ASP.NET Core.
Prise en charge d’Antiforgery
Pour ajouter la prise en charge Antiforgery à une requête HTTP, injectez
AntiforgeryStateProvider ajoutez-en-tête RequestToken à la collection d’en-têtes en

tant que RequestVerificationToken :

razor

@inject AntiforgeryStateProvider Antiforgery

C#

private async Task OnSubmit()


{
var antiforgery = Antiforgery.GetAntiforgeryToken();
var request = new HttpRequestMessage(HttpMethod.Post, "action");
request.Headers.Add("RequestVerificationToken",
antiforgery.RequestToken);
var response = await client.SendAsync(request);
...
}

Pour plus d’informations, consultez Authentification et autorisationBlazor ASP.NET Core .

Exemples de composants de framework Blazor


pour tester l’accès à l’API web
Différents outils réseau sont disponibles publiquement pour tester directement les
applications back-end d’API web, comme Firefox Browser Developer et Postman . La
source de référence du framework Blazor inclut des ressources de test HttpClient utiles
pour tester :

Ressources HttpClientTest dans le dépôt GitHub dotnet/aspnetcore

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Ressources supplémentaires
Scénarios de sécurité supplémentaires ASP.NET CoreBlazor côté serveur : inclut
une couverture sur l’utilisation de HttpClient pour effectuer des requêtes d’API web
sécurisées.
Effectuer des requêtes HTTP en utilisant IHttpClientFactory dans ASP.NET Core
Appliquer HTTPS dans ASP.NET Core
Activation des demandes multi-origines (CORS) dans ASP.NET Core
Configuration du point de terminaison HTTPS Kestrel
Cross-Origin Resource Sharing (CORS) at W3C

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Utiliser des images dans ASP.NET Core
Blazor
Article • 21/12/2023

Cet article décrit les scénarios courants d’utilisation des images dans les applications
Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lors de l’utilisation des modes d’affichage WebAssembly interactif ou Auto interactif, le


code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Définition dynamique d’une source d’image


L’exemple suivant montre comment définir dynamiquement la source d’une image avec
un champ C#.

Pour l’exemple de cette section :

Obtenez trois images d’une source quelconque ou cliquez avec le bouton droit sur
chacune des images suivantes pour les enregistrer localement. Nommez les
images image1.png , image2.png et image3.png .

Placez les images dans un nouveau dossier nommé images à la racine web de
l’application ( wwwroot ). Le dossier images est utilisé uniquement à des fins de
démonstration. Vous pouvez organiser les images dans n’importe quelle
disposition de dossier de votre choix, y compris les servir directement à partir du
dossier wwwroot .

Dans le composant ShowImage1 suivant :

La source de l’image ( src ) est définie dynamiquement sur la valeur de


imageSource en C#.

La méthode ShowImage met à jour le champ imageSource en fonction d’un


argument d’image id passé à la méthode.
Les boutons de rendu appellent la méthode ShowImage avec un argument image
pour chacune des trois images disponibles dans le dossier images . Le nom de
fichier est composé à l’aide de l’argument passé à la méthode et correspond à
l’une des trois images du dossier images .

ShowImage1.razor :

razor
@page "/show-image-1"

<PageTitle>Show Image 1</PageTitle>

<h1>Show Image Example 1</h1>

@if (imageSource is not null)


{
<p>
<img src="@imageSource" />
</p>
}

@for (var i = 1; i <= 3; i++)


{
var imageId = i;
<button @onclick="() => ShowImage(imageId)">
Image @imageId
</button>
}

@code {
private string? imageSource;

private void ShowImage(int id)


{
imageSource = $"images/image{id}.png";
}
}

L’exemple précédent utilise un champ C# pour contenir les données sources de l’image,
mais vous pouvez également utiliser une propriété C# pour contenir les données.

7 Notes

Évitez d’utiliser une variable de boucle directement dans une expression lambda,
comme i dans l’exemple de boucle for précédent. Sinon, la même variable est
utilisée par toutes les expressions lambda, ce qui entraîne l’utilisation de la même
valeur dans toutes les expressions lambda. Capturez la valeur de la variable dans
une variable locale. Dans l'exemple précédent :

La variable de boucle i est affectée à imageId .


imageId est utilisé dans l’expression lambda.

Vous pouvez également utiliser une boucle foreach avec Enumerable.Range, qui
ne souffre pas du problème précédent :

razor
@foreach (var imageId in Enumerable.Range(1,3))
{
<button @onclick="() => ShowImage(imageId)">
Image @imageId
</button>
}

Pour plus d’informations, consultez Gestion des événements ASP.NET CoreBlazor.

Diffuser en continu des données d’image


Une image peut être envoyée directement au client à l’aide des fonctions
d’interopérabilité de streaming de Blazor au lieu d’héberger l’image sur une URL
publique.

L’exemple de cette section diffuse des données sources d’image à l’aide de


l’interopérabilité JavaScript (JS). La fonction setImage JS suivante accepte la balise
<img> id et le flux de données pour l’image. Cette fonction effectue les étapes

suivantes :

Lit le flux fourni dans un ArrayBuffer .


Crée un Blob pour encapsuler le ArrayBuffer .
Crée une URL d’objet pour servir d’adresse à l’image à afficher.
Met à jour l’élément <img> avec le imageElementId spécifié avec l’URL d’objet que
vous venez de créer.
Pour éviter les fuites de mémoire, la fonction appelle revokeObjectURL pour
supprimer l’URL d’objet lorsque le composant a fini de fonctionner avec une
image.

HTML

<script>
window.setImage = async (imageElementId, imageStream) => {
const arrayBuffer = await imageStream.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
const image = document.getElementById(imageElementId);
image.onload = () => {
URL.revokeObjectURL(url);
}
image.src = url;
}
</script>
7 Notes

Pour obtenir des conseils généraux sur l’emplacement JS et nos recommandations


pour les applications de production, consultez Interopérabilité JavaScript ASP.NET
Core Blazor (interopérabilité JS).

Le composant ShowImage2 suivant :

Injecte des services pour un System.Net.Http.HttpClient et


Microsoft.JSInterop.IJSRuntime.
Inclut une balise <img> pour afficher une image.
Dispose d’une méthode C# GetImageStreamAsync pour récupérer un Stream pour
une image. Une application de production peut générer dynamiquement une
image en fonction de l’utilisateur spécifique ou récupérer une image à partir du
stockage. L’exemple suivant récupère l’avatar .NET pour le dépôt GitHub dotnet .
Dispose d’une méthode SetImageAsync qui est déclenchée lors de la sélection du
bouton par l’utilisateur. SetImageAsync effectue les tâches suivantes :
Récupère le Stream partir de GetImageStreamAsync .
Encapsule le Stream dans un DotNetStreamReference, ce qui permet de diffuser
en continu les données d’image vers le client.
Appelle la fonction JavaScript setImage , qui accepte les données sur le client.

7 Notes

Les applications côté serveur utilisent un service HttpClient dédié pour effectuer
des requêtes. Par conséquent, aucune action n’est requise par le développeur d’une
application Blazor côté serveur pour inscrire un service HttpClient. Les applications
côté client ont une inscription de service HttpClient par défaut lorsque l’application
est créée à partir d’un modèle de projet Blazor. Si aucune inscription de service
HttpClient n’est présente dans le fichier Program d’une application côté client,
fournissez-en une en ajoutant builder.Services.AddHttpClient(); . Pour plus
d’informations, consultez Effectuer des requêtes HTTP en utilisant
IHttpClientFactory dans ASP.NET Core.

ShowImage2.razor :

razor

@page "/show-image-2"
@inject HttpClient Http
@inject IJSRuntime JS

<PageTitle>Show Image 2</PageTitle>

<h1>Show Image Example 2</h1>

<p>
<img id="image" />
</p>

<button @onclick="SetImageAsync">
Set Image
</button>

@code {
private async Task<Stream> GetImageStreamAsync()
{
return await Http.GetStreamAsync(
"https://avatars.githubusercontent.com/u/9141961");
}

private async Task SetImageAsync()


{
var imageStream = await GetImageStreamAsync();
var dotnetImageStream = new DotNetStreamReference(imageStream);
await JS.InvokeVoidAsync("setImage", "image", dotnetImageStream);
}
}

Ressources supplémentaires
Chargements de fichiers Blazor ASP.NET Core
Chargements de fichiers : Charger un aperçu des images
Téléchargements de fichiers dans ASP.NET Core Blazor
Appeler des méthodes .NET à partir de fonctions JavaScript dans ASP.NET Core
Blazor
Appeler des fonctions JavaScript à partir de méthodes .NET dans ASP.NET Core
Blazor
Blazor exemples de référentiel GitHub (dotnet/blazor-samples)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus
documentation
d’informations, consultez notre
guide du contributeur.
 Indiquer des commentaires sur
le produit
Authentification et autorisation avec
ASP.NET Core Blazor
Article • 09/02/2024

Cet article décrit la prise en charge d’ASP.NET Core pour la configuration et la gestion
de la sécurité dans les applications Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du Blazorréférentiel GitHub
d’exemples qui correspond à la version de .NET que vous ciblez. Pour le moment, tous
les exemples de la documentation ne figurent pas dans les exemples d’applications,
mais nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans
les exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant
du premier trimestre 2024.

Les scénarios de sécurité diffèrent entre les applications Blazor côté serveur et côté
client. Étant donné qu’une application côté serveur s’exécute sur le serveur, les
vérifications d’autorisation sont en mesure de déterminer :

Les options de l’interface utilisateur présentées à un utilisateur (par exemple, les


entrées de menu disponibles pour un utilisateur).
Les règles d’accès pour les zones de l’application et les composants.

Pour une application côté client, l’autorisation est uniquement utilisée pour déterminer
les options de l’interface utilisateur à afficher. Dans la mesure où les vérifications côté
client peuvent être modifiées ou ignorées par un utilisateur, une application côté client
ne peut pas appliquer les règles d’accès d’autorisation.

Les conventions d’autorisation Razor Pages ne s’appliquent pas aux composants Razor
routables. Si un composant Razor non routable est incorporé dans une page d’une
application Razor Pages, les conventions d’autorisation de la page affectent
indirectement le composant Razor ainsi que le reste du contenu de la page.

7 Notes

Les exemples de code de cet article adoptent les types référence null (NRT) et
l’analyse statique de l’état null du compilateur .NET, qui sont pris en charge dans
ASP.NET Core dans .NET 6 ou une version ultérieure. Lorsque vous ciblez ASP.NET
Core 5.0 ou version antérieure, supprimez la désignation de type nul ( ? ) des
exemples de cet article.

Prise en charge d’Antiforgery


Blazor ajoute Antiforgery Middleware et nécessite une protection anti-falsification du
point de terminaison par défaut.

Le composant AntiforgeryToken affiche un jeton anti-falsification en tant que champ


masqué, et ce composant est automatiquement ajouté aux instances de formulaire
(EditForm). Pour plus d’informations, consultez Vue d’ensemble des formulaires Blazor
ASP.NET Core.
Le service AntiforgeryStateProvider fournit un accès à un jeton anti-falsification associé à
la session active. Injectez le service et appelez sa méthode GetAntiforgeryToken() pour
obtenir le AntiforgeryRequestToken actuel. Pour plus d’informations, consultez Appeler
une API web à partir d’une application Blazor ASP.NET Core.

Blazor stocke les jetons de requête dans l’état du composant, ce qui garantit que les
jetons anti-falsification sont disponibles pour les composants interactifs, même s’ils
n’ont pas accès à la requête.

Authentification
Blazor utilise les mécanismes d’authentification ASP.NET Core existants pour établir
l’identité de l’utilisateur. Le mécanisme exact dépend de la façon dont l’application
Blazor est hébergée, côté serveur ou côté client.

Authentification Blazor côté serveur


Le Blazor rendu de façon interactive côté serveur fonctionne sur une connexion SignalR
avec le client. L’authentification dans les applications basées sur SignalR est gérée au
moment où la connexion est établie. L’authentification peut être basée sur un cookie ou
un autre jeton du porteur, mais l’authentification est gérée via le hub SignalR et
entièrement au sein du circuit.

Le service AuthenticationStateProvider intégré obtient des données d’état


d’authentification du HttpContext.User d’ASP.NET Core. C’est ainsi que l’état
d’authentification s’intègre avec les mécanismes d’authentification ASP.NET Core.

IHttpContextAccessor / HttpContext dans les composants Razor

IHttpContextAccessor doit être évité avec le rendu interactif, car il n’existe pas de
HttpContext valide disponible.

IHttpContextAccessor peut être utilisé pour les composants rendus statiquement sur le
serveur. Toutefois, nous vous recommandons de l’éviter si possible.

HttpContext peut être utilisé comme paramètre en cascade uniquement dans les
composants racines rendus statiquement pour les tâches générales, telles que l’inspection
et la modification d’en-têtes ou d’autres propriétés dans le composant App
( Components/App.razor ). La valeur est toujours null pour le rendu interactif.

C#
[CascadingParameter]
public HttpContext? HttpContext { get; set; }

Pour les scénarios où HttpContext est requis dans les composants interactifs, nous vous
recommandons de transmettre les données via l’état du composant persistant à partir
du serveur. Pour plus d’informations, consultez Autres scénarios de sécurité ASP.NET
Core Blazor côté serveur.

État partagé

Les applications Blazor côté serveur résident dans la mémoire du serveur, et plusieurs
sessions d’application sont hébergées dans le même processus. Pour chaque session
d’application, Blazor démarre un circuit avec sa propre étendue de conteneur d’injection
de dépendances, de sorte que les services délimités sont uniques par session Blazor.

2 Avertissement

Nous ne recommandons pas que les applications sur le même serveur partagent un
état à l’aide des services singleton, sauf si des précautions extrêmes sont prises, car
cela peut introduire des vulnérabilités de sécurité, comme des fuites d’état
utilisateur entre les circuits.

Vous pouvez utiliser des services singleton avec état dans les applications Blazor si elles
sont spécifiquement conçues pour cela. Par exemple, l’utilisation d’un cache de mémoire
singleton est acceptable, car un cache de mémoire nécessite une clé pour accéder à une
entrée donnée. En supposant que les utilisateurs ne contrôlent pas les clés de cache
utilisées avec le cache, l’état stocké dans le cache ne fuit pas entre les circuits.

Pour obtenir des conseils généraux sur la gestion de l’état, consultez Gestion de l’état
BlazorASP.NET Core.

Authentification Blazor côté client


Dans les applications Blazor côté client, les vérifications d’authentification peuvent être
contournées, car le code côté client peut être modifié par des utilisateurs. Cela vaut
également pour toutes les technologies d’application côté client, y compris les
infrastructures d’application monopage JavaScript et les applications natives pour
n’importe quel système d’exploitation.

Ajoutez ce qui suit :


Une référence de package pour le package NuGet
Microsoft.AspNetCore.Components.Authorization .

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET,
consultez les articles figurant sous Installer et gérer des packages dans Flux de
travail de la consommation des packages (documentation NuGet). Vérifiez
les versions du package sur NuGet.org .

L’espace de noms Microsoft.AspNetCore.Components.Authorization au fichier


_Imports.razor de l’application.

Pour gérer l’authentification, l’utilisation du service AuthenticationStateProvider intégré


ou personnalisé est traitée dans les sections suivantes.

Pour plus d’informations, consultez Sécuriser ASP.NET Core Blazor WebAssembly.

Service AuthenticationStateProvider
AuthenticationStateProvider est le service sous-jacent utilisé par le composant
AuthorizeView et les services d’authentification en cascade pour obtenir l’état
d’authentification d’un utilisateur.

Vous n’utilisez généralement pas AuthenticationStateProvider directement. Utilisez le


composant AuthorizeView ou les approches Task<AuthenticationState> décrites plus
loin dans cet article. Le principal inconvénient de l’utilisation directe de
AuthenticationStateProvider est que le composant n’est pas automatiquement averti en
cas de modifications de données d’état de l’authentification sous-jacente.

7 Notes

Pour implémenter un AuthenticationStateProvider personnalisé, consultez


Sécuriser des applications Blazor ASP.NET Core côté serveur.

Le service AuthenticationStateProvider peut fournir les données ClaimsPrincipal de


l’utilisateur actuel, comme indiqué dans l’exemple suivant.

ClaimsPrincipalData.razor :

razor
@page "/claims-principle-data"
@using System.Security.Claims
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>ClaimsPrincipal Data</h1>

<button @onclick="GetClaimsPrincipalData">Get ClaimsPrincipal Data</button>

<p>@authMessage</p>

@if (claims.Count() > 0)


{
<ul>
@foreach (var claim in claims)
{
<li>@claim.Type: @claim.Value</li>
}
</ul>
}

<p>@surname</p>

@code {
private string? authMessage;
private string? surname;
private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

private async Task GetClaimsPrincipalData()


{
var authState = await AuthenticationStateProvider
.GetAuthenticationStateAsync();
var user = authState.User;

if (user.Identity is not null && user.Identity.IsAuthenticated)


{
authMessage = $"{user.Identity.Name} is authenticated.";
claims = user.Claims;
surname = user.FindFirst(c => c.Type ==
ClaimTypes.Surname)?.Value;
}
else
{
authMessage = "The user is NOT authenticated.";
}
}
}

Dans l'exemple précédent :

ClaimsPrincipal.Claims retourne les revendications de l’utilisateur ( claims ) pour


affichage dans l’interface utilisateur.
Ligne qui obtient les appels ClaimsPrincipal.FindAll du nom de l’utilisateur
( surname ) avec un prédicat pour filtrer les revendications de l’utilisateur.

Si user.Identity.IsAuthenticated est true et parce que l’utilisateur est ClaimsPrincipal,


les revendications peuvent être énumérées et l’appartenance aux rôles évaluée.

Pour plus d’informations sur l’injection de dépendances (DI) et les services, consultez
Injection de dépendances ASP.NET Core Blazor et Injection de dépendances dans
ASP.NET Core. Pour plus d’informations sur l’implémentation d’un
AuthenticationStateProvider personnalisé dans des applications Blazor côté serveur,
consultez Sécuriser des applications Blazor ASP.NET Core côté serveur.

Exposer l’état d’authentification comme un


paramètre en cascade
Si les données d’état d’authentification sont requises pour la logique procédurale, par
exemple lors d’une action déclenchée par l’utilisateur, obtenez les données d’état
d’authentification en définissant un paramètre en cascade de type
Task< AuthenticationState > , comme le montre l’exemple suivant.

CascadeAuthState.razor :

razor

@page "/cascade-auth-state"

<h1>Cascade Auth State</h1>

<p>@authMessage</p>

@code {
private string authMessage = "The user is NOT authenticated.";

[CascadingParameter]
private Task<AuthenticationState>? authenticationState { get; set; }

protected override async Task OnInitializedAsync()


{
if (authenticationState is not null)
{
var authState = await authenticationState;
var user = authState?.User;

if (user?.Identity is not null && user.Identity.IsAuthenticated)


{
authMessage = $"{user.Identity.Name} is authenticated.";
}
}
}
}

Si user.Identity.IsAuthenticated est true , les revendications peuvent être énumérées


et l’appartenance aux rôles évaluée.

Configurez le Task< AuthenticationState > paramètre en cascade en utilisant le


AuthorizeRouteView et les services d’état d’authentification en cascade.

Lorsque vous créez une application Blazor à partir de l’un des modèles de projet Blazor
dont l’authentification est activée, l’application contient le AuthorizeRouteView et l’appel
à AddCascadingAuthenticationState présentés dans l’exemple suivant. Une application
Blazor côté client inclut également les inscriptions de service requises. Des informations
supplémentaires sont présentées dans la section Personnaliser le contenu non autorisé
avec le composant Routeur.

razor

<Router ...>
<Found ...>
<AuthorizeRouteView RouteData="routeData"
DefaultLayout="typeof(Layout.MainLayout)" />
...
</Found>
</Router>

Dans le fichier Program , inscrivez les services d’état d’authentification en cascade :

C#

builder.Services.AddCascadingAuthenticationState();

Dans une application Blazor côté client, ajoutez des services pour les options et
l’autorisation au fichier Program :

C#

builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();

Dans une application Blazor côté serveur, les services pour les options et l’autorisation
sont déjà présents. Par conséquent, aucune étape supplémentaire n’est nécessaire.
Autorisation
Une fois qu’un utilisateur est authentifié, les règles d’autorisation sont appliquées pour
contrôler ce que l’utilisateur peut faire.

L’accès est généralement accordé ou refusé selon les conditions suivantes :

Un utilisateur est authentifié (connecté).


Un utilisateur appartient à un rôle.
Un utilisateur a une revendication.
Une stratégie est satisfaite.

Tous ces concepts sont les mêmes que dans une application ASP.NET Core MVC ou
Razor Pages. Pour plus d’informations sur la sécurité d’ASP.NET Core, consultez les
articles sous Sécurité ASP.NET Core et Identity.

AuthorizeView (composant)
Le composant AuthorizeView affiche de manière sélective le contenu d’interface
utilisateur en fonction de l’autorisation dont dispose l’utilisateur. Cette approche est
utile lorsque vous devez uniquement afficher les données de l’utilisateur et que vous
n’avez pas besoin d’utiliser l’identité de l’utilisateur dans la logique procédurale.

Le composant expose une variable context de type AuthenticationState ( @context dans


la syntaxe Razor), que vous pouvez utiliser pour accéder aux informations relatives à
l’utilisateur connecté :

razor

<AuthorizeView>
<p>Hello, @context.User.Identity?.Name!</p>
</AuthorizeView>

Si l’utilisateur n’est pas autorisé, vous pouvez également fournir un contenu différent à
afficher en combinant les paramètres Authorized et NotAuthorized :

razor

<AuthorizeView>
<Authorized>
<p>Hello, @context.User.Identity?.Name!</p>
<p><button @onclick="SecureMethod">Authorized Only Button</button>
</p>
</Authorized>
<NotAuthorized>
<p>You're not authorized.</p>
</NotAuthorized>
</AuthorizeView>

@code {
private void SecureMethod() { ... }
}

Un gestionnaire d’événements par défaut pour un élément autorisé, comme la méthode


SecureMethod pour l’élément <button> dans l’exemple précédent, ne peut être appelé

que par un utilisateur autorisé.

2 Avertissement

Le balisage côté client et les méthodes associées à un AuthorizeView sont


uniquement protégés contre l’affichage et l’exécution dans l’interface utilisateur
affichée dans les applications Blazor côté client. Afin de protéger un contenu
autorisé et des méthodes sécurisées dans Blazor côté client, le contenu est
généralement fourni par un appel d’API web sécurisé et autorisé à une API de
serveur et n’est jamais stocké dans l’application. Pour plus d’informations, consultez
Appeler une API web à partir d’une application ASP.NET Core Blazor et scénarios
de sécurité supplémentaires Blazor WebAssembly ASP.NET Core.

Le contenu de Authorized et de NotAuthorized peut inclure des éléments arbitraires,


comme d’autres composants interactifs.

Les conditions d’autorisation, comme les rôles ou les stratégies qui contrôlent les
options d’interface utilisateur ou d’accès, sont traitées dans la section Autorisation.

Si les conditions d’autorisation ne sont pas spécifiées, AuthorizeView utilise une


stratégie par défaut :

Les utilisateurs authentifiés (connectés) sont autorisés.


Les utilisateurs non authentifiés (déconnectés) ne sont pas autorisés.

Le composant AuthorizeView peut être utilisé dans le composant NavMenu


( Shared/NavMenu.razor ) pour afficher un composant NavLink (NavLink), mais notez que
cette approche supprime uniquement l’élément de liste de la sortie affichée. Elle
n’empêche pas l’utilisateur d’accéder au composant. Implémentez l’autorisation
séparément dans le composant de destination.

Autorisation en fonction du rôle et de la stratégie


Le composant AuthorizeView prend en charge l’autorisation basée sur le rôle et basée
sur les stratégies.

Pour l’autorisation en fonction du rôle, utilisez le paramètre Roles. Dans l’exemple


suivant, l’utilisateur doit avoir une revendication de rôle pour les rôles Admin ou
Superuser :

razor

<AuthorizeView Roles="Admin, Superuser">


<p>You have an 'Admin' or 'Superuser' role claim.</p>
</AuthorizeView>

Pour exiger qu’un utilisateur ait à la fois des revendications de rôle Admin et Superuser ,
imbriquez AuthorizeView composants :

razor

<AuthorizeView Roles="Admin">
<p>User: @context.User</p>
<p>You have the 'Admin' role claim.</p>
<AuthorizeView Roles="Superuser" Context="innerContext">
<p>User: @innerContext.User</p>
<p>You have both 'Admin' and 'Superuser' role claims.</p>
</AuthorizeView>
</AuthorizeView>

Le code précédent établit un Context pour le composant interne AuthorizeView afin


d’éviter une collision de contexte AuthenticationState. Le contexte AuthenticationState
est accessible dans l’extérieur AuthorizeView avec l’approche standard pour accéder au
contexte ( @context.User ). Le contexte est accessible dans l’intérieur AuthorizeView avec
le contexte nommé innerContext ( @innerContext.User ).

Pour plus d’informations, notamment des conseils de configuration, consultez


Autorisation basée sur les rôles dans ASP.NET Core.

Pour l’autorisation basée sur une stratégie, utilisez le paramètre Policy avec une stratégie
unique :

razor

<AuthorizeView Policy="Over21">
<p>You satisfy the 'Over21' policy.</p>
</AuthorizeView>
Pour gérer le cas où l’utilisateur doit satisfaire à une stratégie parmi d’autres, créez une
stratégie qui confirme que l’utilisateur satisfait à d’autres stratégies.

Pour gérer le cas où l’utilisateur doit satisfaire plusieurs stratégies simultanément, suivez
l’une des approches suivantes :

Créez une stratégie pour AuthorizeView qui confirme que l’utilisateur satisfait à
plusieurs autres stratégies.

Imbriquez les stratégies dans plusieurs composants AuthorizeView :

razor

<AuthorizeView Policy="Over21">
<AuthorizeView Policy="LivesInCalifornia">
<p>You satisfy the 'Over21' and 'LivesInCalifornia' policies.
</p>
</AuthorizeView>
</AuthorizeView>

L’autorisation basée sur les revendications est un cas spécial d’autorisation basée sur les
stratégies. Par exemple, vous pouvez définir une stratégie qui impose aux utilisateurs
d’avoir une certaine revendication. Pour plus d’informations, consultez Autorisation
basée sur une stratégie dans ASP.NET Core.

Si ni Roles ni Policy n’est spécifié, AuthorizeView utilise la stratégie par défaut :

Les utilisateurs authentifiés (connectés) sont autorisés.


Les utilisateurs non authentifiés (déconnectés) ne sont pas autorisés.

Étant donné que les comparaisons de chaînes .NET respectent la casse par défaut, les
noms de rôle et de stratégie correspondants respectent également la casse. Par
exemple, Admin ( A majuscule) n’est pas traité comme le même rôle que admin ( a
minuscule).

La casse Pascal est généralement utilisée pour les noms de rôles et de stratégie (par
exemple, BillingAdministrator ), mais son utilisation n’est pas une exigence stricte.
Différents schémas de casse, tels que la case chameau, la casse kebab et la casse
serpent, sont autorisés. L’utilisation d’espaces dans les noms de rôle et de stratégie est
inhabituelle, mais autorisée par l’infrastructure. Par exemple, billing administrator est
un format de rôle ou de nom de stratégie inhabituel dans les applications .NET, mais il
s’agit d’un nom de rôle ou de stratégie valide.

Contenu affiché lors de l’authentification asynchrone


Blazor permet de déterminer l’état d’authentification de façon asynchrone. Le scénario
principal de cette approche réside dans les applications Blazor côté client qui créent une
requête d’authentification sur un point de terminaison externe.

Lorsque l’authentification est en cours, AuthorizeView n’affiche aucun contenu par


défaut. Pour afficher le contenu lors de l’authentification, affectez le contenu au
paramètre Authorizing :

razor

<AuthorizeView>
<Authorized>
<p>Hello, @context.User.Identity?.Name!</p>
</Authorized>
<Authorizing>
<p>You can only see this content while authentication is in
progress.</p>
</Authorizing>
</AuthorizeView>

Cette approche n’est normalement pas applicable aux applications Blazor côté serveur.
Les applications Blazor côté serveur connaissent l’état de l’authentification dès que l’état
est établi. Un contenu Authorizing peut être fourni dans le composant d’une application
AuthorizeView, mais le contenu n’est jamais affiché.

Attribut [Authorize]
L’[Authorize]attribut est disponible dans les composantsRazor :

razor

@page "/"
@attribute [Authorize]

You can only see this if you're signed in.

) Important

Utilisez uniquement [Authorize] sur des composants @page auxquels l’accès se fait
via le routeur Blazor. L’autorisation est effectuée uniquement en tant qu’aspect du
routage et pas pour les composants enfants rendus dans une page. Pour autoriser
l’affichage d’éléments spécifiques dans une page, utilisez AuthorizeView à la place.
L’attribut [Authorize] prend aussi en charge l’autorisation basée sur les rôles ou sur une
stratégie. Pour l’autorisation en fonction du rôle, utilisez le paramètre Roles :

razor

@page "/"
@attribute [Authorize(Roles = "Admin, Superuser")]

<p>You can only see this if you're in the 'Admin' or 'Superuser' role.</p>

Pour l’autorisation en fonction des stratégies, utilisez le paramètre Policy :

razor

@page "/"
@attribute [Authorize(Policy = "Over21")]

<p>You can only see this if you satisfy the 'Over21' policy.</p>

Si ni Roles ni Policy n’est spécifié, [Authorize] utilise la stratégie par défaut :

Les utilisateurs authentifiés (connectés) sont autorisés.


Les utilisateurs non authentifiés (déconnectés) ne sont pas autorisés.

Lorsque l’utilisateur n’est pas autorisé et si l’application ne personnalise pas le contenu


non autorisé avec le composant Routeur, l’infrastructure affiche automatiquement le
message de secours suivant :

HTML

Not authorized.

Autorisation des ressources


Pour autoriser des utilisateurs pour les ressources, transmettez les données de route de
la requête au paramètre Resource de AuthorizeRouteView.

Dans le contenu Router.Found d’une route demandée :

razor

<AuthorizeRouteView Resource="routeData" RouteData="routeData"


DefaultLayout="typeof(MainLayout)" />
Pour plus d’informations sur la façon dont les données d’état d’autorisation sont
transmises et utilisées dans la logique procédurale, consultez la section Exposer l’état
d’authentification sous la forme d’un paramètre en cascade.

Quand AuthorizeRouteView reçoit les données de route pour la ressource, les stratégies
d’autorisation ont accès à RouteData.PageType et RouteData.RouteValues, ce qui permet
à la logique personnalisée de prendre des décisions d’autorisation.

Dans l’exemple suivant, une stratégie EditUser est créée dans AuthorizationOptions
pour la configuration du service d’autorisation de l’application (AddAuthorizationCore)
avec la logique suivante :

Déterminer s’il existe une valeur de route avec la clé id . Si la clé existe, la valeur de
route est stockée dans value .
Dans une variable nommée id , stocker value sous forme de chaîne ou définir une
valeur de chaîne vide ( string.Empty ).
Si id n’est pas une chaîne vide, déclarer que la stratégie est satisfaite (retourner
true ) si la valeur de la chaîne commence par EMP . Sinon, déclarer que la stratégie

échoue (retourner false ).

Dans le fichier Program :

Ajouter des espaces de noms pour Microsoft.AspNetCore.Components et


System.Linq :

C#

using Microsoft.AspNetCore.Components;
using System.Linq;

Ajouter la stratégie :

C#

options.AddPolicy("EditUser", policy =>


policy.RequireAssertion(context =>
{
if (context.Resource is RouteData rd)
{
var routeValue = rd.RouteValues.TryGetValue("id", out var
value);
var id = Convert.ToString(value,
System.Globalization.CultureInfo.InvariantCulture) ??
string.Empty;

if (!string.IsNullOrEmpty(id))
{
return id.StartsWith("EMP",
StringComparison.InvariantCulture);
}
}

return false;
})
);

L’exemple précédent est une stratégie d’autorisation simplifiée à l’excès, simplement


destinée à illustrer le concept à partir d’un exemple pratique. Pour plus d’informations
sur la création et la configuration de stratégies d’autorisation, consultez Autorisation
basée sur une stratégie dans ASP.NET Core.

Dans le composant EditUser suivant, la ressource au niveau de /users/{id}/edit


possède un paramètre de route pour l’identificateur de l’utilisateur ( {id} ). Le
composant utilise la stratégie d’autorisation EditUser précédente pour déterminer si la
valeur de route de id commence par EMP . Si id commence par EMP , la stratégie aboutit
et l’accès au composant est autorisé. Si id commence par une autre valeur que EMP ou
si id est une chaîne vide, la stratégie échoue et le composant ne se charge pas.

EditUser.razor :

razor

@page "/users/{id}/edit"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "EditUser")]

<h1>Edit User</h1>

<p>The "EditUser" policy is satisfied! <code>Id</code> starts with 'EMP'.


</p>

@code {
[Parameter]
public string? Id { get; set; }
}

Personnaliser le contenu non autorisé avec le


composant Router
Le composant Router, conjointement avec le composant AuthorizeRouteView, permet à
l’application de spécifier du contenu personnalisé si :
L’utilisateur ne répond pas à une condition [Authorize] appliquée au composant.
Le balisage de l’élément <NotAuthorized> est affiché. L’attribut [Authorize] est
décrit dans la section Attribut [Authorize].
L’autorisation asynchrone est en cours, ce qui signifie généralement que le
processus d’authentification de l’utilisateur est en cours. Le balisage de l’élément
<Authorizing> est affiché.

razor

<Router ...>
<Found ...>
<AuthorizeRouteView ...>
<NotAuthorized>
...
</NotAuthorized>
<Authorizing>
...
</Authorizing>
</AuthorizeRouteView>
</Found>
</Router>

Le contenu de Authorized et de NotAuthorized peut inclure des éléments arbitraires,


comme d’autres composants interactifs.

7 Notes

L’élément précédent nécessite l’inscription des services d’état d’authentification en


cascade dans le fichier Program de l’application :

C#

builder.Services.AddCascadingAuthenticationState();

Si le contenu NotAuthorized n’est pas spécifiée, le AuthorizeRouteView utilise le


message de remplacement suivant :

HTML

Not authorized.

Une application créée à partir du modèle de projet Blazor WebAssembly dont


l’authentification est activée comprend un composant RedirectToLogin qui est
positionné dans le contenu <NotAuthorized> du composant Router. Lorsqu’un utilisateur
n’est pas authentifié ( context.User.Identity?.IsAuthenticated != true ), le composant
RedirectToLogin redirige le navigateur vers le point de terminaison

authentication/login pour l’authentification. L’utilisateur est renvoyé à l’URL demandée

après s’être authentifié auprès du fournisseur d’identité.

Logique procédurale
Si l’application est appelée à vérifier les règles d’autorisation dans le cadre de la logique
procédurale, utilisez un paramètre en cascade de type Task< AuthenticationState > pour
obtenir le ClaimsPrincipal de l’utilisateur. Task< AuthenticationState > peut être combiné
avec d’autres services, comme IAuthorizationService , pour évaluer les stratégies.

Dans l’exemple suivant :

Le user.Identity.IsAuthenticated exécute le code pour les utilisateurs authentifiés


(connectés).
Le user.IsInRole("admin") exécute le code pour les utilisateurs dans le rôle
« Admin ».
Le (await AuthorizationService.AuthorizeAsync(user, "content-
editor")).Succeeded exécute le code pour les utilisateurs respectant la stratégie

« éditeur de contenu ».

Une application Blazor côté serveur inclut les espaces de noms appropriés par défaut
lorsqu’elle est créée à partir du modèle de projet. Dans une application Blazor côté
client, confirmez la présence des espaces de noms Microsoft.AspNetCore.Authorization
et Microsoft.AspNetCore.Components.Authorization dans le composant ou dans le
fichier de l’application _Imports.razor :

razor

@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization

ProceduralLogic.razor :

razor

@page "/procedural-logic"
@inject IAuthorizationService AuthorizationService

<h1>Procedural Logic Example</h1>

<button @onclick="@DoSomething">Do something important</button>


@code {
[CascadingParameter]
private Task<AuthenticationState>? authenticationState { get; set; }

private async Task DoSomething()


{
if (authenticationState is not null)
{
var authState = await authenticationState;
var user = authState?.User;

if (user is not null)


{
if (user.Identity is not null &&
user.Identity.IsAuthenticated)
{
// ...
}

if (user.IsInRole("Admin"))
{
// ...
}

if ((await AuthorizationService.AuthorizeAsync(user,
"content-editor"))
.Succeeded)
{
// ...
}
}
}
}
}

Résoudre les erreurs


Erreurs courantes :

L’autorisation exige un paramètre en cascade de type Task<AuthenticationState> .


Envisagez d’utiliser CascadingAuthenticationState pour le fournir.

La valeur null est reçue pour authenticationStateTask

Il est probable que le projet n’a pas été créé à l’aide d’un modèle Blazor côté serveur
dont l’authentification est activée.

Dans .NET 7 ou versions antérieures, enveloppez un <CascadingAuthenticationState>


dans certaines parties de l’arborescence de l’interface utilisateur, par exemple dans le
routeur Blazor :

razor

<CascadingAuthenticationState>
<Router ...>
...
</Router>
</CascadingAuthenticationState>

Dans .NET 8 ou versions ultérieures, n’utilisez pas le composant


CascadingAuthenticationState :

diff

- <CascadingAuthenticationState>
<Router ...>
...
</Router>
- </CascadingAuthenticationState>

Ajoutez à la place des services d’état d’authentification en cascade à la collection de


services dans le fichier Program :

C#

builder.Services.AddCascadingAuthenticationState();

Le composant CascadingAuthenticationState (.NET 7 ou versions antérieures) ou les


services fournis par AddCascadingAuthenticationState (.NET 8 ou versions ultérieures)
fournissent le paramètre en cascade Task< AuthenticationState > qui reçoit à son tour du
service d’injection de dépendances AuthenticationStateProvider sous-jacent.

Ressources supplémentaires
Documentation sur la plateforme d’identités Microsoft
Vue d’ensemble
Protocoles OAuth 2.0 et OpenID Connect sur la Plateforme d’identités Microsoft
Plateforme d’identités Microsoft et flux de code d’autorisation OAuth
Jetons d’ID de la plateforme d’identités Microsoft
Jetons d’accès de la plateforme d’identités Microsoft
Thèmes de sécurité ASP.NET Core
Configurer l’authentification Windows dans ASP.NET Core
Créer une version personnalisée de la bibliothèque JavaScript Authentication.MSAL
Awesome Blazor : Authentification Exemples de liens de la communauté
Authentification et autorisation avec ASP.NET Core Blazor Hybrid

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Applications Blazor sécurisées côté
serveur ASP.NET Core
Article • 23/01/2024

Cet article explique comment sécuriser les applications Blazor côté serveur en tant
qu'applications ASP.NET Core.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration consiste à télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne figurent pas dans les exemples
d’applications, mais nous nous employons à transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Les applications Blazor côté serveur sont configurées pour la sécurité de la même
manière que les applications ASP.NET Core. Pour plus d’informations, consultez les
articles relevant des rubriques de sécurité ASP.NET Core.

Le contexte d'authentification n'est établi qu'au démarrage de l'application, c'est-à-dire


lorsque l'application se connecte pour la première fois au WebSocket. Le contexte
d’authentification est conservé pendant toute la durée de vie du circuit. Les applications
revalident périodiquement l'état d'authentification de l'utilisateur, actuellement toutes
les 30 minutes par défaut.

Si l’application doit capturer des utilisateurs pour des services personnalisés ou réagir
aux mises à jour de l’utilisateur, consultez Scénarios de sécurité supplémentaires
ASP.NET Core Blazor côté serveur.

Blazor diffère d’une application web rendue sur serveur traditionnelle qui effectue de
nouvelles requêtes HTTP avec des cookie sur chaque navigation de page.
L’authentification est vérifiée pendant les événements de navigation. Toutefois, les
cookie ne sont pas impliqués. Les Cookie sont envoyés uniquement lors de l’envoi d’une
requête HTTP à un serveur, ce qui n’est pas le cas lorsque l’utilisateur navigue dans une
application Blazor. Pendant la navigation, l’état d’authentification de l’utilisateur est
vérifié dans le circuit Blazor, que vous pouvez mettre à jour à tout moment sur le serveur
à l’aide de l’RevalidatingAuthenticationStateProviderabstraction.

) Important

La mise en œuvre d'une personnalisation NavigationManager pour obtenir la


validation de l'authentification pendant la navigation n'est pas recommandée. Si
l’application doit exécuter une logique d’état d’authentification personnalisée
pendant la navigation, utilisez un personnalisé AuthenticationStateProvider.

7 Notes

Les exemples de code de cet article adoptent les types référence null (NRT) et
l'analyse statique de l'état null du compilateur .NET, qui sont pris en charge dans
ASP.NET Core 6.0 ou une version ultérieure. Lorsque vous ciblez ASP.NET Core 5.0
ou version antérieure, supprimez la désignation de type null ( ? ) des exemples de
cet article.

Modèle de projet
Créez une nouvelle application côté serveur Blazor en suivant les instructions de la
section Outils pour ASP.NET Core Blazor.

Visual Studio

Après avoir choisi le modèle d'application côté serveur et configuré le projet,


sélectionnez l'authentification de l'application sous Type d'authentification :

Aucun (valeur par défaut) : aucune authentification.


Comptes individuels : les comptes d’utilisateur sont stockés dans l’application
à l’aide d’ASP.NET Core Identity.

Interface utilisateur BlazorIdentity (comptes


individuels)
Blazor prend en charge la génération d’une interface utilisateur complète basée sur
Blazor de Identity lorsque vous choisissez l’option d’authentification Comptes individuels.

Le modèle Web App Blazor échafaude le code Identity d’une base de données SQL
Server. La version en ligne de commande utilise SQLite par défaut et inclut une base de
données SQLite pour Identity.

Le modèle gère les éléments suivants :

Ajoute des composants IdentityRazor et une logique associée pour les tâches
d’authentification de routine, telles que la connexion et la déconnexion des
utilisateurs.
Les composants Identity prennent également en charge les fonctionnalités
avancées Identity, telles que la confirmation de compte et la récupération de
mot de passe et l’authentification multifacteur à l’aide d’une application tierce.
Les scénarios de rendu côté serveur interactif (SSR interactif) et de rendu côté
client (CSR) sont pris en charge.
Ajoute les packages et dépendances associés à Identity.
Référence les packages Identity dans _Imports.razor .
Crée une classe Identity d’utilisateur personnalisée ( ApplicationUser ).
Crée et inscrit un contexte de base de données EF Core ( ApplicationDbContext ).
Configure le routage pour les points de terminaison Identity intégrés.
Inclut la validation Identity et la logique métier.

Pour inspecter les composants Identity du framework Blazor, accédez-y dans les dossiers
Pages et Shared du dossier Account dans le modèle de projet Application web Blazor

(source de référence) .

Lorsque vous choisissez les modes de rendu Interactive WebAssembly ou Interactive


Auro, le serveur gère toutes les requêtes d’authentification et d’autorisation, et les
composants Identity restent sur le serveur dans le projet principal de l’application web
Blazor. Le modèle de projet inclut une classe PersistentAuthenticationStateProvider
(source de référence) dans le projet .Client pour synchroniser l’état
d’authentification de l’utilisateur entre le serveur et le navigateur. La classe est une
implémentation personnalisée de AuthenticationStateProvider. Le fournisseur utilise la
classe PersistentComponentState pour prédéfinir l’état d’authentification et le conserver
sur la page.

Dans le projet principal d’une application web Blazor, le fournisseur d’état


d’authentification est nommé IdentityRevalidatingAuthenticationStateProvider (source
de référence) (solutions d’interactivité de serveur uniquement) ou
PersistingRevalidatingAuthenticationStateProvider (source de référence) (solutions
d’interactivité WebAssembly ou automatique).

Pour plus d’informations sur la persistance de l’état pré-rendu, consultez Pré-rendre les
composants Razor ASP.NET Core.

Pour plus d’informations sur l’interface utilisateur BlazorIdentity et des conseils sur
l’intégration de connexions externes via des sites web sociaux, consultez Nouveautés de
l’identité dans .NET 8 .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .
Gérer l’état de l’authentification dans les Web
Apps Blazor
Cette section s’applique aux Web Apps Blazor qui adoptent :

Rendu côté serveur interactif (SSR interactif) et rendu côté client (CSR).
Rendu côté client (CSR).

Un fournisseur d’état de l’authentification côté client est utilisé uniquement dans Blazor
et n’est pas intégré au système d’authentification ASP.NET Core. Lors de la prérendu,
Blazor respecte les métadonnées définies sur la page et utilise le système
d’authentification ASP.NET Core pour déterminer si l’utilisateur est authentifié. Lorsqu’un
utilisateur va d’une page à une autre, un fournisseur d’authentification côté client est
utilisé. Lorsque l’utilisateur actualise la page (actualisation complète de la page), le
fournisseur d’état de l’authentification côté client n’est pas impliqué dans la décision
d’authentification sur le serveur. L’état de l’utilisateur n’étant pas conservé par le
serveur, tout état d’authentification géré côté client est perdu.

Pour résoudre ce problème, la meilleure approche consiste à effectuer l’authentification


dans le système d’authentification ASP.NET Core. Le fournisseur d’état de
l’authentification côté client ne se charge que de refléter l’état d’authentification de
l’utilisateur. Les exemples d’utilisation des fournisseurs d’état d’authentification sont
illustrés par le modèle de projet Web App Blazor :

PersistingRevalidatingAuthenticationStateProvider (source de référence) : Pour


Web Apps Blazor qui adoptent le rendu côté serveur interactif (SSR interactif) et le
rendu côté client (CSR). Il s’agit d’un AuthenticationStateProvider côté serveur qui
revalide l’empreinte de sécurité de l’utilisateur connecté toutes les 30 minutes
lorsqu’un circuit interactif est connecté. Il utilise également le service d’état du
composant persistant pour transmettre l’état d’authentification au client. Ce
dernier est ensuite fixe pour la durée de vie du rendu côté client (CSR).

PersistingServerAuthenticationStateProvider (source de référence) : Pour Web


Apps Blazor qui adoptent uniquement le rendu côté client (CSR). Il s’agit d’un
AuthenticationStateProvider côté serveur qui utilise le service d’état du composant
persistant pour transmettre l’état d’authentification au client. Ce dernier est ensuite
fixe pour la durée de vie du rendu côté client (CSR).

PersistentAuthenticationStateProvider (source de référence) : Pour Web Apps


Blazor qui adoptent le rendu côté client (CSR). Il s’agit d’un
AuthenticationStateProvider côté client qui détermine l’état d’authentification de
l’utilisateur en recherchant les données persistantes dans la page, lorsqu’elles ont
été rendues sur le serveur. Cet état d’authentification est fixe pour la durée de vie
du rendu côté client (CSR). Si l’utilisateur doit se connecter ou se déconnecter, une
actualisation complète de la page est nécessaire. Cela fournit uniquement un nom
d’utilisateur et un e-mail à des fins d’affichage. Il n’inclut pas les jetons qui
s’authentifient auprès du serveur lors de l’exécution de requêtes ultérieures. Ces
dernières sont gérées séparément à l’aide d’un cookie, inclus dans les requêtes
HttpClient envoyées au serveur.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Revendications et jetons supplémentaires en


provenance de fournisseurs externes
Pour stocker des revendications supplémentaires en provenance de fournisseurs
externes, consultez Conserver les revendications et les jetons supplémentaires de
fournisseurs externes dans ASP.NET Core.

Azure App Service sur Linux avec Identity


Server
Spécifiez explicitement l’émetteur quand le déploiement a pour cible Azure App Service
sur Linux avec Identity Server. Pour plus d’informations, consultez Utiliser Identity pour
sécuriser un back-end d’API web pour les SPA.

Implémenter un AuthenticationStateProvider
personnalisé
Si l’application nécessite un fournisseur personnalisé, implémentez
AuthenticationStateProvider et remplacez GetAuthenticationStateAsync.
Dans l’exemple suivant, tous les utilisateurs sont authentifiés avec le nom d’utilisateur
mrfibuli .

CustomAuthenticationStateProvider.cs :

C#

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class CustomAuthenticationStateProvider : AuthenticationStateProvider


{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "mrfibuli"),
}, "Custom Authentication");

var user = new ClaimsPrincipal(identity);

return Task.FromResult(new AuthenticationState(user));


}
}

Le service CustomAuthenticationStateProvider est inscrit dans le ficher Program :

C#

using Microsoft.AspNetCore.Components.Authorization;

...

builder.Services.AddScoped<AuthenticationStateProvider,
CustomAuthenticationStateProvider>();

Confirmez ou ajoutez un AuthorizeRouteView au composant Router.

Dans le composant Routes ( Components/Routes.razor ) :

razor

<Router ...>
<Found ...>
<AuthorizeRouteView RouteData="@routeData"
DefaultLayout="@typeof(Layout.MainLayout)" />
...
</Found>
</Router>

Ajoutez des services d’état d’authentification en cascade à la collection de services dans


le fichier Program :

C#

builder.Services.AddCascadingAuthenticationState();

7 Notes

Lorsque vous créez une application Blazor à partir de l'un des modèles de projet
Blazor avec l'authentification activée, l'application inclut AuthorizeRouteView et
appelle AddCascadingAuthenticationState. Pour plus d’informations, consultez
Authentification et autorisation Blazor ASP.NET Core avec des informations
supplémentaires présentées dans la section Personnaliser le contenu non autorisé
avec le composant Routeur de l’article.

Un AuthorizeView illustre le nom de l’utilisateur authentifié dans n’importe quel


composant :

razor

<AuthorizeView>
<Authorized>
<p>Hello, @context.User.Identity?.Name!</p>
</Authorized>
<NotAuthorized>
<p>You're not authorized.</p>
</NotAuthorized>
</AuthorizeView>

Pour obtenir des conseils sur l’utilisation de AuthorizeView, consultez Authentification et


autorisation Blazor ASP.NET Core.

Notification sur les changements d’état de


l’authentification
Un personnalisé AuthenticationStateProvider peut faire appel à
NotifyAuthenticationStateChanged sur la classe de base AuthenticationStateProvider
pour informer les consommateurs de la modification de l’état d’authentification en vue
d’un nouveau rendu.

L’exemple suivant est basé sur l’implémentation d’une personnalisation


AuthenticationStateProvider en suivant les instructions de la section Implémenter une
personnalisation AuthenticationStateProvider.

L’implémentation suivante CustomAuthenticationStateProvider expose une méthode


personnalisée, AuthenticateUser , pour connecter un utilisateur et informer les
consommateurs du changement d’état d’authentification.

CustomAuthenticationStateProvider.cs :

C#

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class CustomAuthenticationStateProvider : AuthenticationStateProvider


{
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var identity = new ClaimsIdentity();
var user = new ClaimsPrincipal(identity);

return Task.FromResult(new AuthenticationState(user));


}

public void AuthenticateUser(string userIdentifier)


{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, userIdentifier),
}, "Custom Authentication");

var user = new ClaimsPrincipal(identity);

NotifyAuthenticationStateChanged(
Task.FromResult(new AuthenticationState(user)));
}
}

Dans un composant :

Injection du code AuthenticationStateProvider.


Ajoutez un champ pour contenir l’identificateur d’utilisateur.
Ajoutez un bouton et une méthode pour caster le AuthenticationStateProvider vers
CustomAuthenticationStateProvider et appeler AuthenticateUser avec
l’identificateur d’utilisateur.

razor

@inject AuthenticationStateProvider AuthenticationStateProvider

<input @bind="userIdentifier" />


<button @onclick="SignIn">Sign in</button>

<AuthorizeView>
<Authorized>
<p>Hello, @context.User.Identity?.Name!</p>
</Authorized>
<NotAuthorized>
<p>You're not authorized.</p>
</NotAuthorized>
</AuthorizeView>

@code {
public string userIdentifier = string.Empty;

private void SignIn()


{
((CustomAuthenticationStateProvider)AuthenticationStateProvider)
.AuthenticateUser(userIdentifier);
}
}

L’approche précédente peut être améliorée pour déclencher des notifications de


changements d’état d’authentification via un service personnalisé. Les éléments suivants
AuthenticationService conservent le principal de revendications de l’utilisateur actuel

dans un champ de stockage ( currentUser ) avec un événement ( UserChanged ) auquel le


AuthenticationStateProvider peut s’abonner, où l’événement appelle
NotifyAuthenticationStateChanged. Avec la configuration supplémentaire décrite plus
loin dans cette section, le AuthenticationService peut être injecté dans un composant
avec une logique qui définit le CurrentUser pour déclencher l’événement UserChanged .

C#

using System.Security.Claims;

public class AuthenticationService


{
public event Action<ClaimsPrincipal>? UserChanged;
private ClaimsPrincipal? currentUser;

public ClaimsPrincipal CurrentUser


{
get { return currentUser ?? new(); }
set
{
currentUser = value;

if (UserChanged is not null)


{
UserChanged(currentUser);
}
}
}
}

Dans le fichier Program , enregistrez le AuthenticationService dans le conteneur


d'injection de dépendances :

C#

builder.Services.AddScoped<AuthenticationService>();

Les éléments suivants CustomAuthenticationStateProvider s’abonnent à l’événement


AuthenticationService.UserChanged . GetAuthenticationStateAsync retourne l’état

d’authentification de l’utilisateur. Initialement, l’état d’authentification est basé sur la


valeur de AuthenticationService.CurrentUser . En cas de modification de l’utilisateur, un
nouvel état d’authentification est créé avec le nouvel utilisateur ( new
AuthenticationState(newUser) ) pour les appels à GetAuthenticationStateAsync :

C#

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;

public class CustomAuthenticationStateProvider : AuthenticationStateProvider


{
private AuthenticationState authenticationState;

public CustomAuthenticationStateProvider(AuthenticationService service)


{
authenticationState = new AuthenticationState(service.CurrentUser);

service.UserChanged += (newUser) =>


{
authenticationState = new AuthenticationState(newUser);

NotifyAuthenticationStateChanged(
Task.FromResult(new AuthenticationState(newUser)));
};
}

public override Task<AuthenticationState> GetAuthenticationStateAsync()


=>
Task.FromResult(authenticationState);
}

La méthode SignIn du composant suivant crée un principal de revendications pour


l’identificateur de l’utilisateur à définir sur AuthenticationService.CurrentUser :

razor

@inject AuthenticationService AuthenticationService

<input @bind="userIdentifier" />


<button @onclick="SignIn">Sign in</button>

<AuthorizeView>
<Authorized>
<p>Hello, @context.User.Identity?.Name!</p>
</Authorized>
<NotAuthorized>
<p>You're not authorized.</p>
</NotAuthorized>
</AuthorizeView>

@code {
public string userIdentifier = string.Empty;

private void SignIn()


{
var currentUser = AuthenticationService.CurrentUser;

var identity = new ClaimsIdentity(


new[]
{
new Claim(ClaimTypes.Name, userIdentifier),
},
"Custom Authentication");

var newUser = new ClaimsPrincipal(identity);

AuthenticationService.CurrentUser = newUser;
}
}

Injectez AuthenticationStateProvider pour les


services liés à un composant
N’essayez pas de résoudre AuthenticationStateProvider dans une étendue
personnalisée, car cela entraîne la création d’une nouvelle instance du
AuthenticationStateProvider qui n’est pas correctement initialisée.

Pour accéder au AuthenticationStateProvider dans un service délimité à un composant,


injectez le AuthenticationStateProvider avec la @inject directive ou l’[Inject]attribut et
passez-le au service en tant que paramètre. Cette approche garantit que l’instance
correcte et initialisée du AuthenticationStateProvider est utilisée pour chaque instance
d’application utilisateur.

ExampleService.cs :

C#

public class ExampleService


{
public async Task<string> ExampleMethod(AuthenticationStateProvider
authStateProvider)
{
var authState = await
authStateProvider.GetAuthenticationStateAsync();
var user = authState.User;

if (user.Identity is not null && user.Identity.IsAuthenticated)


{
return $"{user.Identity.Name} is authenticated.";
}
else
{
return "The user is NOT authenticated.";
}
}
}

Inscrivez le service comme délimité. Dans une application Blazor côté serveur, les
services étendus ont une durée de vie égale à la durée du circuit de connexion client.

Dans le fichier Program :

C#

builder.Services.AddScoped<ExampleService>();

Dans le composant InjectAuthStateProvider suivant :

Le composant hérite OwningComponentBase.


Le AuthenticationStateProvider est injecté et passé à
ExampleService.ExampleMethod .
ExampleService est résolu avec OwningComponentBase.ScopedServices et

GetRequiredService, qui renvoie l’instance correcte et initialisée de ExampleService


qui existe pendant la durée de vie du circuit de l’utilisateur.

InjectAuthStateProvider.razor :

razor

@page "/inject-auth-state-provider"
@inherits OwningComponentBase
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>Inject <code>AuthenticationStateProvider</code> Example</h1>

<p>@message</p>

@code {
private string? message;
private ExampleService? ExampleService { get; set; }

protected override async Task OnInitializedAsync()


{
ExampleService = ScopedServices.GetRequiredService<ExampleService>
();

message = await
ExampleService.ExampleMethod(AuthenticationStateProvider);
}
}

Pour plus d’informations, consultez les conseils sur OwningComponentBase dans


l’injection de dépendances Blazor dans ASP.NET Core.

Affichage de contenu non autorisé lors du


prérendu avec un fichier personnalisé
AuthenticationStateProvider
Pour éviter d'afficher du contenu non autorisé lors du prérendu avec un fichier
personnalisé AuthenticationStateProvider, adoptez l'une des approches suivantes :

Implémentation de IHostEnvironmentAuthenticationStateProvider pour que la


personnalisation de AuthenticationStateProvider prenne en charge le prérendu :
pour un exemple d'implémentation de
IHostEnvironmentAuthenticationStateProvider, consultez l' Blazor implémentation
du framework ServerAuthenticationStateProvider dans
ServerAuthenticationStateProvider.cs (source de référence) .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Désactiver le prérendu : indiquez le mode de rendu avec le paramètre prerender


défini sur false au niveau le plus élevé dans la hiérarchie des composants de
l’application qui n’est pas un composant racine.

7 Notes

Rendre un composant racine interactif, comme le composant App , n’est pas


pris en charge. Par conséquent, le prérendu ne peut pas être désactivé
directement par le composant App .

Pour les applications basées sur le modèle de projet d’application web Blazor, le
prérendu est habituellement désactivé où le composant Routes est utilisé dans le
composant App ( Components/App.razor ) :

razor

<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)"


/>

Désactivez également le pré-rendu pour le composant HeadOutlet :

razor

<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender:


false)" />

Authentifier l'utilisateur sur le serveur avant le démarrage de l'application : pour


adopter cette approche, l'application doit répondre à la requête initiale d'un
utilisateur avec la page de connexion basée sur Identity- ou afficher et empêcher
toute demande adressée aux points de terminaison Blazor jusqu'à ce qu'ils soient
authentifiés. Pour plus d’informations, consultez Créer une application ASP.NET
Core avec des données utilisateur protégées par autorisation. Après
l'authentification, le contenu non autorisé dans les composants Razor pré-rendus
n'est affiché que lorsque l'utilisateur n'est réellement pas autorisé à afficher le
contenu.

Gestion de l'état des utilisateurs


En dépit du mot « état » dans le nom, AuthenticationStateProvider n’est pas destiné au
stockage de l’état général de l’utilisateur. AuthenticationStateProvider indique
uniquement l’état d’authentification de l’utilisateur par rapport à l’application, s’il est
connecté à l’application et sous quel nom il est connecté.

L'authentification utilise la même authentification Identity ASP.NET Core que les


applications Pages Razor et MVC. L’état utilisateur stocké pour ASP.NET Core Identity
transite vers Blazor sans ajouter de code supplémentaire à l’application. Suivez les
instructions fournies dans les articles et tutoriels ASP.NET Core Identity pour que les
fonctionnalités Identity prennent effet dans les parties Blazor de l’application.

Pour obtenir des conseils sur la gestion générale de l’état en dehors de ASP.NET Core
Identity, consultez Gestion de l’état Blazor ASP.NET Core.

Abstractions de sécurité supplémentaires


Deux abstractions supplémentaires participent à la gestion de l’état d’authentification :

ServerAuthenticationStateProvider (source de référence ) :


unAuthenticationStateProvider utilisé par l’infrastructure Blazor pour obtenir l’état
d’authentification du serveur.

RevalidatingServerAuthenticationStateProvider (source de référence ) : une classe


de base pour les services AuthenticationStateProvider utilisés par l’infrastructure
Blazor pour recevoir un état d’authentification de l’environnement hôte et le
revalider à intervalles réguliers.

L'intervalle de revalidation par défaut de 30 minutes peut être ajusté dans


RevalidatingIdentityAuthenticationStateProvider
(Areas/Identity/RevalidatingIdentityAuthenticationStateProvider.cs) . L’exemple
suivant raccourcit l’intervalle à 20 minutes :
C#

protected override TimeSpan RevalidationInterval =>


TimeSpan.FromMinutes(20);

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Durée de validité de l’URL de redirection


temporaire
Cette section s’applique aux applications web Blazor.

Utilisez l’option
RazorComponentsServiceOptions.TemporaryRedirectionUrlValidityDuration pour obtenir
ou définir la durée de vie de la validité de la protection des données pour les URL de
redirection temporaires émises par le rendu côté serveur de Blazor. Celles-ci sont utilisés
temporairement : la durée de vie doit donc être suffisamment longue pour qu’un client
reçoive l’URL et commence la navigation vers celle-ci. Cependant, elle doit aussi être
suffisamment longue pour tenir compte des décalages d’horloge entre les serveurs. La
valeur par défaut est de cinq minutes.

Dans l’exemple suivant, la valeur est étendue à sept minutes :

C#

builder.Services.AddRazorComponents(options =>
options.TemporaryRedirectionUrlValidityDuration =
TimeSpan.FromMinutes(7));

Ressources supplémentaires
Démarrage rapide : Ajouter la connexion avec Microsoft à une application web
ASP.NET Core
Démarrage rapide : Protéger une API Web ASP.NET Core avec la plateforme
d'identités Microsoft
Configurer ASP.NET Core pour une utilisation avec des serveurs proxy et des
équilibreurs de charge. Cet article fournit des conseils sur les aspects suivants :
Utilisation du middleware Forwarded Headers pour préserver les informations
de schéma HTTPS sur les serveurs proxy et les réseaux internes.
Autres scénarios et cas d’usage, notamment la configuration manuelle de
schéma, la modification des chemins de requêtes pour assurer le bon routage
des requêtes et le transfert du schéma de requête pour les proxys inverses Linux
et non IIS.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Conseils d’atténuation des menaces
pour le rendu statique côté serveur de
Blazor ASP.NET Core
Article • 09/02/2024

Cet article explique les considérations de sécurité que les développeurs doivent prendre
en compte lors du développement d’applications web Blazor avec un rendu statique
côté serveur.

Blazor combine trois modèles différents en un pour l’écriture d’applications web


interactives. Le rendu côté serveur traditionnel, un modèle de type requête/réponse
basé sur HTTP. Le rendu interactif côté serveur, un modèle de rendu basé sur SignalR.
Enfin, le rendu côté client, un modèle de rendu basé sur WebAssembly.

Toutes les considérations de sécurité générales définies pour les modes de rendu
interactif s’appliquent à Web Apps Blazor lorsqu’il existe un rendu de composants
interactifs dans l’un des modes de rendu pris en charge. Les sections suivantes
expliquent les considérations de sécurité spécifiques au rendu côté serveur non
interactif dans les applications web Blazor et les aspects spécifiques qui s’appliquent
lorsque les modes de rendu interagissent entre eux.

Considérations générales relatives au rendu


côté serveur
Le modèle de rendu côté serveur (SSR) est basé sur le modèle de requête/réponse
traditionnel de HTTP. Par conséquent, il existe des sujets de préoccupation courants
entre le SSR et le protocole HTTP de requête/réponse. Les inquiétudes générales
relatives à la sécurité et les menaces spécifiques doivent être atténuées avec succès. Le
framework fournit des mécanismes intégrés pour gérer certaines de ces menaces, mais
d’autres menaces sont spécifiques au code d’application et doivent être gérées par
l’application. Ces menaces peuvent être classées ainsi :

Authentification et autorisation : l’application doit s’assurer que l’utilisateur est


authentifié et autorisé à accéder à l’application et aux ressources qu’elle expose. Le
framework fournit des mécanismes intégrés pour l’authentification et l’autorisation,
mais l’application doit s’assurer que les mécanismes sont correctement configurés
et utilisés. Les mécanismes intégrés pour l’authentification et l’autorisation sont
expliqués dans Nœud de sécurité Serveur de la documentation Blazor et dans
Sécurité et Identitynœud de la documentation ASP.NET Core. Il n’en sera donc pas
question ici.

Validation et assainissement des entrées : toutes les entrées provenant d’un client
doivent être validées et nettoyées avant l’utilisation. Sinon, l’application peut être
exposée à des attaques, telles que l’injection SQL, le script intersite, la falsification
de requête intersite, la redirection ouverte et d’autres formes d’attaques. L’entrée
peut provenir de n’importe où dans la requête.

Gestion des sessions : la gestion correcte des sessions utilisateur est essentielle
pour s’assurer que l’application n’est pas exposée aux attaques, telles que la
fixation de session, le détournement de session et d’autres attaques. Les
informations stockées dans la session doivent être correctement protégées et
chiffrées, et le code de l’application doit empêcher un utilisateur malveillant de
deviner ou de manipuler des sessions.

Gestion et journalisation des erreurs : l’application doit s’assurer que les erreurs
sont correctement gérées et journalisées. Sinon, l’application peut être exposée à
des attaques, telles que la divulgation d’informations. Cela peut se produire
lorsque l’application retourne des informations sensibles dans la réponse ou
lorsque l’application retourne des messages d’erreur détaillés avec des données
qui peuvent être utilisées pour attaquer l’application.

Protection des données : les données sensibles doivent être correctement


protégées, ce qui inclut la logique d’application lors de l’exécution sur
WebAssembly, car elles peuvent être facilement inversées.

Déni de service : l’application doit s’assurer qu’elle n’est pas exposée aux attaques,
telles que le déni de service. Cela se produit par exemple lorsque l’application n’est
pas correctement protégée contre les attaques par force brute ou lorsqu’une
action peut entraîner l’utilisation de trop de ressources par l’application.

Validation et assainissement des entrées


Toutes les entrées provenant du client doivent être considérées comme non approuvées,
sauf si ses informations ont été générées et protégées sur le serveur, telles qu’un jeton
CSRF, un cookie d’authentification, un identificateur de session ou toute autre charge
utile protégée par le chiffrement authentifié.

L’entrée est normalement disponible pour l’application via un processus de liaison, par
exemple via l’attribut [SupplyParameterFromQuery] ou l’attribut
[SupplyParameterFromForm]. Avant de traiter cette entrée, l’application doit s’assurer
que les données sont valides. Par exemple, l’application doit confirmer qu’aucune erreur
de liaison n’a été détectée lors du mappage des données de formulaire à une propriété
de composant. Sinon, l’application pourrait traiter des données non valides.

Si l’entrée est utilisée pour effectuer une redirection, l’application doit s’assurer que
l’entrée est valide et qu’elle ne pointe pas vers un domaine considéré comme non valide
ou vers un sous-chemin non valide dans le chemin de base de l’application. Sinon,
l’application peut être exposée à des attaques de redirection ouvertes, où un attaquant
peut créer un lien qui redirige l’utilisateur vers un site malveillant.

Si l’entrée est utilisée pour effectuer une requête de base de données, l’application doit
confirmer que l’entrée est valide et qu’elle n’expose pas l’application aux attaques par
injection SQL. Sinon, un attaquant peut créer une requête malveillante qui pourrait être
utilisée pour extraire des informations de la base de données ou pour modifier la base
de données.

Les données qui pourraient provenir d’une entrée utilisateur doivent également être
assainies avant d’être incluses dans une réponse. Par exemple, l’entrée peut contenir du
code HTML ou JavaScript qui peut être utilisé pour effectuer des attaques de script
intersite, qui peuvent à leur tour être utilisées pour extraire des informations de
l’utilisateur ou pour effectuer des actions pour le compte de l’utilisateur.

L’infrastructure fournit les mécanismes suivants pour faciliter la validation et


l’assainissement des entrées :

Toutes les données de formulaire liées sont validées pour l’exactitude de base. Si
une entrée ne peut pas être analysée, le processus de liaison signale une erreur
que l’application peut découvrir avant d’effectuer une action avec les données. Le
composant EditForm intégré prend cela en compte avant d’appeler le rappel du
formulaire OnValidSubmit. Blazor évite d’exécuter le rappel si une ou plusieurs
erreurs de liaison sont présentes.
Le framework utilise un jeton d’antifalsification pour se protéger contre les
attaques de falsification de requête intersite.

Toutes les autorisations et entrées doivent être validées sur le serveur au moment de
l’exécution d’une action donnée pour s’assurer que les données sont valides et précises
à ce moment-là et que l’utilisateur est autorisé à effectuer l’action. Cette approche est
conforme aux instructions de sécurité fournies pour le rendu côté serveur interactif.

Gestion des sessions


La gestion des sessions est gérée par l’infrastructure. L’infrastructure utilise un cookie de
session pour identifier la session utilisateur. Le cookie de session est protégé à l’aide des
API de protection des données principales ASP.NET. Le cookie de session n’est pas
accessible au code JavaScript s’exécutant sur le navigateur et ne peut pas être
facilement deviné ou manipulé par un utilisateur.

En ce qui concerne d’autres données de session, telles que les données stockées dans
les services, les données de session doivent être stockées dans des services délimités,
car les services délimités sont uniques par session utilisateur donnée, contrairement aux
services singleton partagés entre toutes les sessions utilisateur dans une instance de
processus donnée.

Quant au SSR, il n’existe pas beaucoup de différences entre les services délimités et
temporaires dans la plupart des cas, car la durée de vie du service est limitée à une seule
requête. Il existe une différence dans deux cas :

Si le service est injecté dans plusieurs emplacements ou à des moments différents


pendant la demande.
Si le service peut être utilisé dans un contexte de serveur interactif, où il survit à
plusieurs rendus, et qu’il est primordial que le service soit limité à la session
utilisateur.

Gestion et journalisation des erreurs


L’infrastructure fournit une journalisation intégrée pour l’application au niveau de
l’infrastructure. L’infrastructure journalise les événements importants, tels que lorsque le
jeton d’antifalsification d’un formulaire ne parvient pas à être validé, lorsqu’un
composant racine commence à s’afficher et lorsqu’une action est distribuée.
L’application est responsable de la journalisation des autres événements susceptibles
d’être importants à enregistrer.

L’infrastructure fournit une journalisation intégrée pour l’application au niveau de


l’infrastructure. L’infrastructure gère les erreurs qui se produisent pendant le rendu d’un
composant et utilise le mécanisme de limite d’erreur pour afficher un message d’erreur
convivial ou permet à l’erreur de monter en bulles jusqu’au middleware de gestion des
exceptions, qui est configuré pour afficher la page d’erreur.

Les erreurs qui se produisent lors du rendu de diffusion en continu une fois que la
réponse a commencé à être envoyée au client sont affichées dans la réponse finale sous
la forme d’un message d’erreur générique. Les détails sur la cause de l’erreur ne sont
inclus que pendant le développement.
Protection des données
Le framework offre des mécanismes de protection des informations sensibles pour une
session utilisateur donnée et garantit que les composants intégrés utilisent ces
mécanismes pour protéger les informations sensibles, telles que la protection de
l’identité de l’utilisateur lors de l’authentification cookie. En dehors des scénarios gérés
par l’infrastructure, le code du développeur est responsable de la protection d’autres
informations spécifiques à l’application. La méthode la plus courante consiste à utiliser
les API de protection des données principales ASP.NET ou toute autre forme de
chiffrement. En règle générale, l’application a pour responsabilité de :

S’assurer qu’un utilisateur ne peut pas inspecter ou modifier les informations


privées d’un autre utilisateur.
S’assurer qu’un utilisateur ne peut pas modifier les données utilisateur d’un autre
utilisateur, comme un identificateur interne.

En ce qui concerne la protection des données, vous devez comprendre clairement où le


code s’exécute. Pour le rendu statique côté serveur (Static SSR) et le rendu interactif côté
serveur (Interactive SSR), le code est stocké sur le serveur et n’atteint jamais le client.
Pour le mode de rendu WebAssembly interactif, le code d’application atteint toujours le
client, ce qui signifie que toute information sensible stockée dans le code de
l’application est disponible pour toute personne ayant accès à l’application.
L’obfuscation et autre technique similaire pour « protéger » le code ne sont pas
efficaces. Une fois que le code atteint le client, il peut être rétro-conçu pour extraire les
informations sensibles.

Denial of service (déni de service)


Au niveau du serveur, l’infrastructure fournit des limites sur les paramètres de
requête/réponse, tels que la taille maximale de la requête et la taille de l’en-tête. En ce
qui concerne le code d’application, le système de mappage de formulaires de Blazor
définit des limites similaires à celles définies par le système de liaison de modèle de
MVC :

Limite du nombre maximum d'erreurs.


Limite de la profondeur maximale de récursivité pour le Binder.
Limite du nombre maximal d’éléments liés dans une collection.

En outre, des limites sont définies pour le formulaire, telles qu’une taille maximale de clé
de formulaire et de valeur, et un nombre maximal d’entrées.
En général, l’application doit évaluer lorsqu’il y a une chance qu’une demande
déclenche une quantité asymétrique de travail par le serveur. Par exemple, lorsque
l’utilisateur envoie une requête paramétrée par N et que le serveur effectue une
opération en réponse qui est N fois plus coûteuse, où N est un paramètre qu’un
utilisateur contrôle et peut augmenter indéfiniment. Normalement, l’application doit
imposer une limite au nombre maximal de N qu’elle est prête à traiter ou doit s’assurer
que toute opération est inférieure, égale ou plus coûteuse que la demande par un
facteur constant.

Cet aspect relève davantage de la différence de croissance entre le travail effectué par le
client et le travail effectué par le serveur, que d’une comparaison spécifique 1→N. Par
exemple, un client peut envoyer un élément de travail (en insérant des éléments dans
une liste) qui prend N unités de temps à effectuer, alors que le serveur a besoin du
traitement de N^2^ (qui peut faire quelque chose de très naïf). C’est la différence entre
N et N^2^ qui compte.

Ainsi, la quantité de travail que le serveur accepte de faire est limitée, selon l’application.
Cet aspect s’applique aux charges de travail côté serveur, car les ressources se trouvent
sur le serveur, mais ne s’appliquent pas nécessairement aux charges de travail
WebAssembly sur le client dans la plupart des cas.

L’autre aspect important est que ce n’est pas seulement réservé au temps processeur.
Cela s’applique également à toutes les ressources, telles que la mémoire, le réseau et
l’espace sur le disque.

Pour les charges de travail WebAssembly, il n’y a généralement pas à s’inquiéter de la


quantité de travail effectuée par le client, car celui-ci est normalement limité par les
ressources disponibles sur le client. Toutefois, il existe certains cas où le client peut être
affecté, si par exemple, une application affiche des données d’autres utilisateurs et qu’un
utilisateur est capable d’ajouter des données au système qui obligent les clients qui
affichent les données à effectuer une quantité de travail qui n’est pas proportionnelle à
la quantité de données ajoutées par l’utilisateur.

Liste de vérification recommandée (non


exhaustive)
Vérifiez que l’utilisateur est authentifié et autorisé à accéder à l’application et aux
ressources qu’elle expose.
Validez et assainissez toutes les entrées provenant d’un client avant de l’utiliser.
Gérez correctement les sessions utilisateur pour vous assurer que l’état n’est pas
partagé par erreur entre les utilisateurs.
Gérez et consignez correctement les erreurs pour éviter d’exposer des informations
sensibles.
Consignez les événements importants dans l’application pour identifier les
problèmes potentiels et les actions d’audit effectuées par les utilisateurs.
Protégez les informations sensibles à l’aide d’API de protection des données
ASP.NET Core ou de l’un des composants disponibles
(Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage,
PersistentComponentState).
Assurez-vous que l’application comprend les ressources qui peuvent être
consommées par une demande donnée et qu’elle dispose des limites en place
pour éviter les attaques par déni de service.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Conseils d’atténuation des menaces
pour le rendu interactif côté serveur de
ASP.NET Core Blazor
Article • 09/02/2024

Cet article explique comment atténuer les menaces de sécurité dans les applications
Blazor.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.
Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples
d’applications BlazorSample_{PROJECT TYPE} à partir du Blazordépôt GitHub
d’exemples correspondant à la version .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Les applications adoptent un modèle de traitement des données avec état, dans lequel
le serveur et le client maintiennent une relation de longue durée. L’état persistant est
maintenu par un circuit, qui peut s’étendre sur des connexions potentiellement de
longue durée.

Lorsqu’un utilisateur visite un site , le serveur crée un circuit dans la mémoire du serveur.
Le circuit indique au navigateur le contenu à afficher et répond aux événements, par
exemple lorsque l’utilisateur sélectionne un bouton dans l’interface utilisateur. Pour
effectuer ces actions, un circuit appelle des fonctions JavaScript dans le navigateur de
l’utilisateur et des méthodes .NET sur le serveur. Cette interaction bidirectionnelle basée
sur JavaScript est appelée interopérabilité JavaScript (interop JS).

Étant donné que l’interopérabilité de JS se produit sur Internet et que le client utilise un
navigateur distant, les applications partagent la plupart des problèmes de sécurité des
applications web. Cette rubrique décrit les menaces courantes pour les applications
Blazor côté serveur et fournit des conseils d’atténuation des menaces visant les
applications pour Internet.

Dans les environnements contraints, comme à l’intérieur de réseaux d’entreprise ou


d’intranets, certains conseils d’atténuation :

Ne s’appliquent pas dans l’environnement contraint.


Ne valent pas le coût de l’implémentation, car le risque de sécurité est faible dans
un environnement contraint.

État partagé
Les applications Blazor côté serveur résident dans la mémoire du serveur, et plusieurs
sessions d’application sont hébergées dans le même processus. Pour chaque session
d’application, Blazor démarre un circuit avec sa propre étendue de conteneur d’injection
de dépendances, de sorte que les services délimités sont uniques par session Blazor.

2 Avertissement
Nous ne recommandons pas que les applications sur le même serveur partagent un
état à l’aide des services singleton, sauf si des précautions extrêmes sont prises, car
cela peut introduire des vulnérabilités de sécurité, comme des fuites d’état
utilisateur entre les circuits.

Vous pouvez utiliser des services singleton avec état dans les applications Blazor si elles
sont spécifiquement conçues pour cela. Par exemple, l’utilisation d’un cache de mémoire
singleton est acceptable, car un cache de mémoire nécessite une clé pour accéder à une
entrée donnée. En supposant que les utilisateurs ne contrôlent pas les clés de cache
utilisées avec le cache, l’état stocké dans le cache ne fuit pas entre les circuits.

Pour obtenir des conseils généraux sur la gestion de l’état, consultez Gestion de l’état
BlazorASP.NET Core.

IHttpContextAccessor / HttpContext dans les


composants Razor
IHttpContextAccessor doit être évité avec le rendu interactif, car il n’existe pas de
HttpContext valide disponible.

IHttpContextAccessor peut être utilisé pour les composants rendus statiquement sur le
serveur. Toutefois, nous vous recommandons de l’éviter si possible.

HttpContext peut être utilisé comme paramètre en cascade uniquement dans les
composants racines rendus statiquement pour les tâches générales, telles que l’inspection
et la modification d’en-têtes ou d’autres propriétés dans le composant App
( Components/App.razor ). La valeur est toujours null pour le rendu interactif.

C#

[CascadingParameter]
public HttpContext? HttpContext { get; set; }

Pour les scénarios où HttpContext est requis dans les composants interactifs, nous vous
recommandons de transmettre les données via l’état du composant persistant à partir
du serveur. Pour plus d’informations, consultez Autres scénarios de sécurité ASP.NET
Core Blazor côté serveur.

Épuisement des ressources


L’épuisement des ressources peut se produire lorsqu’un client interagit avec le serveur
et que celui-ci consomme des ressources excessives. La consommation excessive de
ressources affecte principalement :

UC
Mémoire
Connexions clientes

Les attaques par déni de service (DoS) visent généralement à épuiser les ressources
d’une application ou d’un serveur. Toutefois, l’épuisement des ressources n’est pas
nécessairement le résultat d’une attaque sur le système. Par exemple, les ressources
limitées peuvent être épuisées en raison d’une demande élevée des utilisateurs. Les
attaques DoS sont abordées plus loin dans la section DoS.

Les ressources externes au framework Blazor, comme les bases de données et les
descripteurs de fichiers (utilisés pour lire et écrire des fichiers), peuvent également
rencontrer un épuisement des ressources. Pour plus d’informations, consultez Meilleures
pratiques pour ASP.NET Core.

UC
L’épuisement du processeur peut se produire lorsqu’un ou plusieurs clients forcent le
serveur à effectuer un travail intensif pour le processeur.

Par exemple, considérez une application qui calcule une suite de Fibonnacci. Un nombre
de Fibonnacci est produit à partir d’une séquence de Fibonnacci, où chaque nombre de
la séquence est la somme des deux nombres précédents. La quantité de travail
nécessaire pour atteindre la réponse dépend de la longueur de la séquence et de la
taille de la valeur initiale. Si l’application n’impose pas de limites à la requête d’un client,
les calculs gourmands en ressources processeur peuvent dominer le temps processeur
et diminuer les performances des autres tâches. La consommation excessive de
ressources est un problème de sécurité qui a un impact sur la disponibilité.

L’épuisement du processeur est une préoccupation pour toutes les applications


publiques. Dans les applications web standard, les requêtes et les connexions expirent à
titre de protection, mais les applications Blazor ne fournissent pas les mêmes sécurités.
Les applications Blazor doivent inclure des vérifications et des limites appropriées avant
d’effectuer un travail potentiellement gourmand en ressources processeur.

Mémoire
L’épuisement de la mémoire peut se produire lorsqu’un ou plusieurs clients forcent le
serveur à consommer une grande quantité de mémoire.

Par exemple, considérez une application avec un composant qui accepte et affiche une
liste d’éléments. Si l’application Blazor n’impose pas de limites au nombre d’éléments
autorisés ou au nombre d’éléments rendus au client, le traitement et le rendu,
nécessitant beaucoup de mémoire, peuvent dominer la mémoire du serveur au point
que les performances du serveur en souffrent. Le serveur peut se bloquer ou ralentir au
point qu’il semble être en panne.

Envisagez le scénario suivant pour la maintenance et l’affichage d’une liste d’éléments


qui se rapportent à un scénario d’épuisement potentiel de la mémoire sur le serveur :

Les éléments d’une propriété ou d’un champ List<T> utilisent la mémoire du


serveur. Si l’application permet à la liste d’éléments de s’étendre sans limite, le
serveur risque de manquer de mémoire. Le manque de mémoire entraîne la fin de
la session active (blocage), et toutes les sessions simultanées de cette instance de
serveur reçoivent une exception de mémoire insuffisante. Pour éviter ce scénario,
l’application doit utiliser une structure de données qui impose une limite
d’éléments aux utilisateurs simultanés.
Si un schéma de pagination n’est pas utilisé pour le rendu, le serveur utilise de la
mémoire supplémentaire pour les objets qui ne sont pas visibles dans l’interface
utilisateur. Sans limitation du nombre d’éléments, les besoins en mémoire peuvent
épuiser la mémoire du serveur disponible. Pour éviter ce scénario, utilisez l’une des
approches suivantes :
Utilisez des listes paginées lors du rendu.
Affichez uniquement les 100 à 1 000 premiers éléments et exigez que
l’utilisateur entre des critères de recherche pour rechercher des éléments au-
delà des éléments affichés.
Pour un scénario de rendu plus avancé, implémentez des listes ou des grilles qui
prennent en charge la virtualisation. À l’aide de la virtualisation, les listes
affichent uniquement un sous-ensemble d’éléments actuellement visibles par
l’utilisateur. Lorsque l’utilisateur interagit avec la barre de défilement dans
l’interface utilisateur, le composant affiche uniquement les éléments nécessaires
à l’affichage. Les éléments qui ne sont actuellement pas requis pour l’affichage
peuvent être conservés dans le stockage secondaire, ce qui est l’approche
idéale. Les éléments non affichés peuvent également être conservés en
mémoire, ce qui est moins idéal.

7 Notes
Blazor prend en charge la virtualisation intégrée. Pour plus d’informations,
consultez Virtualisation des composants ASP.NET Core Razor.

Les applications Blazor offrent un modèle de programmation similaire à d’autres


infrastructures d’interface utilisateur pour les applications avec état, comme WPF,
Windows Forms ou Blazor WebAssembly. La différence principale est qu’avec plusieurs
infrastructures d’interface utilisateur, la mémoire consommée par l’application
appartient au client et affecte uniquement ce client individuel. Par exemple, une
application Blazor WebAssembly s’exécute entièrement sur le client et utilise
uniquement les ressources de mémoire du client. Pour une application Blazor côté
serveur, la mémoire consommée par l’application appartient au serveur et est partagée
entre les clients sur l’instance du serveur.

Les besoins en mémoire côté serveur sont à prendre en compte pour toutes les
applications Blazor côté serveur. Toutefois, la plupart des applications web sont sans état
et la mémoire utilisée lors du traitement d’une requête est libérée lorsque la réponse est
retournée. En règle générale, n’autorisez pas les clients à allouer une quantité de
mémoire non limitée comme dans toute autre application côté serveur qui conserve les
connexions clientes. La mémoire consommée par une application Blazor côté serveur
persiste plus longtemps qu’une requête unique.

7 Notes

Pendant le développement, un profileur peut être utilisé, ou une trace peut être
capturée pour évaluer les besoins en mémoire des clients. Un profileur ou une trace
ne capture pas la mémoire allouée à un client spécifique. Pour capturer l’utilisation
de la mémoire d’un client spécifique pendant le développement, capturez un
vidage et examinez la demande de mémoire de tous les objets enracinés sur le
circuit d’un utilisateur.

Connexions clientes
L’épuisement de connexions peut se produire lorsqu’un ou plusieurs clients ouvrent trop
de connexions simultanées au serveur, empêchant les autres clients d’établir de
nouvelles connexions.

Les clients Blazor établissent une connexion unique par session et maintiennent la
connexion ouverte tant que la fenêtre du navigateur est ouverte. Étant donné la nature
persistante des connexions et la nature avec état des applications Blazor côté serveur,
l’épuisement des connexions est un risque plus élevé pour la disponibilité de
l’application.

Par défaut, il n’existe aucune limite quant au nombre de connexions par utilisateur pour
une application. Si l’application nécessite une limite de connexion, suivez une ou
plusieurs des approches suivantes :

Exigez l’authentification, ce qui limite naturellement la capacité des utilisateurs non


autorisés à se connecter à l’application. Pour que ce scénario soit efficace, les
utilisateurs doivent être empêchés de provisionner de nouveaux utilisateurs à la
demande.
Limitez le nombre de connexions par utilisateur. La limitation des connexions peut
être effectuée à l’aide des approches suivantes. Veillez à autoriser les utilisateurs
légitimes à accéder à l’application (par exemple, lorsqu’une limite de connexion est
établie en fonction de l’adresse IP du client).

Au niveau de l’application :
Extensibilité du routage du point de terminaison.
Exigez l’authentification pour se connecter à l’application et effectuez le suivi
des sessions actives par utilisateur.
Rejetez les nouvelles sessions lorsque vous atteignez une limite.
Les connexions WebSocket proxy à une application via l’utilisation d’un
proxy, comme Azure SignalR Service, qui multiplexe les connexions des
clients à une application. Cela fournit une application avec une capacité de
connexion supérieure à celle qu’un seul client peut établir, ce qui empêche
un client d’épuiser les connexions au serveur.

Au niveau du serveur : utilisez un proxy/une passerelle devant l’application. Par


exemple, Azure Front Door vous permet de définir, de gérer et de surveiller le
routage global du trafic web vers une application et fonctionne lorsque les
applications sont configurées pour utiliser l’interrogation longue.

7 Notes

Bien que l’interrogation longue soit prise en charge, WebSockets est le


protocole de transport recommandé. En date de février 2023, Azure Front
Door ne prend pas en charge WebSockets, mais la prise en charge de
WebSockets est en cours de développement pour une prochaine version
du service. Pour plus d’informations, consultez Prise en charge des
connexions WebSocket sur Azure Front Door .
Attaques par déni de service (DoS)
Les attaques par déni de service (DoS) impliquent un client qui entraîne l’épuisement
d’une ou plusieurs des ressources du serveur, ce qui rend l’application indisponible. Les
applications Blazor incluent des limites par défaut et s’appuient sur d’autres limites
ASP.NET Core et SignalR définies sur CircuitOptions pour se protéger contre les attaques
DoS :

CircuitOptions.DisconnectedCircuitMaxRetained
CircuitOptions.DisconnectedCircuitRetentionPeriod
CircuitOptions.JSInteropDefaultCallTimeout
CircuitOptions.MaxBufferedUnacknowledgedRenderBatches
HubConnectionContextOptions.MaximumReceiveMessageSize

Pour plus d’informations et des exemples de codage de configuration, consultez les


articles suivants :

Conseils pour BlazorSignalR ASP.NET Core


C ASP.NET Core SignalR

Interactions avec le navigateur (client)


Un client interagit avec le serveur par le biais de la distribution d’événements
d’interopérabilité JS et de l’achèvement du rendu. La communication d’interopérabilité
JS va dans les deux sens entre JavaScript et .NET :

Les événements de navigateur sont distribués du client au serveur de manière


asynchrone.
Le serveur répond de manière asynchrone en remettant l’interface utilisateur en
fonction des besoins.

Fonctions JavaScript appelées à partir de .NET


Pour les appels de méthodes .NET à JavaScript :

Tous les appels ont un délai d’expiration configurable après lequel ils échouent,
renvoyant un OperationCanceledException à l’appelant.
Il existe un délai d’attente par défaut pour les appels
(CircuitOptions.JSInteropDefaultCallTimeout) d’une minute. Pour configurer
cette limite, consultez Appeler des fonctions JavaScript à partir de méthodes
.NET dans ASP.NET Core Blazor.
Un jeton d’annulation peut être fourni pour contrôler l’annulation pour chaque
appel. Comptez sur le délai d’expiration de la légende par défaut si possible, et
liez un délai d’appel au client si un jeton d’annulation est fourni.
Le résultat d’un appel JavaScript ne peut pas être approuvé. Le client d’application
Blazor s’exécutant dans le navigateur recherche la fonction JavaScript à appeler. La
fonction est appelée et le résultat ou une erreur est généré. Un client malveillant
peut tenter de :
Provoquer un problème dans l’application en retournant une erreur à partir de
la fonction JavaScript.
Induire un comportement inattendu sur le serveur en retournant un résultat
inattendu à partir de la fonction JavaScript.

Prenez les précautions suivantes pour vous prémunir contre les scénarios précédents :

Encapsulez les appels d’interopérabilité JS dans des instructions try-catch pour


tenir compte des erreurs qui peuvent se produire pendant les appels. Pour plus
d’informations, consultez Gérer les erreurs dans les applications ASP.NET Core
Blazor.
Validez les données retournées par les appels d’interopérabilité JS, y compris les
messages d’erreur, avant d’effectuer une action.

Méthodes .NET appelées à partir du navigateur


N’approuvez pas les appels de JavaScript vers des méthodes .NET. Lorsqu’une méthode
.NET est exposée à JavaScript, tenez compte de la façon dont la méthode .NET est
appelée :

Traitez toute méthode .NET exposée à JavaScript comme un point de terminaison


public pour l’application.
Valider une entrée.
Vérifiez que les valeurs se trouvent dans les plages attendues.
Vérifiez que l’utilisateur a l’autorisation d’effectuer l’action demandée.
N’allouez pas une quantité excessive de ressources dans le cadre de l’appel de
la méthode .NET. Par exemple, effectuez des vérifications et placez des limites
sur l’utilisation du processeur et de la mémoire.
Prenez en compte le fait que les méthodes statiques et d’instance peuvent être
exposées aux clients JavaScript. Évitez de partager l’état entre les sessions, sauf
si la conception demande le partage d’état avec des contraintes appropriées.
Pour les méthodes d’instance exposées via des objets
DotNetObjectReference créés à l’origine par l’injection de dépendances (DI),
les objets doivent être inscrits en tant qu’objets délimités. Cela s’applique à
n’importe quel service de DI utilisé par l’application .
Pour les méthodes statiques, évitez d’établir un état qui ne peut pas être
étendu au client, sauf si l’application partage explicitement l’état par
conception entre tous les utilisateurs d’une instance de serveur.
Évitez de transmettre les données fournies par l’utilisateur dans les paramètres
aux appels JavaScript. Si la transmission de données dans des paramètres est
absolument nécessaire, assurez-vous que le code JavaScript gère la transmission
des données sans introduire de vulnérabilités XSS (Cross-Site Scripting). Par
exemple, n’écrivez pas de données fournies par l’utilisateur dans le
modèle DOM (Document Object Model) en définissant la propriété innerHTML
d’un élément. Envisagez d’utiliser une stratégie de sécurité du contenu (CSP)
pour désactiver eval et d’autres primitives JavaScript non sécurisées. Pour plus
d’informations, consultez Appliquer une stratégie de sécurité de contenu pour
ASP.NET Core Blazor.
Évitez d’implémenter la répartition personnalisée des appels .NET en plus de
l’implémentation de la répartition du framework. L’exposition des méthodes .NET
au navigateur est un scénario avancé, non recommandé pour le développement
Blazor général.

Événements
Les événements fournissent un point d’entrée à une application. Les mêmes règles de
protection des points de terminaison dans les applications web s’appliquent à la gestion
des événements dans les applications Blazor. Un client malveillant peut envoyer toutes
les données qu’il souhaite envoyer en tant que charge utile pour un événement.

Par exemple :

Un événement de modification pour un <select> peut envoyer une valeur qui


n’est pas dans les options présentées par l’application au client.
Un <input> peut envoyer des données texte au serveur, en contournant la
validation côté client.

L’application doit valider les données pour tout événement géré par l’application. Les
composants de formulaire du framework Blazor effectuent des validations de base. Si
l’application utilise des composants de formulaire personnalisés, du code personnalisé
doit être écrit pour valider les données d’événement comme nécessaire.

Les événements étant asynchrones, plusieurs événements peuvent être distribués au


serveur avant que l’application n’ait le temps de réagir en produisant un nouveau rendu.
Cela a certaines implications en matière de sécurité à prendre en compte. La limitation
des actions du client dans l’application doit être effectuée à l’intérieur des gestionnaires
d’événements et ne dépend pas de l’état d’affichage du rendu actuel.
Considérez un composant de compteur qui doit permettre à un utilisateur d’incrémenter
un compteur au maximum trois fois. Le bouton permettant d’incrémenter le compteur
est conditionnel et basé sur la valeur de count :

razor

<p>Count: @count</p>

@if (count < 3)


{
<button @onclick="IncrementCount" value="Increment count" />
}

@code
{
private int count = 0;

private void IncrementCount()


{
count++;
}
}

Un client peut distribuer un ou plusieurs événements d’incrément avant que le


framework produise un nouveau rendu de ce composant. Le résultat est que le count
peut être incrémenté plus de trois fois par l’utilisateur, car le bouton n’est pas supprimé
par l’interface utilisateur assez rapidement. La bonne façon d’atteindre la limite de trois
incréments count est illustrée dans l’exemple suivant :

razor

<p>Count: @count</p>

@if (count < 3)


{
<button @onclick="IncrementCount" value="Increment count" />
}

@code
{
private int count = 0;

private void IncrementCount()


{
if (count < 3)
{
count++;
}
}
}

En ajoutant la vérification if (count < 3) { ... } à l’intérieur du gestionnaire, la


décision d’incrémenter count est basée sur l’état actuel de l’application. La décision
n’est pas basée sur l’état de l’interface utilisateur comme dans l’exemple précédent, cet
état pouvant être temporairement obsolète.

Protection contre les envois multiples


Si un rappel d’événement appelle une opération de longue durée de manière
asynchrone, comme l’extraction de données à partir d’un service ou d’une base de
données externe, envisagez d’utiliser une protection. La protection peut empêcher
l’utilisateur de mettre en file d’attente plusieurs opérations pendant que l’opération est
en cours avec un retour visuel. Le code de composant suivant définit isLoading sur
true tandis que DataService.GetDataAsync obtient des données à partir du serveur. Si
isLoading est true , le bouton est désactivé dans l’interface utilisateur :

razor

<button disabled="@isLoading" @onclick="UpdateData">Update</button>

@code {
private bool isLoading;
private Data[] data = Array.Empty<Data>();

private async Task UpdateData()


{
if (!isLoading)
{
isLoading = true;
data = await DataService.GetDataAsync(DateTime.Now);
isLoading = false;
}
}
}

Le modèle de protection illustré dans l’exemple précédent fonctionne si l’opération


d’arrière-plan est exécutée de manière asynchrone avec le modèle async - await .

Annuler tôt et éviter l’utilisation après la suppression


Outre l’utilisation d’une protection, comme décrit dans la section Protection contre les
envois multiples, envisagez d’utiliser un CancellationToken pour annuler les opérations
de longue durée lorsque le composant est supprimé. Cette approche présente
l’avantage supplémentaire d’éviter l’utilisation après la suppression dans les
composants :

razor

@implements IDisposable

...

@code {
private readonly CancellationTokenSource TokenSource =
new CancellationTokenSource();

private async Task UpdateData()


{
...

data = await DataService.GetDataAsync(DateTime.Now,


TokenSource.Token);

if (TokenSource.Token.IsCancellationRequested)
{
return;
}

...
}

public void Dispose()


{
TokenSource.Cancel();
}
}

Évitez les événements qui produisent de grandes


quantités de données
Certains événements DOM, comme oninput ou onscroll , peuvent produire une grande
quantité de données. Évitez d’utiliser ces événements dans le côté serveur du serveur
Blazor.

Autres conseils de sécurité


Les conseils pour la sécurisation des applications ASP.NET Core s’appliquent aux
applications Blazor côté serveur et sont abordés dans les sections suivantes de cet
article :
Journalisation et données sensibles
Protéger les informations en transit avec HTTPS
Scripting inter-site (XSS)
Protection cross-origin
Click-jacking
Redirections ouvertes

Journalisation et données sensibles


Les interactions d’interopérabilité JS entre le client et le serveur sont enregistrées dans
les journaux du serveur avec des instances ILogger. Blazor évite de journaliser les
informations sensibles, comme les événements réels ou les entrées et sorties
d’interopérabilité JS.

Lorsqu’une erreur se produit sur le serveur, le framework avertit le client et supprime la


session. Par défaut, le client reçoit un message d’erreur générique qui peut être vu dans
les outils de développement du navigateur.

L’erreur côté client n’inclut pas la pile des appels et ne fournit pas de détails sur la cause
de l’erreur, mais les journaux du serveur contiennent de telles informations. À des fins
de développement, des informations d’erreur sensibles peuvent être mises à la
disposition du client en activant les erreurs détaillées.

2 Avertissement

L’exposition d’informations sur les erreurs aux clients sur Internet est un risque de
sécurité qui doit toujours être évité.

Protéger les informations en transit avec HTTPS


Blazor utilise SignalR pour la communication entre le client et le serveur. Blazor utilise
normalement le transport que SignalR négocie, qui est généralement WebSockets.

Blazor ne garantit pas l’intégrité et la confidentialité des données envoyées entre le


serveur et le client. Toujours utiliser HTTPS.

Scripting inter-site (XSS)


Le scripting inter-site (XSS) permet à une partie non autorisée d’exécuter une logique
arbitraire dans le contexte du navigateur. Une application compromise peut
potentiellement exécuter du code arbitraire sur le client. La vulnérabilité peut être
utilisée pour effectuer potentiellement un certain nombre d’actions malveillantes sur le
serveur :

Envoyer des événements faux/non valides au serveur.


Envoyer des complétions de rendu fausses/non valides.
Évitez de distribuer les complétions de rendu.
Envoyer les appels d’interopérabilité de JavaScript à .NET.
Modifier la réponse des appels d’interopérabilité de .NET à JavaScript.
Évitez d’envoyer .NET aux résultats de l’interopérabilité JS.

Le framework Blazor prend des mesures pour se protéger contre certaines des menaces
précédentes :

Il cesse de produire de nouvelles mises à jour de l’interface utilisateur si le client


n’accuse pas réception des lots de rendu. Configuré avec
CircuitOptions.MaxBufferedUnacknowledgedRenderBatches.
Il fait expirer tout appel .NET à JavaScript après une minute sans réception de
réponse du client. Configuré avec CircuitOptions.JSInteropDefaultCallTimeout.
Il effectue la validation de base sur toutes les entrées provenant du navigateur
pendant l’interopérabilité JS :
Les références .NET sont valides et du type attendu par la méthode .NET.
Les données sont malformées.
Le nombre correct d’arguments pour la méthode est présent dans la charge
utile.
Les arguments ou le résultat peuvent être désérialisés correctement avant
d’appeler la méthode.
Il effectue la validation de base dans toutes les entrées provenant du navigateur à
partir d’événements distribués :
L’événement a un type valide.
Les données de l’événement peuvent être désérialisées.
Un gestionnaire d’événements est associé à l’événement.

En plus des protections que le framework implémente, l’application doit être codée par
le développeur pour se protéger contre les menaces et prendre les mesures
appropriées :

Validez toujours les données lors de la gestion des événements.


Prenez les mesures appropriées lors de la réception de données non valides :
Ignorez les données et retournez. Cela permet à l’application de continuer à
traiter les requêtes.
Si l’application détermine que l’entrée est illégitime et n’a pas pu être produite
par un client légitime, levez une exception. La levée d’une exception détruit le
circuit et met fin à la session.
Ne faites pas confiance au message d’erreur fourni par les complétions de lot de
rendu incluses dans les journaux. L’erreur est fournie par le client et n’est
généralement pas de confiance, car le client peut être compromis.
Ne faites pas confiance à l’entrée sur les appels d’interopérabilité JS dans l’une ou
l’autre direction entre les méthodes JavaScript et .NET.
L’application est chargée de vérifier que le contenu des arguments et des résultats
est valide, même si les arguments ou les résultats sont correctement désérialisés.

Pour qu’une vulnérabilité XSS existe, l’application doit incorporer une entrée utilisateur
dans la page rendue. Blazor exécute une étape de compilation où le balisage dans un
fichier .razor est transformé en logique C# procédurale. Au moment de l’exécution, la
logique C# génère une arborescence de rendu décrivant les éléments, le texte et les
composants enfants. Cela est appliqué au DOM du navigateur via une séquence
d’instructions JavaScript (ou est sérialisé au format HTML dans le cas d’un prérendu) :

L’entrée utilisateur rendue via une syntaxe Razor normale (par exemple,
@someStringValue ) n’expose pas de vulnérabilité XSS, car la syntaxe Razor est

ajoutée au DOM via des commandes qui peuvent uniquement écrire du texte.
Même si la valeur inclut du balisage HTML, la valeur est affichée sous forme de
texte statique. Lors du prérendu, la sortie est encodée au format HTML, ce qui
affiche également le contenu sous forme de texte statique.
Les balises de script ne sont pas autorisées et ne doivent pas être incluses dans
l’arborescence de rendu des composants de l’application. Si une balise de script
est incluse dans le balisage d’un composant, une erreur de compilation est
générée.
Les auteurs de composants peuvent créer des composants en C# sans utiliser
Razor. L’auteur du composant est chargé d’utiliser les API appropriées lors de
l’émission de la sortie. Par exemple, utilisez builder.AddContent(0,
someUserSuppliedString) et non builder.AddMarkupContent(0,
someUserSuppliedString) , car ce dernier pourrait créer une vulnérabilité XSS.

Envisagez d’atténuer davantage les vulnérabilités XSS. Par exemple, implémentez une
stratégie de sécurité du contenu (CSP) restrictive. Pour plus d’informations, consultez
Appliquer une stratégie de sécurité de contenu pour ASP.NET Core Blazor.

Pour plus d’informations, consultez Empêcher le scripting inter-site (XSS) dans ASP.NET
Core.

Protection cross-origin
Les attaques cross-origin impliquent qu’un client d’une autre origine effectue une action
sur le serveur. L’action malveillante est généralement une requête GET ou un POST de
formulaire (falsification de requête intersites, ou CSRF), mais l’ouverture d’un WebSocket
malveillant est également possible. Les applications Blazor offrent les mêmes garanties
que toutes les autres applications SignalR utilisant l’offre de protocole hub :

L’accès aux applications peut se faire à partir d’origines différentes, sauf si des
mesures supplémentaires sont prises pour empêcher cela. Pour désactiver l’accès
Cross-Origin, désactivez CORS dans le point de terminaison en ajoutant l’intergiciel
CORS au pipeline et en ajoutant DisableCorsAttribute aux métadonnées du point
de terminaison Blazor, ou limitez l’ensemble d’origines autorisées par en
configurant SignalR pour le partage de ressources Cross-Origin. Pour obtenir des
conseils sur les restrictions d’origine WebSocket, consultez Prise en charge des
WebSockets dans ASP.NET Core.
Si CORS est activé, des étapes supplémentaires peuvent être nécessaires pour
protéger l’application en fonction de la configuration de CORS. Si CORS est
globalement activé, il peut être désactivé pour le hub BlazorSignalR en ajoutant les
métadonnées DisableCorsAttribute aux métadonnées du point de terminaison
après avoir appelé MapBlazorHub sur le générateur d’itinéraires de point de
terminaison.

Pour plus d’informations, consultez Prévenir les attaques par falsification de requête
intersites (XSRF/CSRF) dans ASP.NET Core.

Click-jacking
Le click-jacking implique le rendu d’un site en tant que <iframe> à l’intérieur d’un site à
partir d’une autre origine afin d’inciter l’utilisateur à effectuer des actions sur le site
attaqué.

Pour protéger une application contre le rendu à l’intérieur d’un <iframe> , utilisez une
stratégie de sécurité du contenu (CSP) et l’en-tête X-Frame-Options .

Pour plus d’informations, consultez les ressources suivantes :

Appliquer une stratégie de sécurité du contenu pour ASP.NET Core Blazor


Documents web MDN : X-Frame-Options

Redirections ouvertes
Lorsqu’une session d’application démarre, le serveur effectue la validation de base des
URL envoyées dans le cadre du démarrage de la session. Le framework vérifie que l’URL
de base est un parent de l’URL actuelle avant d’établir le circuit. Aucune vérification
supplémentaire n’est effectuée par le framework.

Lorsqu’un utilisateur sélectionne un lien sur le client, l’URL du lien est envoyée au
serveur, qui détermine l’action à entreprendre. Par exemple, l’application peut effectuer
une navigation côté client ou indiquer au navigateur d’accéder au nouvel emplacement.

Les composants peuvent également déclencher des requêtes de navigation par


programmation via l’utilisation de NavigationManager. Dans ces scénarios, l’application
peut effectuer une navigation côté client ou indiquer au navigateur d’accéder au nouvel
emplacement.

Les composants doivent :

Éviter d’utiliser l’entrée utilisateur dans le cadre des arguments d’appel de


navigation.
Valider les arguments pour s’assurer que la cible est autorisée par l’application.

Sinon, un utilisateur malveillant pourrait forcer le navigateur à accéder à un site contrôlé


par un attaquant. Dans ce scénario, l’attaquant incite l’application à utiliser une entrée
utilisateur dans le cadre de l’appel de la méthode NavigationManager.NavigateTo.

Ce conseil s’applique également lors du rendu des liens dans le cadre de l’application :

Si possible, utilisez des liens relatifs.


Vérifiez que les destinations des liens absolus sont valides avant de les inclure dans
une page.

Pour plus d’informations, consultez Empêcher les attaques par redirection ouverte dans
ASP.NET Core.

Liste de contrôle de sécurité


La liste suivante de considérations relatives à la sécurité n’est pas exhaustive :

Validez les arguments des événements.


Validez les entrées et les résultats des appels d’interopérabilité JS.
Évitez d’utiliser (ou validez au préalable) l’entrée utilisateur pour les pour les appels
d’interopérabilité .NET à JS.
Empêchez le client d’allouer une quantité de mémoire non limitée.
Données dans le composant.
Objets DotNetObjectReference retournés au client.
Protégez-vous contre les envois multiples.
Annulez les opérations de longue durée lorsque le composant est supprimé.
Évitez les événements qui produisent de grandes quantités de données.
Évitez d’utiliser l’entrée utilisateur dans le cadre des appels à
NavigationManager.NavigateTo et validez l’entrée utilisateur pour les URL par
rapport à un ensemble d’origines autorisées d’abord, si cela est inévitable.
Ne prenez pas de décisions d’autorisation basées sur l’état de l’interface utilisateur,
mais uniquement en fonction de l’état du composant.
Envisagez d’utiliser une stratégie de sécurité du contenu (CSP) pour vous
protéger contre les attaques XSS. Pour plus d’informations, consultez Appliquer
une stratégie de sécurité de contenu pour ASP.NET Core Blazor.
Envisagez d’utiliser une CSP et X-Frame-Options pour vous protéger contre le
click-jacking.
Vérifiez que les paramètres CORS sont appropriés lors de l’activation de CORS, ou
désactivez explicitement CORS pour les applications Blazor.
Testez pour vous assurer que les limites côté serveur de l’application Blazor offrent
une expérience utilisateur acceptable sans niveaux de risque inacceptables.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Secure an ASP.NET Core Blazor Web App
with OpenID Connect (OIDC)
Article • 02/14/2024

This article describes how to secure a Blazor Web App with OpenID Connect (OIDC)
using a sample app in the dotnet/blazor-samples GitHub repository (.NET 8 or later) .

This version of the article covers implementing OIDC without adopting the Backend for
Frontend (BFF) pattern. The BFF pattern is useful for making authenticated requests to
external services. Change the article version selector to OIDC with BFF pattern if the
app's specification calls for adopting the BFF pattern.

The following specification is covered:

The Blazor Web App uses the Auto render mode with global interactivity.
Custom auth state provider services are used by the server and client apps to
capture the user's authentication state and flow it between the server and client.
This app is a starting point for any OIDC authentication flow. OIDC is configured
manually in the app and doesn't rely upon Microsoft Entra ID or Microsoft
Identity Web packages, nor does the sample app require Microsoft Azure
hosting. However, the sample app can used with Entra, Microsoft Identity Web, and
hosted in Azure.
Automatic non-interactive token refresh.

Sample app
The sample app consists of two projects:

BlazorWebAppOidc : Server-side project of the Blazor Web App, containing an

example Minimal API endpoint for weather data.


BlazorWebAppOidc.Client : Client-side project of the Blazor Web App.

Access the sample apps through the latest version folder from the repository's root with
the following link. The projects are in the BlazorWebAppOidc folder for .NET 8 or later.

View or download sample code (how to download)

Server-side Blazor Web App project


( BlazorWebAppOidc )
The BlazorWebAppOidc project is the server-side project of the Blazor Web App.

The BlazorWebAppOidc.http file can be used for testing the weather data request. Note
that the BlazorWebAppOidc project must be running to test the endpoint, and the
endpoint is hardcoded into the file. For more information, see Use .http files in Visual
Studio 2022.

Configuration
This section explains how to configure the sample app.

7 Note

For Microsoft Entra ID and Azure AD B2C, you can use


AddMicrosoftIdentityWebApp from Microsoft Identity Web
(Microsoft.Identity.Web NuGet package , API documentation), which adds both
the OIDC and Cookie authentication handlers with the appropriate defaults. The
sample app and the guidance in this section doesn't use Microsoft Identity Web.
The guidance demonstrates how to configure the OIDC handler manually for any
OIDC provider. For more information on implementing Microsoft Identity Web, see
the linked resources.

The following OpenIdConnectOptions configuration is found in the project's Program file


on the call to AddOpenIdConnect:

SignInScheme: Sets the authentication scheme corresponding to the middleware


responsible of persisting user's identity after a successful authentication. The OIDC
handler needs to use a sign-in scheme that's capable of persisting user credentials
across requests. The following line is present merely for demonstration purposes. If
omitted, DefaultSignInScheme is used as a fallback value.

C#

oidcOptions.SignInScheme =
CookieAuthenticationDefaults.AuthenticationScheme;

Scopes for openid and profile (Scope) (Optional): The openid and profile
scopes are also configured by default because they're required for the OIDC
handler to work, but these may need to be re-added if scopes are included in the
Authentication:Schemes:MicrosoftOidc:Scope configuration. For general
configuration guidance, see Configuration in ASP.NET Core and ASP.NET Core
Blazor configuration.

C#

oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);

SaveTokens: Defines whether access and refresh tokens should be stored in the
AuthenticationProperties after a successful authorization. This property is set to
false by default to reduce the size of the final authentication cookie.

C#

oidcOptions.SaveTokens = false;

Scope for offline access (Scope): The offline_access scope is required for the
refresh token.

C#

oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);

Authority and ClientId: Sets the Authority and Client ID for OIDC calls.

C#

oidcOptions.Authority = "{AUTHORITY}";
oidcOptions.ClientId = "{CLIENT ID}";

Example:
Authority ( {AUTHORITY} ): https://login.microsoftonline.com/a3942615-d115-
4eb7-bc84-9974abcf5064/v2.0/ (uses Tenant ID a3942615-d115-4eb7-bc84-
9974abcf5064 )

Client Id ( {CLIENT ID} ): 4ba4de56-9cef-45d9-83fa-a4c18f9f5f0f

C#

oidcOptions.Authority = "https://login.microsoftonline.com/a3942615-
d115-4eb7-bc84-9974abcf5064/v2.0/";
oidcOptions.ClientId = "4ba4de56-9cef-45d9-83fa-a4c18f9f5f0f";

Example for Microsoft Azure "common" authority:


The "common" authority should be used for multi-tenant apps. You can also use
the "common" authority for single-tenant apps, but a custom IssuerValidator is
required, as shown later in this section.

C#

oidcOptions.Authority =
"https://login.microsoftonline.com/common/v2.0/";

ClientSecret: The OIDC client secret.

The following example is only for testing and demonstration purposes. Don't
store the client secret in the app's assembly or check the secret into source
control. Store the client secret in User Secrets, Azure Key Vault, or an environment
variable.

Authentication scheme configuration is automatically read from


builder.Configuration["Authentication:Schemes:{SCHEME NAME}:{PropertyName}"] ,

where the {SCHEME NAME} placeholder is the scheme, which is MicrosoftOidc by


default. Because configuration is preconfigured, a client secret can automatically
be read via the Authentication:Schemes:MicrosoftOidc:ClientSecret configuration
key. On the server using environment variables, name the environment variable
Authentication__Schemes__MicrosoftOidc__ClientSecret :

.NET CLI

set Authentication__Schemes__MicrosoftOidc__ClientSecret={CLIENT
SECRET}

For demonstration and testing only, the ClientSecret can be set directly. Don't set
the value directly for deployed production apps. For slightly improved security,
conditionally compile the line with the DEBUG symbol:

C#

#if DEBUG
oidcOptions.ClientSecret = "{CLIENT SECRET}";
#endif

Example:

Client secret ( {CLIENT SECRET} ): 463471c8c4...f90d674bc9 (shortened for display)

C#
#if DEBUG
oidcOptions.ClientSecret = "463471c8c4...137f90d674bc9";
#endif

ResponseType: Configures the OIDC handler to only perform authorization code


flow. Implicit grants and hybrid flows are unnecessary in this mode.

In the Entra or Azure portal's Implicit grant and hybrid flows app registration
configuration, do *not select either checkbox for the authorization endpoint to
return Access tokens or ID tokens. The OIDC handler automatically requests the
appropriate tokens using the code returned from the authorization endpoint.

C#

oidcOptions.ResponseType = OpenIdConnectResponseType.Code;

MapInboundClaims and configuration of NameClaimType and RoleClaimType:


Many OIDC servers use " name " and " role " rather than the SOAP/WS-Fed defaults
in ClaimTypes. When MapInboundClaims is set to false , the handler doesn't
perform claims mappings and the claim names from the JWT are used directly by
the app. The following example manually maps the name and role claims:

C#

oidcOptions.MapInboundClaims = false;
oidcOptions.TokenValidationParameters.NameClaimType =
JwtRegisteredClaimNames.Name;
oidcOptions.TokenValidationParameters.RoleClaimType = "role";

Path configuration: Paths must match the redirect URI (login callback path) and
post logout redirect (signed-out callback path) paths configured when registering
the application with the OIDC provider. In the Azure portal, paths are configured in
the Authentication blade of the app's registration. Both the sign-in and sign-out
paths must be registered as redirect URIs. The default values are /signin-oidc and
/signout-callback-oidc .

CallbackPath: The request path within the app's base path where the user-agent
is returned.

In the Entra or Azure portal, set the path in the Web platform configuration's
Redirect URI:

https://localhost/signin-oidc
7 Note

A port isn't required for localhost addresses.

SignedOutCallbackPath: The request path within the app's base path where the
user agent is returned after sign out from the identity provider.

In the Entra or Azure portal, set the path in the Web platform configuration's
Redirect URI:

https://localhost/signout-callback-oidc

7 Note

A port isn't required for localhost addresses.

7 Note

If using Microsoft Identity Web, the provider currently only redirects back
to the SignedOutCallbackPath if the microsoftonline.com Authority
( https://login.microsoftonline.com/{TENANT ID}/v2.0/ ) is used. This
limitation doesn't exist if you can use the "common" Authority with
Microsoft Identity Web. For more information, see postLogoutRedirectUri
not working when authority url contains a tenant ID (AzureAD/microsoft-
authentication-library-for-js #5783) .

RemoteSignOutPath: Requests received on this path cause the handler to invoke


sign-out using the sign-out scheme.

In the Entra or Azure portal, set the Front-channel logout URL:

https://localhost/signout-oidc

7 Note

A port isn't required for localhost addresses.

C#
oidcOptions.CallbackPath = new PathString("{PATH}");
oidcOptions.SignedOutCallbackPath = new PathString("{PATH}");
oidcOptions.RemoteSignOutPath = new PathString("{PATH}");

Examples (default values):

C#

oidcOptions.CallbackPath = new PathString("/signin-oidc");


oidcOptions.SignedOutCallbackPath = new PathString("/signout-callback-
oidc");
oidcOptions.RemoteSignOutPath = new PathString("/signout-oidc");

(Microsoft Azure only with the "common" endpoint)


TokenValidationParameters.IssuerValidator: Many OIDC providers work with the
default issuer validator, but we need to account for the issuer parameterized with
the Tenant ID ( {TENANT ID} ) returned by
https://login.microsoftonline.com/common/v2.0/.well-known/openid-
configuration . For more information, see SecurityTokenInvalidIssuerException with

OpenID Connect and the Azure AD "common" endpoint (AzureAD/azure-


activedirectory-identitymodel-extensions-for-dotnet #1731) .

Only for apps using Microsoft Entra ID or Azure AD B2C with the "common"
endpoint:

C#

var microsoftIssuerValidator =
AadIssuerValidator.GetAadIssuerValidator(oidcOptions.Authority);
oidcOptions.TokenValidationParameters.IssuerValidator =
microsoftIssuerValidator.Validate;

Sample app code


Inspect the sample app for the following features:

Automatic non-interactive token refresh with the help of a custom cookie refresher
( CookieOidcRefresher.cs ).
The PersistingAuthenticationStateProvider class
( PersistingAuthenticationStateProvider.cs ) is a server-side
AuthenticationStateProvider that uses PersistentComponentState to flow the
authentication state to the client, which is then fixed for the lifetime of the
WebAssembly application.
An example requests to the Blazor Web App for weather data is handled by a
Minimal API endpoint ( /weatherforecast ) in the Program file ( Program.cs ). The
endpoint requires authorization by calling RequireAuthorization. For any
controllers that you add to the project, add the [Authorize] attribute to the
controller or action.

Client-side Blazor Web App project


( BlazorWebAppOidc.Client )
The BlazorWebAppOidc.Client project is the client-side project of the Blazor Web App.

The PersistentAuthenticationStateProvider class


( PersistentAuthenticationStateProvider.cs ) is a client-side AuthenticationStateProvider
that determines the user's authentication state by looking for data persisted in the page
when it was rendered on the server. The authentication state is fixed for the lifetime of
the WebAssembly application.

If the user needs to log in or out, a full page reload is required.

The sample app only provides a user name and email for display purposes. It doesn't
include tokens that authenticate to the server when making subsequent requests, which
works separately using a cookie that's included on HttpClient requests to the server.

Redirect to the home page on signout


When a user navigates around the app, the LogInOrOut component
( Layout/LogInOrOut.razor ) sets a hidden field for the return URL ( ReturnUrl ) to the
value of the current URL ( currentURL ). When the user signs out of the app, the identity
provider returns them to the page from which they signed out.

If the user signs out from a secure page, they're returned back to the same secure page
after signing out only to be sent back through the authentication process. This behavior
is fine when users need to switch accounts frequently. However, a alternative app
specification may call for the user to be returned to the app's home page or some other
page after signout. The following example shows how to set the app's home page as the
return URL for signout operations.
The important changes to the LogInOrOut component are demonstrated in the following
example. The value of the hidden field for the ReturnUrl is set to the home page at / .
IDisposable is no longer implemented. The NavigationManager is no longer injected.
The entire @code block is removed.

Layout/LogInOrOut.razor :

razor

@using Microsoft.AspNetCore.Authorization

<div class="nav-item px-3">


<AuthorizeView>
<Authorized>
<form action="authentication/logout" method="post">
<AntiforgeryToken />
<input type="hidden" name="ReturnUrl" value="/" />
<button type="submit" class="nav-link">
<span class="bi bi-arrow-bar-left-nav-menu" aria-
hidden="true">
</span> Logout @context.User.Identity?.Name
</button>
</form>
</Authorized>
<NotAuthorized>
<a class="nav-link" href="authentication/login">
<span class="bi bi-person-badge-nav-menu" aria-
hidden="true"></span>
Login
</a>
</NotAuthorized>
</AuthorizeView>
</div>

Additional resources
AzureAD/microsoft-identity-web GitHub repository : Helpful guidance on
implementing Microsoft Identity Web for Microsoft Entra ID and Azure Active
Directory B2C for ASP.NET Core apps, including links to sample apps and related
Azure documentation. Currently, Blazor Web Apps aren't explicitly addressed by
the Azure documentation, but the setup and configuration of a Blazor Web App for
ME-ID and Azure hosting is the same as it is for any ASP.NET Core web app.
AuthenticationStateProvider service
Manage authentication state in Blazor Web Apps
6 Collaborate with us on
ASP.NET Core feedback
GitHub
ASP.NET Core is an open source
The source for this content can project. Select a link to provide
be found on GitHub, where you feedback:
can also create and review
issues and pull requests. For  Open a documentation issue
more information, see our
contributor guide.  Provide product feedback
Confirmation de compte et récupération
de mot de passe dans ASP.NET Core
Blazor
Article • 09/02/2024

Cet article explique comment configurer une application web ASP.NET Core Blazor avec
confirmation par e-mail et réinitialisation de mot de passe.

Espace de noms
L’espace de noms de l’application utilisé par l’exemple de cet article est BlazorSample .
Mettez à jour les exemples de code pour utiliser l’espace de noms de votre application.

Sélectionner et configurer un fournisseur de


messagerie électronique
Dans cet article, l’API transactionnelle de Mailchimp est utilisée via Mandrill.net
pour envoyer un e-mail. Nous vous recommandons d’utiliser un service de messagerie
pour envoyer des e-mails plutôt que SMTP. SMTP est difficile à configurer et à sécuriser
correctement. Quel que soit le service de messagerie que vous utilisez, accédez à leurs
conseils pour les applications .NET, créez un compte, configurez une clé API pour leur
service et installez les packages NuGet requis.

Créez une classe pour récupérer (fetch) la clé API de messagerie sécurisée. L’exemple de
cet article utilise une classe nommée AuthMessageSenderOptions avec une propriété
EmailAuthKey pour contenir la clé.

AuthMessageSenderOptions :

C#

namespace BlazorSample;

public class AuthMessageSenderOptions


{
public string? EmailAuthKey { get; set; }
}
Enregistrer l’instance de configuration AuthMessageSenderOptions dans le fichier Program
:

C#

builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

Configurer un secret utilisateur pour la clé de


sécurité du fournisseur
Définissez la clé avec l’outil secret-manager. Dans l’exemple suivant, le nom de la clé est
EmailAuthKey et la clé est représentée par l’espace réservé {KEY} . Dans un interpréteur

de commandes, accédez au dossier racine de l’application et exécutez la commande


suivante avec la clé API :

CLI .NET

dotnet user-secrets set "EmailAuthKey" "{KEY}"

Pour plus d’informations, consultez Stockage sécurisé des secrets d’application dans le
développement en ASP.NET Core.

Implémentez IEmailSender
Implémentez IEmailSender pour le fournisseur. L’exemple suivant est basé sur l’API
transactionnelle de Mailchimp à l’aide de Mandrill.net . Pour un autre fournisseur,
reportez-vous à leur documentation sur la façon d’implémenter l’envoi d’un message
dans la méthode Execute .

Components/Account/EmailSender.cs :

C#

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Mandrill;
using Mandrill.Model;
using BlazorSample.Data;

namespace BlazorSample.Components.Account;

public class EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor,


ILogger<EmailSender> logger) : IEmailSender<ApplicationUser>
{
private readonly ILogger logger = logger;

public AuthMessageSenderOptions Options { get; } =


optionsAccessor.Value;

public Task SendConfirmationLinkAsync(ApplicationUser user, string


email,
string confirmationLink) => SendEmailAsync(email, "Confirm your
email",
$"Please confirm your account by " +
"<a href='{confirmationLink}'>clicking here</a>.");

public Task SendPasswordResetLinkAsync(ApplicationUser user, string


email,
string resetLink) => SendEmailAsync(email, "Reset your password",
$"Please reset your password by <a href='{resetLink}'>clicking
here</a>.");

public Task SendPasswordResetCodeAsync(ApplicationUser user, string


email,
string resetCode) => SendEmailAsync(email, "Reset your password",
$"Please reset your password using the following code:
{resetCode}");

public async Task SendEmailAsync(string toEmail, string subject, string


message)
{
if (string.IsNullOrEmpty(Options.EmailAuthKey))
{
throw new Exception("Null EmailAuthKey");
}

await Execute(Options.EmailAuthKey, subject, message, toEmail);


}

public async Task Execute(string apiKey, string subject, string message,


string toEmail)
{
var api = new MandrillApi(apiKey);
var mandrillMessage = new MandrillMessage("luke@guardrex.com",
toEmail,
subject, message);
await api.Messages.SendAsync(mandrillMessage);

logger.LogInformation("Email to {EmailAddress} sent!", toEmail);


}
}

7 Notes
Le contenu du corps des messages peut nécessiter un encodage spécial pour le
fournisseur du service de messagerie. Si le lien dans le corps du message n’est pas
cliquable dans le message électronique, consultez la documentation du fournisseur
du service.

Configurer l’application pour prendre en


charge les e-mails
Dans le fichier Program , remplacez l’implémentation de l’expéditeur d’e-mail par
EmailSender :

diff

- builder.Services.AddSingleton<IEmailSender<ApplicationUser>,
IdentityNoOpEmailSender>();
+ builder.Services.AddSingleton<IEmailSender<ApplicationUser>, EmailSender>
();

Supprimez le IdentityNoOpEmailSender
( Components/Account/IdentityNoOpEmailSender.cs ) de l’application.

Dans le composant RegisterConfirmation


( Components/Account/Pages/RegisterConfirmation.razor ), supprimez le bloc conditionnel
dans le bloc @code qui vérifie si EmailSender est IdentityNoOpEmailSender :

diff

- else if (EmailSender is IdentityNoOpEmailSender)


- {
- ...
- }

En outre, dans le composant RegisterConfirmation , supprimez le balisage Razor et le


code pour vérifier le champ emailConfirmationLink , en laissant uniquement la ligne
demandant à l’utilisateur de vérifier son e-mail ...

diff

- @if (emailConfirmationLink is not null)


- {
- ...
- }
- else
- {
<p>Please check your email to confirm your account.</p>
- }

@code {
- private string? emailConfirmationLink;

...
}

E-mail et délai d’expiration d’activité


Le délai d'inactivité par défaut est de 14 jours. Le code suivant définit le délai
d’expiration d’activité à 5 jours avec une fenêtre d’expiration glissante :

C#

builder.Services.ConfigureApplicationCookie(options => {
options.ExpireTimeSpan = TimeSpan.FromDays(5);
options.SlidingExpiration = true;
});

Modifier les durées de vie de tous les jetons de


protection des données
Le code suivant modifie la période de délai d’expiration de tous les jetons de protection
des données à 3 heures :

C#

builder.Services.Configure<DataProtectionTokenProviderOptions>(options =>
options.TokenLifespan = TimeSpan.FromHours(3));

Les jetons utilisateur Identity intégrés


(AspNetCore/src/Identity/Extensions.Core/src/TokenOptions.cs ) ont un délai
d’expiration d’un jour .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Modifier la durée de vie du jeton d’e-mail


La durée de vie par défaut des jetons utilisateur Identity est d’un jour .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Pour modifier la durée de vie du jeton d’e-mail, ajoutez un


DataProtectorTokenProvider<TUser> et DataProtectionTokenProviderOptions
personnalisés :

CustomTokenProvider.cs :

C#

using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;

namespace BlazorSample;

public class CustomEmailConfirmationTokenProvider<TUser>


: DataProtectorTokenProvider<TUser> where TUser : class
{
public CustomEmailConfirmationTokenProvider(
IDataProtectionProvider dataProtectionProvider,
IOptions<EmailConfirmationTokenProviderOptions> options,
ILogger<DataProtectorTokenProvider<TUser>> logger)
: base(dataProtectionProvider, options, logger)
{
}
}
public class EmailConfirmationTokenProviderOptions
: DataProtectionTokenProviderOptions
{
public EmailConfirmationTokenProviderOptions()
{
Name = "EmailDataProtectorTokenProvider";
TokenLifespan = TimeSpan.FromHours(4);
}
}

public class CustomPasswordResetTokenProvider<TUser>


: DataProtectorTokenProvider<TUser> where TUser : class
{
public CustomPasswordResetTokenProvider(
IDataProtectionProvider dataProtectionProvider,
IOptions<PasswordResetTokenProviderOptions> options,
ILogger<DataProtectorTokenProvider<TUser>> logger)
: base(dataProtectionProvider, options, logger)
{
}
}

public class PasswordResetTokenProviderOptions :


DataProtectionTokenProviderOptions
{
public PasswordResetTokenProviderOptions()
{
Name = "PasswordResetDataProtectorTokenProvider";
TokenLifespan = TimeSpan.FromHours(3);
}
}

Configurer les services pour utiliser le fournisseur de jetons personnalisé dans le fichier
Program :

C#

builder.Services.AddIdentityCore<ApplicationUser>(options =>
{
options.SignIn.RequireConfirmedAccount = true;
options.Tokens.ProviderMap.Add("CustomEmailConfirmation",
new TokenProviderDescriptor(

typeof(CustomEmailConfirmationTokenProvider<ApplicationUser>)));
options.Tokens.EmailConfirmationTokenProvider =
"CustomEmailConfirmation";
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
builder.Services
.AddTransient<CustomEmailConfirmationTokenProvider<ApplicationUser>>();

Résolution des problèmes


Si vous ne parvenez pas à faire fonctionner les e-mails :

Définissez un point d’arrêt dans EmailSender.Execute pour vérifier que


SendEmailAsync est appelé.

Créez une application console pour envoyer un e-mail à l’aide de code similaire à
EmailSender.Execute pour déboguer le problème.
Passez en revue les pages d’historique des e-mails du compte sur le site web du
fournisseur de messagerie.
Vérifiez les messages dans votre dossier de courrier indésirable.
Essayez un autre alias de messagerie sur un autre fournisseur de messagerie, tel
que Microsoft, Yahoo, ou Gmail.
Essayez de faire un envoi à d’autres comptes de messagerie.

2 Avertissement

N’utilisez pas de secrets de production dans le test et le développement. Si vous


publiez l’application sur Azure, définissez les secrets comme paramètres
d’application dans le portail d’application web Azure. Le système de configuration
est configuré pour lire les clés des variables d'environnement.

Activer la confirmation de compte après qu’un


site a des utilisateurs
L’activation de la confirmation de compte sur un site avec des utilisateurs bloque tous
les utilisateurs existants. Les utilisateurs existants sont bloqués, car leurs comptes ne
sont pas confirmés. Pour contourner un blocage utilisateur existant, utilisez l’une des
approches suivantes :

Mettez à jour la base de données pour marquer tous les utilisateurs existants
comme étant confirmés.
Confirmer les utilisateurs existants. Par exemple, envoyez des e-mails par lots avec
des liens de confirmation.
Ressources supplémentaires
Mandrill.net (référentiel GitHub feinoujc/Mandrill.net)
Développeur Mailchimp : API transactionnelle

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Autres scénarios de sécurité ASP.NET
Core Blazor côté serveur
Article • 19/12/2023

Cet article explique comment configurer Blazor côté serveur pour d’autres scénarios de
sécurité, notamment comment passer des jetons à une application Blazor.

Dans cet article, les termes client/côté client et serveur/côté serveur sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.
Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une
application web Blazor.

Pour obtenir des conseils sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrageBlazor et l’emplacement des contenus <head> et
<body>.

Les exemples de composants interactifs présentés dans la documentation n’indiquent


pas de mode d’affichage interactif. Pour rendre les exemples interactifs, il faut soit
hériter d’un mode d’affichage interactif pour un composant enfant à partir d’un
composant parent, soit appliquer un mode d’affichage interactif à une définition de
composant, soit définir globalement le mode d’affichage pour l’ensemble de
l’application. La meilleure façon d’exécuter le code de démonstration est de télécharger
les applications d’exemple BlazorSample_{PROJECT TYPE} à partir du référentiel
dotnet/blazor-samples GitHub .

7 Notes

Les exemples de code de cet article adoptent les types référence null (NRT) et
l'analyse statique de l'état null du compilateur .NET, qui sont pris en charge dans
ASP.NET Core 6.0 ou une version ultérieure. Lorsque vous ciblez ASP.NET Core 5.0
ou version antérieure, supprimez la désignation de type Null ( ? ) des types string? ,
TodoItem[]? , WeatherForecast[]? et IEnumerable<GitHubBranch>? dans les exemples

de l’article.
Passer des jetons à une application Blazor côté
serveur
Les jetons disponibles en dehors des composants Razor d’une application Blazor côté
serveur peuvent être passés aux composants selon l’approche décrite dans cette section.
L’exemple de cette section se concentre sur la transmission de jetons d’accès et
d’actualisation, mais l’approche est valide pour d’autres états de contexte HTTP fournis
par HttpContext.

7 Notes

Le passage du jeton anti-falsification de requête (CSRF/XSRF) aux composants


Razor est utile dans les scénarios où les composants exécutent un POST vers
Identity ou d’autres points de terminaison nécessitant une validation. Toutefois, ne
suivez pas les instructions de cette section pour le traitement des requêtes POST de
formulaire ou des requêtes d’API web avec prise en charge de XSRF. L’infrastructure
Blazor fournit une prise en charge intégrée de la protection contre la falsification
pour les formulaires et l’appel d’API web. Pour plus d’informations, consultez les
ressources suivantes :

Prise en charge générale de la protection contre la falsification :


authentification et autorisation ASP.NET Core Blazor
Prise en charge de l’anti-contrefaçon pour les formulaires : vue d’ensemble
des formulaires Blazor ASP.NET Core
Prise en charge de la protection contre la falsification pour les API web :
appeler une API web à partir d’une application ASP.NET Core Blazor

Authentifiez l’application comme vous le feriez avec une application Pages ou MVC
Razor standard. Provisionnez et enregistrez les jetons dans l’authentification cookie.

Dans le fichier Program :

C#

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

builder.Services.Configure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = true;

options.Scope.Add("offline_access");
});

7 Notes

Les API Microsoft.AspNetCore.Authentication.OpenIdConnect et


Microsoft.IdentityModel.Protocols.OpenIdConnect sont fournies par le package
NuGet Microsoft.AspNetCore.Authentication.OpenIdConnect .

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET,
consultez les articles figurant sous Installer et gérer des packages dans Flux de
travail de la consommation des packages (documentation NuGet). Vérifiez
les versions du package sur NuGet.org .

Si vous le souhaitez, des étendues supplémentaires sont ajoutées avec


options.Scope.Add("{SCOPE}"); , où l’espace réservé {SCOPE} est l’étendue
supplémentaire à ajouter.

Définir un service de fournisseur de jetons qui peut être utilisé dans l’application Blazor
pour résoudre les jetons depuis l’injection de dépendances (DI).

TokenProvider.cs :

C#

public class TokenProvider


{
public string? AccessToken { get; set; }
public string? RefreshToken { get; set; }
}

Dans le fichier Program , ajoutez des services à :

IHttpClientFactory : utilisé dans les classes de service pour obtenir des données à
partir d’une API serveur avec un jeton d’accès. L’exemple de cette section est un
service de données de prévision météorologique ( WeatherForecastService ) qui
nécessite un jeton d’accès.
TokenProvider : contient les jetons d’accès et d’actualisation. Inscrivez le service

fournisseur de jetons en tant que service étendu.

C#

builder.Services.AddHttpClient();
builder.Services.AddScoped<TokenProvider>();

Dans le composant App ( Components/App.razor ), résolvez le service et initialisez-le avec


les données de HttpContext en tant que paramètre en cascade :

razor

@inject TokenProvider TokenProvider

...

@code {
[CascadingParameter]
public HttpContext? HttpContext { get; set; }

protected override Task OnInitializedAsync()


{
TokenProvider.AccessToken = await
HttpContext.GetTokenAsync("access_token");
TokenProvider.RefreshToken = await
HttpContext.GetTokenAsync("refresh_token");

return base.OnInitializedAsync();
}
}

Dans le service qui effectue une requête d’API sécurisée, injectez le fournisseur de jetons
et récupérez le jeton pour la requête d’API :

WeatherForecastService.cs :

C#

using System;
using System.Net.Http;
using System.Threading.Tasks;

public class WeatherForecastService


{
private readonly HttpClient http;
private readonly TokenProvider tokenProvider;

public WeatherForecastService(IHttpClientFactory clientFactory,


TokenProvider tokenProvider)
{
http = clientFactory.CreateClient();
this.tokenProvider = tokenProvider;
}

public async Task<WeatherForecast[]> GetForecastAsync()


{
var token = tokenProvider.AccessToken;
var request = new HttpRequestMessage(HttpMethod.Get,
"https://localhost:5003/WeatherForecast");
request.Headers.Add("Authorization", $"Bearer {token}");
var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();

return await response.Content.ReadFromJsonAsync<WeatherForecast[]>()


??
Array.Empty<WeatherForecast>();
}
}

Définir le schéma d’authentification


Pour une application qui utilise plusieurs intergiciels d’authentification et qui a donc
plusieurs schémas d’authentification, le schéma utilisé par Blazor peut être explicitement
défini dans la configuration du point de terminaison du fichier Program . L’exemple
suivant définit le schéma OpenID Connect (OIDC) :

C#

using Microsoft.AspNetCore.Authentication.OpenIdConnect;

...

app.MapRazorComponents<App>().RequireAuthorization(
new AuthorizeAttribute
{
AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
})
.AddInteractiveServerRenderMode();

Gestionnaire de circuit pour capturer des


utilisateurs des services personnalisés
Utilisez un CircuitHandler pour capturer un utilisateur à partir du
AuthenticationStateProvider et définissez l’utilisateur dans un service. Si vous souhaitez
mettre à jour l’utilisateur, inscrivez un rappel vers AuthenticationStateChanged et mettez
en file d’attente un Task pour obtenir le nouvel utilisateur et mettre à jour le service.
L'exemple suivant illustre l’approche.

Dans l’exemple suivant :

OnConnectionUpAsync est appelé chaque fois que le circuit se reconnecte,


définissant l’utilisateur pour la durée de vie de la connexion. Seule la méthode
OnConnectionUpAsync est obligatoire, sauf si vous implémentez des mises à jour
via un gestionnaire des modifications de l’authentification ( AuthenticationChanged
dans l’exemple suivant).
OnCircuitOpenedAsync est appelé pour joindre le gestionnaire de l’authentification
modifiée, AuthenticationChanged , pour mettre à jour l’utilisateur.
Le bloc catch de la tâche UpdateAuthentication n’effectue aucune action sur les
exceptions, car il n’existe aucun moyen de les signaler à ce stade dans l’exécution
du code. Si une exception est levée à partir de la tâche, l’exception est signalée
ailleurs dans l’application.

UserService.cs :

C#

using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class UserService


{
private ClaimsPrincipal currentUser = new(new ClaimsIdentity());

public ClaimsPrincipal GetUser()


{
return currentUser;
}

internal void SetUser(ClaimsPrincipal user)


{
if (currentUser != user)
{
currentUser = user;
}
}
}

internal sealed class UserCircuitHandler : CircuitHandler, IDisposable


{
private readonly AuthenticationStateProvider
authenticationStateProvider;
private readonly UserService userService;
public UserCircuitHandler(
AuthenticationStateProvider authenticationStateProvider,
UserService userService)
{
this.authenticationStateProvider = authenticationStateProvider;
this.userService = userService;
}

public override Task OnCircuitOpenedAsync(Circuit circuit,


CancellationToken cancellationToken)
{
authenticationStateProvider.AuthenticationStateChanged +=
AuthenticationChanged;

return base.OnCircuitOpenedAsync(circuit, cancellationToken);


}

private void AuthenticationChanged(Task<AuthenticationState> task)


{
_ = UpdateAuthentication(task);

async Task UpdateAuthentication(Task<AuthenticationState> task)


{
try
{
var state = await task;
userService.SetUser(state.User);
}
catch
{
}
}
}

public override async Task OnConnectionUpAsync(Circuit circuit,


CancellationToken cancellationToken)
{
var state = await
authenticationStateProvider.GetAuthenticationStateAsync();
userService.SetUser(state.User);
}

public void Dispose()


{
authenticationStateProvider.AuthenticationStateChanged -=
AuthenticationChanged;
}
}

Dans le fichier Program :

C#
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;

...

builder.Services.AddScoped<UserService>();
builder.Services.TryAddEnumerable(
ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());

Utilisez le service dans un composant pour obtenir l’utilisateur :

razor

@inject UserService UserService

<h1>Hello, @(UserService.GetUser().Identity?.Name ?? "world")!</h1>

Pour définir l’utilisateur dans l’intergiciel pour MVC, Pages Razor et dans d’autres
scénarios d’ASP.NET Core, appelez SetUser sur le UserService dans l’intergiciel
personnalisé une fois l’intergiciel d’authentification exécuté ou définissez l’utilisateur
avec une implémentation IClaimsTransformation. L’exemple suivant adopte l’approche
d’intergiciel.

UserServiceMiddleware.cs :

C#

public class UserServiceMiddleware


{
private readonly RequestDelegate next;

public UserServiceMiddleware(RequestDelegate next)


{
this.next = next ?? throw new ArgumentNullException(nameof(next));
}

public async Task InvokeAsync(HttpContext context, UserService service)


{
service.SetUser(context.User);
await next(context);
}
}

Juste avant l’appel à app.MapRazorComponents<App>() dans le fichier Program , appelez


l’intergiciel :

C#
app.UseMiddleware<UserServiceMiddleware>();

Accès AuthenticationStateProvider dans


l’intergiciel de requête sortante
Le AuthenticationStateProvider d’un DelegatingHandler pour HttpClient créé avec
IHttpClientFactory est accessible dans un intergiciel de requête sortant à l’aide d’un
gestionnaire d’activités de circuit.

7 Notes

Pour obtenir des conseils généraux sur la définition de gestionnaires pour les
requêtes HTTP par HttpClient instances créées à l’aide de IHttpClientFactory dans
les applications ASP.NET Core, consultez les sections suivantes de Effectuer des
requêtes HTTP à l’aide d’IHttpClientFactory dans ASP.NET Core :

Intergiciel de requête sortante


Utiliser DI dans l’intergiciel de requêtes sortantes

L’exemple suivant utilise AuthenticationStateProvider pour attacher un en-tête de nom


d’utilisateur personnalisé pour les utilisateurs authentifiés aux requêtes sortantes.

Tout d’abord, implémentez la classe CircuitServicesAccessor dans la section suivante


de l’article d’injection de dépendances (DI) Blazor :

Accéder aux services côté serveurBlazor à partir d’une autre étendue de DI

Utilisez le CircuitServicesAccessor pour accéder au AuthenticationStateProvider dans


l’implémentation DelegatingHandler.

AuthenticationStateHandler.cs :

C#

public class AuthenticationStateHandler : DelegatingHandler


{
readonly CircuitServicesAccessor circuitServicesAccessor;

public AuthenticationStateHandler(
CircuitServicesAccessor circuitServicesAccessor)
{
this.circuitServicesAccessor = circuitServicesAccessor;
}

protected override async Task<HttpResponseMessage> SendAsync(


HttpRequestMessage request, CancellationToken cancellationToken)
{
var authStateProvider = circuitServicesAccessor.Services
.GetRequiredService<AuthenticationStateProvider>();
var authState = await
authStateProvider.GetAuthenticationStateAsync();
var user = authState.User;

if (user.Identity is not null && user.Identity.IsAuthenticated)


{
request.Headers.Add("X-USER-IDENTITY-NAME", user.Identity.Name);
}

return await base.SendAsync(request, cancellationToken);


}
}

Dans le fichier Program , inscrivez AuthenticationStateHandler et ajoutez le gestionnaire


à IHttpClientFactory qui crée des instances HttpClient :

C#

builder.Services.AddTransient<AuthenticationStateHandler>();

builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<AuthenticationStateHandler>();

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Sécuriser ASP.NET Core Blazor
WebAssembly
Article • 09/02/2024

Les applications Blazor WebAssembly sont sécurisées de la même manière que les
applications monopages (SPA). Il existe plusieurs façons d’authentifier les utilisateurs
auprès des applications monopages, mais l’approche la plus courante et la plus
complète consiste à utiliser une implémentation basée sur le protocole OAuth 2.0 ,
comme OpenID Connect (OIDC) .

La documentation de sécurité Blazor WebAssembly se concentre principalement sur la


façon d’accomplir des tâches d’authentification et d’autorisation des utilisateurs. Pour
obtenir la couverture générale de concept OAuth 2.0/OIDC, consultez l’article de
présentation générale et sa section Ressources supplémentaires.

Sécurité côté client/SPA


Une application Blazor WebAssembly de codebase .NET/C# est fournie aux clients et le
code de l’application ne peut pas être protégé contre l’inspection et la falsification par
les utilisateurs. Ne placez jamais d’élément de nature secrète dans une application
Blazor WebAssembly, comme le code .NET/C# privé, les clés de sécurité, les mots de
passe ou tout autre type d’informations sensibles.

Pour protéger le code .NET/C# et utiliser les fonctionnalités de ASP.NET Core Data
Protection pour sécuriser les données, utilisez une API web ASP.NET Core côté serveur.
Demander à l’application Blazor WebAssembly côté client d’appeler l’API web côté
serveur pour sécuriser les fonctionnalités de l’application et le traitement des données.
Pour plus d’informations, consultez Appeler une API web à partir d’une application
ASP.NET CoreBlazor et les articles de ce nœud.

Bibliothèque d’authentification
Blazor WebAssembly prend en charge l’authentification et l’autorisation des applications
utilisant OIDC via la
bibliothèque Microsoft.AspNetCore.Components.WebAssembly.Authentication à
l’aide de Microsoft Identity Platform. La bibliothèque propose un ensemble de primitives
pour assurer une authentification fluide auprès des back-ends ASP.NET Core. La
bibliothèque peut s’authentifier auprès de n’importe quel fournisseur d’identité (ou IP,
Identity Provider) prenant en charge OIDC, appelés fournisseurs OpenID (ou OP, OpenID
Provider).

La prise en charge de l’authentification dans la Bibliothèque Blazor WebAssembly


( Authentication.js ) repose sur la Bibliothèque d’authentification Microsoft (MSAL,
msal.js) qui est utilisée pour gérer les détails du protocole d’authentification sous-jacent.
La Bibliothèque Blazor WebAssemblyprend en charge uniquement le flux de code
d’autorisation avec PKCE (Proof Key for Code Exchange). L’octroi implicite n’est pas pris
en charge.

D’autres options existent pour l’authentification des applications monopages,


notamment l’utilisation de cookies SameSite. Cependant, la conception technique de
Blazor WebAssembly a retenu OAuth et OIDC comme étant la meilleure option pour
l’authentification dans les applications Blazor WebAssembly. L’authentification à base de
jetons reposant sur des jetons JWT (JSON Web Tokens) a été préférée à
l’authentification basée sur les cookie pour des raisons fonctionnelles et de sécurité :

Les jetons n’étant pas envoyés dans toutes les requêtes, l’utilisation d’un protocole
basé sur les jetons offre une surface d’attaque plus petite.
Les points de terminaison serveur ne nécessite pas de protection contre la
falsification de requête intersites (CSRF), car les jetons sont envoyés explicitement.
Cela vous permet d’héberger des applications Blazor WebAssembly avec des
applications MVC ou Razor Pages.
Les jetons ont des autorisations plus limitées que les cookies. Par exemple, les
jetons ne permettent pas de gérer le compte d’utilisateur ou de changer le mot de
passe d’un utilisateur, à moins que cette fonctionnalité soit explicitement
implémentée.
Les jetons ont une durée de vie courte (une heure par défaut), ce qui limite la
fenêtre d’attaque. De plus, les jetons peuvent être révoqués à tout moment.
Les jetons JWT autonomes offrent des garanties au client et au serveur eu égard au
processus d’authentification. Par exemple, un client a les moyens de détecter et de
valider l’authenticité des jetons qu’il reçoit et de déterminer s’ils ont été émis dans
le cadre d’un processus d’authentification donné. Si un tiers tente de changer de
jeton pendant le processus d’authentification, le client peut détecter le
changement de jeton et éviter de l’utiliser.
Avec OAuth et OIDC, le jetons n’attendent pas de l’agent utilisateur qu’il se
comporte correctement en s’assurant que l’application est sûre.
Les protocoles basés sur des jetons, comme OAuth et OIDC, permettent
d’authentifier et d’autoriser les utilisateurs dans les applications Webassembly
Blazor autonomes avec le même ensemble de caractéristiques de sécurité.
) Important

Pour les versions de ASP.NET Core qui adoptent Duende Identity Server dans les
modèles de projet Blazor, Duende Software peut vous demander de payer des
frais de licence pour l’utilisation de Duende Identity Server en production. Pour plus
d’informations, consultez Migrer de ASP.NET Core 5.0 vers 6.0.

Processus d’authentification avec OIDC


La bibliothèque Microsoft.AspNetCore.Components.WebAssembly.Authentication
propose plusieurs primitives pour implémenter l’authentification et l’autorisation à l’aide
d’OIDC. Globalement, voici comment fonctionne l’authentification :

Lorsqu’un utilisateur anonyme sélectionne le bouton de connexion ou demande un


composant ou une page Razor avec l’[Authorize]attribut appliqué, l’utilisateur est
redirigé vers la page de connexion de l’application ( /authentication/login ).
Dans la page de connexion, la bibliothèque d’authentification prépare une
redirection vers le point de terminaison d’autorisation. Le point de terminaison
d’autorisation se trouvant en dehors de l’application Blazor WebAssembly, il peut
être hébergé séparément à une autre origine. Le point de terminaison est chargé
de déterminer si l’utilisateur est authentifié et d’émettre un ou plusieurs jetons en
réponse. La bibliothèque d’authentification fournit un rappel de connexion pour
recevoir la réponse d’authentification.
Si l’utilisateur n’est pas authentifié, l’utilisateur est redirigé vers le système
d’authentification sous-jacent, qui est généralement ASP.NET Core Identity.
Si l’utilisateur a déjà été authentifié, le point de terminaison d’autorisation
génère les jetons appropriés et redirige le navigateur vers le point de
terminaison de rappel de connexion ( /authentication/login-callback ).
Quand l’application Blazor WebAssembly charge le point de terminaison de rappel
de connexion ( /authentication/login-callback ), la réponse d’authentification est
traitée.
Si le processus d’authentification arrive à son terme, l’utilisateur est authentifié
et éventuellement renvoyé à l’URL protégée initialement demandée par
l’utilisateur.
Si le processus d’authentification échoue pour une raison quelconque,
l’utilisateur est envoyé à la page d’échec de connexion ( /authentication/login-
failed ), et une erreur s’affiche.
Composant Authentication
Le composant Authentication ( Authentication.razor ) gère les opérations
d’authentification à distance et permet à l’application de :

Configurer les routes d’application pour les états d’authentification.


Définir le contenu de l’interface utilisateur pour les états d’authentification.
Gérer les clés d’authentification.

Les actions d’authentification, telles que l’inscription ou la connexion d’un utilisateur,


sont transmises au composant Blazor du framework
RemoteAuthenticatorViewCore<TAuthenticationState>, qui conserve et contrôle l’état
lors des différentes opérations d’authentification.

Pour obtenir des informations et des exemples supplémentaires, consultez Autres


scénarios de sécurité ASP.NET Core Blazor WebAssembly.

Autorisation
Dans les applications Blazor WebAssembly, les contrôles d’autorisation peuvent être
contournés, car tout le code côté client peut être modifié par les utilisateurs. Cela vaut
également pour toutes les technologies d’application côté client, y compris les
infrastructures d’application JavaScript SPA ou les applications natives pour n’importe
quel système d’exploitation.

Effectuez toujours les vérifications d’autorisation sur le serveur au sein des points de
terminaison de l’API auxquels votre application côté client accède.

Personnaliser l’authentification
Blazor WebAssembly fournit des méthodes permettant d’ajouter et de récupérer des
paramètres supplémentaires pour la bibliothèque d’authentification sous-jacente, afin
d’effectuer des opérations d’authentification à distance avec des fournisseurs d’identité
externes.

Pour passer des paramètres supplémentaires, NavigationManager prend en charge le


passage et la récupération de l’état des entrées de l’historique lors de modifications
d’emplacement externe. Pour plus d’informations, consultez les ressources suivantes :

BlazorFondamentaux>Article Routage et navigation


État de l’historique de navigation
Options de navigation
Documentation MDN : API Historique

L’état stocké par l’API Historique offre les avantages suivants pour l’authentification à
distance :

L’état passé au point de terminaison de l’application sécurisée est lié à la


navigation effectuée pour authentifier l’utilisateur au niveau du point de
terminaison authentication/login .
L’encodage et le décodage supplémentaires des données sont évités.
La surface d’attaque est réduite. Contrairement à l’utilisation de la chaîne de
requête pour stocker l’état de navigation, une navigation de niveau supérieur ou
une influence à partir d’une autre origine ne peut pas définir l’état stocké par l’API
Historique.
L’entrée de l’historique est remplacée après une authentification réussie : l’état
attaché à l’entrée de l’historique est donc supprimé et ne nécessite pas de
nettoyage.

InteractiveRequestOptions représente la demande adressée au fournisseur d’identité


pour la connexion ou le provisionnement d’un jeton d’accès.

NavigationManagerExtensions fournit la méthode NavigateToLogin pour une opération


de connexion et NavigateToLogout pour une opération de déconnexion. Les méthodes
appellent NavigationManager.NavigateTo, en définissant l’état de l’entrée de l’historique
avec un InteractiveRequestOptions passé ou une nouvelle instance de
InteractiveRequestOptions créée par la méthode pour :

Un utilisateur se connecte (InteractionType.SignIn) avec l’URI actuel pour l’URL de


retour.
Un utilisateur se déconnecte (InteractionType.SignOut) avec l’URL de retour.

Les scénarios d’authentification suivants sont abordés dans l’article Scénarios de sécurité
supplémentaires de Blazor WebAssembly d’ASP.NET Core :

Personnaliser le processus de connexion


Se déconnecter avec une URL de retour personnalisée
Personnaliser les options avant d’obtenir un jeton de façon interactive
Personnaliser les options lors de l’utilisation d’un IAccessTokenProvider
Obtenir le chemin de connexion à partir des options d’authentification

Exiger une autorisation pour l’application


entière
Appliquez l’attribut [Authorize] (documentation de l’API) à chaque composant Razor de
l’application en utilisant l’une des approches suivantes :

Dans le fichier Imports de l’application, ajoutez une directive @using pour l’espace
de noms Microsoft.AspNetCore.Authorization avec une directive @attribute pour
l’attribut [Authorize].

_Imports.razor :

razor

@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]

Autorisez l’accès anonyme au composant Authentication pour autoriser la


redirection vers le fournisseur d’identité. Ajoutez le code Razor suivant au
composant Authentication sous sa directive @page.

Authentication.razor :

razor

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@attribute [AllowAnonymous]

Ajoutez l’attribut à chaque composant Razor sous la directive @page :

razor

@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]

7 Notes

Le fait de définir une stratégie pour AuthorizationOptions.FallbackPolicy avec


RequireAuthenticatedUsern’est pas pris en charge.

Utiliser une inscription d’application de


fournisseur d’identité par application
Certains des articles sous cette vue d’ensemble concernent des scénarios d’hébergement
Blazor qui impliquent deux applications ou plus. Une application Blazor WebAssembly
autonome utilise l’API web avec des utilisateurs authentifiés pour accéder aux ressources
du serveur et aux données fournies par une application serveur.

Lorsque ce scénario est implémenté dans des exemples de documentation, deux


inscriptions de fournisseur d’identité sont utilisées, l’une pour l’application cliente et
l’autre pour l’application serveur. L’utilisation d’inscriptions distinctes n’est pas
strictement nécessaire, par exemple dans l’ID Microsoft Entra. Toutefois, l’utilisation de
deux inscriptions est une bonne pratique de sécurité, car elle isole les inscriptions par
application. L’utilisation d’inscriptions distinctes permet également une configuration
indépendante des inscriptions client et serveur.

Jetons d’actualisation
Bien que les jetons d’actualisation ne puissent pas être sécurisés dans les applications
Blazor WebAssembly, ils peuvent être utilisés si vous les implémentez avec des
stratégies de sécurité appropriées.

Pour les applications autonomes Blazor WebAssembly dans ASP.NET Core dans .NET 6
ou version ultérieure, nous vous recommandons d’utiliser :

Flux de code d’autorisation OAuth 2.0 (code) avec une clé de preuve pour
l’échange de code (PKCE).
Jeton d’actualisation dont l’expiration est courte.
Jeton d’actualisation pivoté .
Jeton d’actualisation avec une expiration après laquelle un nouveau flux
d’autorisation interactif est nécessaire pour actualiser les informations
d’identification de l’utilisateur.

Pour plus d’informations, consultez les ressources suivantes :

Jetons d’actualisation Plateforme d'identités Microsoft : durée de vie du jeton


d’actualisation
OAuth 2.0 pour les applications basées sur un navigateur (spécification IETF)

Établir des revendications pour les utilisateurs


Les applications nécessitent souvent des revendications pour les utilisateurs reposant
sur un appel d’API web à destination d’un serveur. Par exemple, les revendications sont
souvent utilisées pour établir une autorisation dans une application. Dans ces scénarios,
l’application demande un jeton d’accès pour accéder au service et se sert du jeton pour
obtenir les données utilisateur pour la création de revendications.

Pour des exemples, consultez les ressources suivantes :

Autres scénarios : Personnaliser l’utilisateur


ASP.NET Core Blazor WebAssembly avec des groupes et des rôles ID Microsoft
Entra

Prise en charge du prerendering


Le prérendu (« prerendering » en anglais) n’est pas pris en charge pour les points de
terminaison d’authentification (segment de chemin /authentication/ ).

Pour plus d’informations, consultez Autres scénarios de sécurité ASP.NET Core Blazor
WebAssembly.

Azure App Service sur Linux avec Identity


Server
Spécifiez explicitement l’émetteur quand le déploiement a pour cible Azure App Service
sur Linux avec Identity Server.

Pour plus d’informations, consultez Utiliser Identity pour sécuriser un back-end d’API
web pour des applications monopage.

Authentification Windows
Nous vous déconseillons d’utiliser l’authentification Windows avec Blazor Webassembly
ou tout autre framework SPA. À la place de l’authentification Windows, nous vous
recommandons d’utiliser des protocoles basés sur des jetons, comme OIDC avec les
services de fédération Active Directory (AD FS).

Si l’authentification Windows est utilisée avec Blazor Webassembly ou avec n’importe


quel autre framework SPA, des mesures supplémentaires sont nécessaires pour protéger
l’application contre les jetons de falsification de requête intersite (CSRF). Les inquiétudes
qui s’appliquent à cookie valent aussi pour l’authentification Windows avec en outre le
fait que cette dernière n’offre aucun mécanisme pour empêcher le partage du contexte
d’authentification entre les origines. Les applications qui utilisent l’authentification
Windows sans une protection supplémentaire contre les jetons CSRF doivent au moins
être limitées à l’intranet d’une organisation et ne pas être utilisées sur Internet.
Pour plus d’informations, consultez Prévenir les attaques par falsification de requête
intersites (XSRF/CSRF) dans ASP.NET Core.

Sécuriser un hub SignalR


Pour sécuriser un hub SignalR dans le projet d’API serveur, appliquez l’attribut
[Authorize] à la classe hub ou aux méthodes de la classe hub.

Dans un projet client avec un prérendu, tel que Blazor WebAssembly hébergé (ASP.NET
Core dans .NET 7 ou une version antérieure) ou une application web Blazor (ASP.NET
Core dans .NET 8 ou version ultérieure), consultez les instructions dans l’aide ASP.NET
Core BlazorSignalR.

Dans un composant de projet client sans prérendu, tel que Blazor WebAssembly
autonome ou les applications non-navigateurs, fournissez un jeton d’accès à la
connexion hub, comme l’illustre l’exemple suivant. Pour plus d’informations, consultez
Authentification et autorisation dans ASP.NET Core SignalR.

razor

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
@inject NavigationManager Navigation

...

var tokenResult = await TokenProvider.RequestAccessToken();

if (tokenResult.TryGetToken(out var token))


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"),
options => { options.AccessTokenProvider = () =>
Task.FromResult(token?.Value); })
.Build();

...
}

Journalisation
Cette section s’applique aux applications Blazor WebAssembly dans ASP.NET Core dans
.NET 7 ou version ultérieure.
Pour activer la journalisation de débogage ou de suivi, consultez la section
Journalisation de l’authentification (Blazor WebAssembly) dans une version 7.0 ou
ultérieure de l’article de journalisation ASP.NET CoreBlazor.

Bac à sable WebAssembly


Le bac à sable WebAssembly limite l’accès à l’environnement du système exécutant du
code WebAssembly, y compris l’accès aux sous-systèmes d’E/S, au stockage système et
aux ressources, ainsi qu’au système d’exploitation. L’isolation entre le code
WebAssembly et le système qui exécute le code fait de WebAssembly une infrastructure
de codage sécurisée pour les systèmes. Toutefois, WebAssembly est vulnérable aux
attaques par canal latéral au niveau du matériel. Des précautions normales et une
diligence raisonnable dans l’approvisionnement en matériel et en plaçant des limitations
sur l’accès au matériel s’appliquent.

WebAssembly n’est pas détenu ou géré par Microsoft.

Pour plus d’informations, consultez les ressources W3C suivantes :

WebAssembly : Sécurité
Spécification WebAssembly : Considérations relatives à la sécurité
W3C WebAssembly Community Group : commentaires et problèmes : le lien
W3C WebAssembly Community Group est fourni uniquement à titre de référence,
ce qui indique clairement que les vulnérabilités et bogues de sécurité
WebAssembly sont corrigés en continu, souvent signalés et traités par le
navigateur. N’envoyez pas de commentaires ou de rapports de bogues Blazor au
W3C WebAssembly Community Group.Blazor les commentaires doivent être
signalés à l’unité de produit Microsoft ASP.NET Core . Si l’unité de produit
Microsoft détermine qu’il existe un problème sous-jacent avec WebAssembly, elle
prend les mesures appropriées pour signaler le problème au groupe de la
communauté WebAssembly W3C.

Conseils de mise en œuvre


Les articles de cette Vue d’ensemble fournissent des informations sur l’authentification
des utilisateurs dans les applications Blazor WebAssembly auprès de certains
fournisseurs.

Applications Blazor WebAssembly autonomes :

Aide générale pour les fournisseurs OIDC et la bibliothèque d’authentification


WebAssembly
Comptes Microsoft
ID Microsoft Entra (ME-ID)
Azure Active Directory (AAD) B2C

Vous trouverez une aide supplémentaire pour la configuration dans les articles suivants :

Autres scénarios de sécurité ASP.NET Core Blazor WebAssembly


Utiliser l’API Graph avec ASP.NET Core Blazor WebAssembly

Utiliser le flux de code d’autorisation


avec PKCE (Proof Key for Code Exchange)
La Bibliothèque d’authentification Microsoft pour JavaScript (MSAL) v2.0 ou ultérieure,
de la Plateforme d’identités Microsoft prend en charge le flux de code d’autorisation
avec Clé de preuve pour l’échange de code (PKCE) et Partage des ressources Cross-
Origin (CORS) pour les applications monopages, dont Blazor.

Microsoft ne recommande pas l’utilisation de l’octroi implicite.

Pour plus d’informations, consultez les ressources suivantes :

Prise en charge du flux d’authentification dans MSAL : octroi implicite


Plateforme d’identités Microsoft et flux d’octroi implicite : préférer le flux de code
d’authentification
Plateforme d’identités Microsoft et flux de code d’autorisation OAuth

Ressources supplémentaires
Documentation sur la plateforme d’identités Microsoft
Documentation générale
Jetons d’accès
Configurer ASP.NET Core pour l’utilisation de serveurs proxy et d’équilibreurs de
charge
Utilisation du middleware Forwarded Headers pour préserver les informations
de schéma HTTPS sur les serveurs proxy et les réseaux internes.
Autres scénarios et cas d’usage, notamment la configuration manuelle de
schéma, la modification des chemins de requêtes pour assurer le bon routage
des requêtes et le transfert du schéma de requête pour les proxys inverses Linux
et non IIS.
Prérendu avec authentification
WebAssembly : Sécurité
Spécification WebAssembly : Considérations relatives à la sécurité

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Sécuriser ASP.NET Core Blazor
WebAssembly avec ASP.NET Core
Identity
Article • 09/02/2024

Les applications autonomes Blazor WebAssembly peuvent être sécurisées avec ASP.NET
Core Identity en suivant les instructions de cet article.

Points de terminaison pour l’inscription, la


connexion et la déconnexion
Au lieu d’utiliser l’interface utilisateur par défaut fournie par ASP.NET Core Identity pour
les applications SPA et Blazor, qui est basée sur Razor Pages, appelez MapIdentityApi
une API back-end pour ajouter des JSpoints de terminaison d’API ON pour l’inscription
et la journalisation des utilisateurs avec ASP.NET Core Identity. Les points de terminaison
d’API Identity prennent également en charge les fonctionnalités avancées, telles que
l’authentification à deux facteurs et la vérification par e-mail.

Sur le client, appelez le point de terminaison /register pour inscrire un utilisateur avec
son adresse e-mail et son mot de passe :

C#

var result = await _httpClient.PostAsJsonAsync(


"register", new
{
email,
password
});

Sur le client, connectez-vous à un utilisateur avec l’authentification cookie à l’aide du


point de terminaison /login avec la chaîne de requête useCookies définie sur true :

C#

var result = await _httpClient.PostAsJsonAsync(


"login?useCookies=true", new
{
email,
password
});

L’API du serveur principal établit l’authentification cookie avec un appel à


AddIdentityCookies sur le générateur d’authentification :

C#

builder.Services
.AddAuthentication(IdentityConstants.ApplicationScheme)
.AddIdentityCookies();

Jeton d’authentification
Pour les scénarios natifs et mobiles dans lesquels certains clients ne prennent pas en
charge les cookies, l’API de connexion fournit un paramètre pour demander des jetons.
Un jeton personnalisé (privé pour la plateforme ASP.NET Core Identity) est émis et peut
être utilisé pour authentifier les requêtes suivantes. Vous devez passer le jeton dans l’en-
tête Authorization en tant que jeton du porteur. Un jeton d’actualisation est également
fourni. Ce jeton permet à l’application de demander un nouveau jeton lorsque l’ancien
expire sans forcer l’utilisateur à se reconnecter.

Les jetons ne sont pas des jetons JSON Web Tokens (JWT) standard. L’utilisation de
jetons personnalisés est intentionnelle, car l’API intégrée Identity est destinée
principalement aux scénarios simples. L’option de jeton n’est pas destinée à être un
fournisseur de services d’identité ou un serveur de jetons complet, mais plutôt une
alternative à l’option cookie pour les clients qui ne peuvent pas utiliser les cookie.

L’aide suivante vous permet de démarrer le processus d’implémentation de


l’authentification par jeton avec l’API de connexion. L’ajout de code personnalisé est
nécessaire pour terminer l’implémentation. Pour plus d’informations, consultez Utiliser
Identity pour sécuriser un back-end d’API web pour des applications monopage.

L’API du serveur principal n’établit pas l’authentification par cookie avec un appel à
AddIdentityCookies sur le générateur d’authentification. Au lieu de cela, l’API du serveur
configure l’authentification par jeton du porteur avec la méthode d’extension
AddBearerToken. Spécifiez le schéma pour les jetons d’authentification du porteur avec
IdentityConstants.BearerScheme.

Dans Backend/Program.cs , remplacez les services d’authentification et la configuration


par ce qui suit :
C#

builder.Services
.AddAuthentication()
.AddBearerToken(IdentityConstants.BearerScheme);

Dans BlazorWasmAuth/Identity/CookieAuthenticationStateProvider.cs , supprimez le


paramètre de chaîne de requête useCookies dans la méthode LoginAsync de
CookieAuthenticationStateProvider :

diff

- /login?useCookies=true
+ /login

À ce stade, vous devez fournir du code personnalisé pour analyser


AccessTokenResponse sur le client et gérer les jetons d’accès et d’actualisation. Pour
plus d’informations, consultez Utiliser Identity pour sécuriser un back-end d’API web
pour des applications monopage.

Autres scénarios liés à Identity


Pour obtenir d’autres scénarios liés à Identity fournis par l’API, consultez Utiliser Identity
pour sécuriser un back-end d’API web pour les applications monopages :

Sécuriser les points de terminaison sélectionnés


Jeton d’authentification
Authentification à deux facteurs (2FA)
des codes de récupération
Gestion des informations utilisateur

Exemples d’applications
Dans cet article, les exemples d’applications servent de référence pour les applications
autonomes Blazor WebAssembly qui accèdent à ASP.NET Core Identity par le biais d’une
API web principale. La démonstration comprend deux applications :

Backend : application API web principale qui gère un magasin d’identités utilisateur

pour ASP.NET Core Identity.


BlazorWasmAuth : application frontale autonome Blazor WebAssembly avec

authentification utilisateur.
Accédez aux exemples d’applications par le biais du dossier de version le plus récent à
partir de la racine du référentiel avec le lien suivant. Les exemples sont fournis pour .NET
8 ou version ultérieure. Reportez-vous au README fichier dans le
BlazorWebAssemblyStandaloneWithIdentity dossier pour connaître les étapes d’exécution

des exemples d’applications.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Packages et code d’application API web back-


end
L’application API web principale gère un magasin d’identités utilisateur pour ASP.NET
Core Identity.

Packages
L’application utilise les packages NuGet suivants :

Microsoft.AspNetCore.Identity
Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.EntityFrameworkCore.InMemory

Si votre application doit utiliser un autre EF Core fournisseur de base de données que le
fournisseur en mémoire, ne créez pas de référence de package dans votre application
pour Microsoft.EntityFrameworkCore.InMemory .

Dans le fichier projet de l’application ( .csproj ), la globalisation invariante est


configurée.

Exemple de code d’application


Les paramètres d’application configurent les URL dorsale et frontale :

Backend application ( BackendUrl ) : https://localhost:7211

BlazorWasmAuth application ( FrontendUrl ) : https://localhost:7171

Le Backend.http fichier peut être utilisé pour tester la requête de données


météorologiques. Notez que l’application BlazorWasmAuth doit s’exécuter pour tester le
point de terminaison et que le point de terminaison est codé en dur dans le fichier. Pour
en savoir plus, reportez-vous à Utiliser des fichiers .http dans Visual Studio 2022.
L’installation et la configuration suivantes se trouvent dans le fichier de
Programl’application .

L’identité de l’utilisateur avec cookie authentification est ajoutée en appelant


AddAuthentication et AddIdentityCookies. Les services pour les vérifications
d’autorisation sont ajoutés par un appel à AddAuthorizationBuilder.

Uniquement recommandé pour les démonstrations, l’application utilise le EF


Corefournisseur de base de données en mémoire pour l’inscription du contexte de base
de données (AddDbContext). Le fournisseur de base de données en mémoire facilite le
redémarrage de l’application et teste les flux utilisateur d’inscription et de connexion.
Toutefois, chaque exécution commence par une nouvelle base de données. Si la base de
données est remplacée par SQLite, les utilisateurs sont enregistrés entre les sessions,
mais la base de données doit être créée par le biais des migrations, comme indiqué
dans le EF Core didacticielde démarrage. Vous pouvez utiliser d’autres fournisseurs
relationnels tels que SQL Server pour votre code de production.

Configurez Identity pour utiliser la base de données EF Core et exposer les points de
terminaison Identity via les appels à AddIdentityCore, AddEntityFrameworkStores et
AddApiEndpoints.

Une politique de partage des ressources entre origines multiples (CORS) est définie pour
autoriser les demandes provenant des applications dorsale et frontale. Les URL de
secours sont configurées pour la stratégie CORS si les paramètres de l’application ne les
fournissent pas :

Backend application ( BackendUrl ) : https://localhost:5001

BlazorWasmAuth application ( FrontendUrl ) : https://localhost:5002

Les services et les points de terminaison pour Swagger/OpenAPI sont inclus pour la
documentation de l’API web et les tests de développement.

Les itinéraires sont mappés pour Identity les points de terminaison en appelant
MapIdentityApi<AppUser>() .

Un point de terminaison de déconnexion ( /Logout ) est configuré dans le pipeline de


l’intergiciel pour déconnecter les utilisateurs.

Pour sécuriser un point de terminaison, ajoutez la RequireAuthorization méthode


d’extension à la définition de routage. Pour un contrôleur, ajoutez l’attribut [Authorize]
au contrôleur ou à l’action.

Pour en savoir plus sur les modèles de base pour l’initialisation et la configuration d’une
DbContext instance, reportez-vous à DbContext Lifetime, Configuration et Initialization
dans la EF Core documentation.

Packages et code d’application Blazor


WebAssembly autonome frontend
Une application frontale autonome Blazor WebAssembly illustre l’authentification et
l’autorisation des utilisateurs pour accéder à une page web privée.

Packages
L’application utilise les packages NuGet suivants :

Microsoft.AspNetCore.Components.WebAssembly.Authentication
Microsoft.Extensions.Http
Microsoft.AspNetCore.Components.WebAssembly
Microsoft.AspNetCore.Components.WebAssembly.DevServer

Exemple de code d’application


Le Models dossier contient les modèles de l’application :

FormResult (Identity/Models/FormResult.cs) : réponse pour la connexion et


l’inscription.
UserBasic (Identity/Models/UserBasic.cs) : informations utilisateur de base pour
s’inscrire et se connecter.
UserInfo (Identity/Models/UserInfo.cs) : informations utilisateur du point de
terminaison d’identité pour établir des revendications.

L’interface IAccountManagement (Identity/CookieHandler.cs) fournit des services de


gestion de compte.

La classe CookieAuthenticationStateProvider
(Identity/CookieAuthenticationStateProvider.cs) gère l’état de cookiel’authentification
basée sur les comptes et fournit des implémentations de service de gestion des
comptes décrites par l’interface IAccountManagement . La méthode LoginAsync active
explicitement l’authentification cookie via la valeur de chaîne de requête useCookies de
true .

La classe CookieHandler (Identity/CookieHandler.cs) garantit que les informations


d’identification cookie sont envoyées avec chaque requête à l’API web principale, qui
gère Identity et gère le magasin de données Identity.
wwwroot/appsettings.file fournit des points de terminaison d’URL frontale et dorsale

Le composantApp expose l’état d’authentification en tant que paramètre en cascade.


Pour plus d’informations, consultez ASP.NET Core Blazor Authentification et autorisation.

Le MainLayout composant et NavMenule composant utilisent le


AuthorizeViewcomposant pour afficher de manière sélective du contenu en fonction de
l’état d’authentification de l’utilisateur.

Les composants suivants gèrent les tâches courantes d’authentification des utilisateurs,
utilisant des IAccountManagement services :

Register composant (Components/Identity/Register.razor)


Login composant (Components/Identity/Login.razor)
Logout composant (Components/Identity/Logout.razor)

Le composant PrivatePage (Components/Pages/PrivatePage.razor) nécessite


l’authentification et affiche les revendications de l’utilisateur.

Les services et la configuration sont fournis dans le fichier Program (Program.cs) :

Le gestionnaire cookie est inscrit en tant que service étendu.


Les services d’autorisation sont inscrits.
Le fournisseur d’état d’authentification personnalisé est inscrit en tant que service
étendu.
L’interface de gestion de compte ( IAccountManagement ) est inscrite.
L’URL de l’hôte de base est configurée pour une instance de client HTTP inscrite.
L’URL principale de base est configurée pour une instance de client HTTP inscrite
utilisée pour les interactions d’authentification avec l’API web principale. Le client
HTTP utilise le cookie gestionnaire pour s’assurer que cookie les informations
d’identification sont envoyées avec chaque requête.

Appelez AuthenticationStateProvider.NotifyAuthenticationStateChanged lorsque l’état


d’authentification de l’utilisateur change. Pour obtenir un exemple, reportez-vous aux
LoginAsync et aux LogoutAsync méthodes de la

CookieAuthenticationStateProviderclasse (Identity/CookieAuthenticationStateProvider.cs
) .

2 Avertissement

Le composant AuthorizeView affiche de manière sélective le contenu d’interface


utilisateur en fonction de l’autorisation dont dispose l’utilisateur. Tout le contenu
d’une Blazor WebAssembly application placé dans un AuthorizeView composant
est détectable sans authentification. Le contenu sensible doit donc être obtenu à
partir d’une API web basée sur un serveur principal une fois l’authentification
terminée. Pour plus d’informations, consultez les ressources suivantes :

Authentification et autorisation avec ASP.NET Core Blazor


Appeler une API web à partir d’une application ASP.NET Core Blazor
Autres scénarios de sécurité ASP.NET Core Blazor WebAssembly

Rôles
Pour des raisons de sécurité, les revendications de rôle ne sont pas renvoyées à partir du
point de terminaison manage/info pour créer UserInfo.Claims pour les utilisateurs de
l’application BlazorWasmAuth .

Pour créer des revendications de rôle par vous-même, effectuez une requête distincte
dans la méthode GetAuthenticationStateAsync de CookieAuthenticationStateProvider
après l’authentification de l’utilisateur auprès d’une API web personnalisée dans le projet
Backend qui fournit des rôles d’utilisateur à partir du magasin de données utilisateur.

Nous avons prévu de fournir des conseils sur ce sujet. Les avancées sont suivies dans
l’article Instructions de revendications de rôle dans le WASM autonome w/Identity
(dotnet/AspNetCore.Docs #31045) .

Résolution des problèmes

Journalisation
Pour activer la journalisation du débogage ou des traces dans le cadre de
l’authentification Blazor WebAssembly, consultez Journalisation ASP.NET Core Blazor.

Erreurs courantes
Vérifiez la configuration de chaque projet. Confirmez que les URL sont correctes :

Projet Backend
appsettings.json

BackendUrl
FrontendUrl

Backend.http : Backend_HostAddress

Projet BlazorWasmAuth : wwwroot/appsettings.json


BackendUrl
FrontendUrl

Si la configuration semble correcte :

Analysez les journaux des applications.

Examinez le trafic réseau entre l’application BlazorWasmAuth et l’application


Backend à l’aide des outils de développement du navigateur. Un message d’erreur

exact ou un message indiquant la cause du problème est souvent retourné au


client par l’application back-end à la suite d’une demande. Vous trouverez des
conseils d’aide sur les outils de développement dans les articles suivants :

Google Chrome (documentation Google)

Microsoft Edge

Mozilla Firefox (documentation Mozilla)

L’équipe Documentation répond pour documenter les commentaires et les bogues des
articles. Ouvrez un problème en utilisant le lien Ouvrir un problème de documentation
en bas de l’article. L’équipe n’est pas en mesure de fournir un support technique.
Plusieurs forums de support publics sont disponibles pour vous aider à résoudre les
problèmes liés à une application. Nous recommandons ce qui suit :

Dépassement de capacité de la pile (étiquette : blazor)


Équipe ASP.NET Core Slack
Blazor Gitter

Les forums précédents ne sont pas détenus ou contrôlés par Microsoft.

Pour les rapports de bogues de framework reproductibles, non liés à la sécurité, non
sensibles et non confidentiels, ouvrez un problème auprès de l’unité de produit ASP.NET
Core . N’ouvrez pas de problème auprès de l’unité de produit tant que vous n’avez pas
investigué de manière approfondie la cause du problème, sans pouvoir le résoudre par
vous-même ou avec l’aide de la communauté sur un forum de support public. L’unité de
produit ne peut pas résoudre les problèmes d’applications individuelles qui sont
défaillantes en raison d’une mauvaise configuration ou de cas d’usage impliquant des
services tiers. Si un rapport est de nature sensible ou confidentielle, ou s’il décrit une
faille de sécurité potentielle dans le produit que des attaquants peuvent exploiter,
consultez Signalement des problèmes de sécurité et des bogues (dépôt GitHub
dotnet/aspnetcore) .

Cookies et données de site


Les Cookies et les données de site peuvent persister entre les mises à jour d’application,
et interférer avec les tests et la résolution des problèmes. Effacez ce qui suit quand vous
apportez des changements au code d’application, au compte d’utilisateur ou à la
configuration de l’application :

cookies de connexion de l’utilisateur


cookies d’application
Données de site mises en cache et stockées

Il existe une approche qui permet d’empêcher les cookie et les données de site
persistants d’interférer avec les tests et la résolution des problèmes. Elle consiste à :

Configurer un navigateur
Utilisez un navigateur de test que vous pouvez configurer pour supprimer tous
les cookies et toutes les données de site à chaque fois qu’il se ferme.
Vérifiez que le navigateur est fermé manuellement ou par l’IDE chaque fois
qu’un changement est apporté à la configuration de l’application, de l’utilisateur
de test ou du fournisseur.
Utilisez une commande personnalisée pour ouvrir un navigateur en mode InPrivate
ou Incognito dans Visual Studio :
Ouvrez la boîte de dialogue Parcourir avec à partir du bouton Exécuter de
Visual Studio.
Cliquez sur le bouton Ajouter.
Indiquez le chemin de votre navigateur dans le champ Programme. Les chemins
d’exécutables suivants sont des emplacements d’installation classiques de
Windows 10. Si votre navigateur est installé à un autre emplacement, ou si vous
n’utilisez pas Windows 10, indiquez le chemin de l’exécutable du navigateur.
Microsoft Edge : C:\Program Files
(x86)\Microsoft\Edge\Application\msedge.exe

Google Chrome : C:\Program Files


(x86)\Google\Chrome\Application\chrome.exe

Mozilla Firefox : C:\Program Files\Mozilla Firefox\firefox.exe


Dans le champ Arguments, indiquez l’option de ligne de commande utilisée par
le navigateur pour qu’il s’ouvre en mode InPrivate ou Incognito. Certains
navigateurs nécessitent l’URL de l’application.
Microsoft Edge : Utilisez -inprivate .
Google Chrome : Utilisez --incognito --new-window {URL} , où l’espace
réservé {URL} correspond à l’URL à ouvrir (par exemple
https://localhost:5001 ).

Mozilla Firefox : Utilisez -private -url {URL} , où l’espace réservé {URL}


correspond à l’URL à ouvrir (par exemple https://localhost:5001 ).
Indiquez un nom dans le champ Nom convivial. Par exemple, Firefox Auth
Testing

Cliquez sur le bouton OK.


Pour éviter d’avoir à sélectionner le profil de navigateur pour chaque itération
de test avec une application, définissez le profil en tant que profil par défaut
avec le bouton Par défaut.
Vérifiez que le navigateur est fermé par l’IDE chaque fois qu’un changement est
apporté à la configuration de l’application, de l’utilisateur de test ou du
fournisseur.

Mises à niveau d’application


Une application fonctionnelle peut échouer immédiatement après la mise à niveau du
kit SDK .NET Core sur l’ordinateur de développement ou la modification des versions de
package au sein de l’application. Dans certains cas, les packages incohérents peuvent
bloquer une application quand vous effectuez des mises à niveau majeures. Vous
pouvez résoudre la plupart de ces problèmes en suivant les instructions suivantes :

1. Effacez les caches de package NuGet du système local en exécutant dotnet nuget
locals all --clear à partir d’un interpréteur de commandes.
2. Supprimez les dossiers bin et obj du projet.
3. Restaurez et regénérez le projet.
4. Supprimez tous les fichiers du dossier de déploiement sur le serveur avant de
redéployer l’application.

7 Notes

L’utilisation de versions de package incompatibles avec le framework cible de


l’application n’est pas prise en charge. Pour plus d’informations sur un package,
utilisez la Galerie NuGet ou l’Explorateur de packages FuGet .

Inspecter les revendications des utilisateurs


Pour résoudre les problèmes liés aux revendications des utilisateurs, le composant
UserClaims suivant peut être utilisé directement dans les applications ou servir de base

à une personnalisation.

UserClaims.razor :

razor
@page "/user-claims"
@using System.Security.Claims
@attribute [Authorize]

<PageTitle>User Claims</PageTitle>

<h1>User Claims</h1>

**Name**: @AuthenticatedUser?.Identity?.Name

<h2>Claims</h2>

@foreach (var claim in AuthenticatedUser?.Claims ?? Array.Empty<Claim>())


{
<p class="claim">@(claim.Type): @claim.Value</p>
}

@code {
[CascadingParameter]
private Task<AuthenticationState>? AuthenticationState { get; set; }

public ClaimsPrincipal? AuthenticatedUser { get; set; }

protected override async Task OnInitializedAsync()


{
if (AuthenticationState is not null)
{
var state = await AuthenticationState;
AuthenticatedUser = state.User;
}
}
}

Ressources supplémentaires
Nouveautés de l’identité dans .NET 8
AuthenticationStateProvider service
Négociation entre origines SignalR côté client pour l’authentification

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus
d’informations, consultez notre  Ouvrir un problème de
guide du contributeur. documentation

 Indiquer des commentaires sur


le produit
Sécuriser une application autonome
ASP.NET Core Blazor WebAssembly avec
la bibliothèque d’authentification
Article • 09/02/2024

Cet article explique comment sécuriser une application autonome ASP.NET Core Blazor
WebAssembly avec la bibliothèque d’authentification Blazor WebAssembly.

La bibliothèque d’authentification Blazor WebAssembly ( Authentication.js ) prend


uniquement en charge le flux de code d’autorisation PKCE (Proof Key for Code
Exchange) via la Microsoft Authentication Library (MSAL, msal.js). Pour implémenter
d’autres flux d’octroi, accédez aux instructions MSAL pour implémenter la bibliothèque
MSAL directement, mais nous ne prenons pas en charge ni ne recommandons
l’utilisation de flux d’octroi autres que PKCE pour les applications Blazor.

Pour obtenir des conseils sur Microsoft Entra (ME-ID) et Azure Active Directory B2C (AAD
B2C), ne suivez pas les conseils de cette rubrique. Consultez Sécuriser une application
autonome Blazor WebAssembly ASP.NET Core avec ID Microsoft Entra ou Sécuriser une
application autonome Blazor WebAssembly ASP.NET Core avec Azure Active Directory
B2C.

Pour obtenir une couverture supplémentaire du scénario de sécurité, après avoir lu cet
article, consultez ASP.NET Core Blazor WebAssembly scénarios de sécurité
supplémentaires.

Procédure pas à pas


Les sous-sections de la procédure pas à pas expliquent comment :

Inscrire une application


Créer l’application Blazor
Exécuter l’application

Inscrire une application


Inscrivez une application auprès d’un fournisseur d’identité (Identity Provider) OIDC
(OpenID Connect) en suivant les conseils d’aide fournis par le responsable du
fournisseur d’identité.
Enregistrez les informations suivantes :

Autorité (par exemple https://accounts.google.com/ ).


ID d’application (client) (par exemple 2...7-e...q.apps.googleusercontent.com ).
Configuration supplémentaire du fournisseur d’identité (consultez la
documentation du fournisseur d’identité).

7 Notes

Le fournisseur d’identité doit utiliser OIDC. Par exemple, le fournisseur d’identité de


Facebook n’est pas un fournisseur conforme à OIDC. Les conseils d’aide fournis
dans cette rubrique ne s’appliquent donc pas au fournisseur d’identité de
Facebook. Pour plus d’informations, consultez Sécuriser ASP.NET Core Blazor
WebAssembly.

Créer l’application Blazor


Pour créer une application Blazor WebAssembly autonome qui utilise la bibliothèque
Microsoft.AspNetCore.Components.WebAssembly.Authentication , suivez les conseils
d’aide relatifs au choix des outils. Si vous ajoutez la prise en charge de l’authentification,
consultez la section Parties de l’application de cet article pour obtenir des conseils
d’aide sur l’installation et la configuration de l’application.

Visual Studio

Pour créer un projet Blazor WebAssembly avec un mécanisme d’authentification :

Une fois que vous avez choisi le modèle Application Blazor WebAssembly, affectez
à Type d’authentification la valeur Comptes individuels.

La sélection de Comptes individuels permet d’utiliser le système Identity d’ASP.NET


Core. Cette sélection ajoute la prise en charge de l’authentification. Elle n’entraîne
pas le stockage des utilisateurs dans une base de données. Les sections suivantes
de cet article fournissent plus de détails.

Configurer l’application
Configurez l’application en suivant les conseils d’aide relatifs au fournisseur d’identité.
Au minimum, l’application nécessite les paramètres de configuration Local:Authority et
Local:ClientId dans le fichier wwwroot/appsettings.json de l’application :
JSON

{
"Local": {
"Authority": "{AUTHORITY}",
"ClientId": "{CLIENT ID}"
}
}

Exemple OIDC Google OAuth 2.0 pour une application qui s’exécute à l’adresse
localhost sur le port 5001 :

JSON

{
"Local": {
"Authority": "https://accounts.google.com/",
"ClientId": "2...7-e...q.apps.googleusercontent.com",
"PostLogoutRedirectUri": "https://localhost:5001/authentication/logout-
callback",
"RedirectUri": "https://localhost:5001/authentication/login-callback",
"ResponseType": "id_token"
}
}

L’URI de redirection ( https://localhost:5001/authentication/login-callback ) est inscrit


dans la console d’API Google , dans Informations d’identification> {NAME} >URI de
redirection autorisés, où {NAME} représente le nom de client de l’application dans la
liste des applications avec ID client OAuth 2.0 de la console d’API Google.

7 Notes

L’indication du numéro de port d’un URI de redirection localhost n’est pas


nécessaire pour certains fournisseurs d’identité OIDC, conformément à la
spécification OAuth 2.0 . Certains fournisseurs d’identité permettent à l’URI de
redirection des adresses de bouclage d’omettre le port. D’autres autorisent
l’utilisation d’un caractère générique pour le numéro de port (par exemple * ). Pour
plus d’informations, consultez la documentation du fournisseur d’identité.

Exécuter l’application
Utilisez l’une des approches suivantes pour exécuter l’application :

Visual Studio
Sélectionnez le bouton Run.
Utilisez Déboguer>Démarrer le débogage dans le menu.
Appuyez sur F5 .
Interpréteur de commandes .NET CLI : exécutez la commande dotnet run à partir
du dossier de l’application.

Parties de l’application
Cette section décrit les parties d’une application générées à partir du modèle de projet
Blazor WebAssembly ainsi que la façon dont l’application est configurée. Il n’existe
aucun conseil d’aide spécifique à suivre dans cette section pour une application de base,
si vous avez créé l’application en suivant les conseils d’aide de la section Procédure pas
à pas. Les conseils d’aide de cette section sont utiles pour mettre à jour une application
afin d’authentifier et d’autoriser les utilisateurs. Toutefois, il existe une autre approche
pour la mise à jour d’une application. Elle consiste à créer une application à partir des
conseils d’aide de la section Procédure pas à pas, et à déplacer les composants, les
classes et les ressources de l’application à mettre à jour vers la nouvelle application.

Package d’authentification
Quand une application est créée pour utiliser des comptes d’utilisateurs individuels, elle
reçoit automatiquement une référence de package pour le package
Microsoft.AspNetCore.Components.WebAssembly.Authentication . Le package fournit
un ensemble de primitives qui permettent à l’application d’authentifier les utilisateurs et
d’obtenir des jetons pour appeler les API protégées.

Si vous ajoutez l’authentification à une application, ajoutez manuellement le package


Microsoft.AspNetCore.Components.WebAssembly.Authentication à l’application.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Prise en charge du service d’authentification


La prise en charge de l’authentification des utilisateurs à l’aide d’OIDC (OpenID Connect)
est inscrite auprès du conteneur de services avec la méthode d’extension
AddOidcAuthentication fournie par le package
Microsoft.AspNetCore.Components.WebAssembly.Authentication .

La méthode AddOidcAuthentication accepte un rappel pour configurer les paramètres


nécessaires à l’authentification d’une application à l’aide d’OIDC. Les valeurs nécessaires
à la configuration de l’application peuvent être obtenues auprès du fournisseur
d’identité conforme à OIDC. Obtenez les valeurs quand vous inscrivez l’application, ce
qui se produit généralement dans son portail en ligne.

Dans le cas d’une nouvelle application, indiquez les valeurs pour les espaces réservés
{AUTHORITY} et {CLIENT ID} dans la configuration suivante. Indiquez les autres valeurs

de configuration nécessaires à utiliser avec le fournisseur d’identité de l’application.


L’exemple concerne Google, qui nécessite PostLogoutRedirectUri , RedirectUri et
ResponseType . Si vous ajoutez l’authentification à une application, ajoutez manuellement

le code et la configuration suivants à l’application avec des valeurs pour les espaces
réservés et autres valeurs de configuration.

Dans le fichier Program :

C#

builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Local", options.ProviderOptions);
});

Configuration wwwroot/appsettings.json
La configuration est fournie par le fichier wwwroot/appsettings.json :

JSON

{
"Local": {
"Authority": "{AUTHORITY}",
"ClientId": "{CLIENT ID}"
}
}

Étendues de jeton d’accès


Le modèle Blazor WebAssembly configure automatiquement les étendues par défaut
pour openid et profile .
Le modèle Blazor WebAssembly ne configure pas automatiquement l’application afin
qu’elle demande un jeton d’accès pour une API sécurisée. Pour provisionner un jeton
d’accès dans le cadre du flux de connexion, ajoutez l’étendue aux étendues de jeton par
défaut de OidcProviderOptions. Si vous ajoutez l’authentification à une application,
ajoutez manuellement le code suivant, puis configurez l’URI d’étendue.

Dans le fichier Program :

C#

builder.Services.AddOidcAuthentication(options =>
{
...
options.ProviderOptions.DefaultScopes.Add("{SCOPE URI}");
});

Pour plus d’informations, consultez les sections suivantes de l’article Scénarios


supplémentaires :

Demander des jetons d’accès supplémentaires


Attacher des jetons aux requêtes sortantes

Fichier Imports
L’espace de noms Microsoft.AspNetCore.Components.Authorization est rendu
disponible dans l’ensemble de l’application via le fichier _Imports.razor :

razor

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}
@using {APPLICATION ASSEMBLY}.Shared

Page d'index
La page d’index ( wwwroot/index.html ) comprend un script qui définit
AuthenticationService en JavaScript. AuthenticationService gère les détails de bas
niveau du protocole OIDC. L’application appelle de manière interne les méthodes
définies dans le script pour effectuer les opérations d’authentification.

HTML

<script
src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/Aut
henticationService.js"></script>

Composant d’application
Le composant App ( App.razor ) est similaire au composant App présent dans les
applications Blazor Server :

Le composant AuthorizeRouteView vérifie que l’utilisateur actuel est autorisé à


accéder à une page donnée, ou affiche le composant RedirectToLogin .
Le composant RedirectToLogin gère la redirection des utilisateurs non autorisés
vers la page de connexion.

En raison des changements apportés au framework dans les différentes versions


d’ASP.NET Core, la syntaxe Razor du composant App ( App.razor ) n’est pas affichée dans
cette section. Pour inspecter la syntaxe du composant d’une version donnée, utilisez
l’une des approches suivantes :

Créez une application provisionnée pour l’authentification à partir du modèle de


projet Blazor WebAssembly par défaut correspondant à la version d’ASP.NET Core
que vous prévoyez d’utiliser. Inspectez le composant App ( App.razor ) dans
l’application générée.

Inspectez le composant App ( App.razor ) dans source de référence . Sélectionnez


la version dans le sélecteur de branche, puis recherchez le composant dans le
dossier ProjectTemplates du référentiel, car il a été déplacé au fil des années.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Composant RedirectToLogin
Le composant RedirectToLogin ( Shared/RedirectToLogin.razor ) :

Gère la redirection des utilisateurs non autorisés vers la page de connexion.


L’URL actuelle à laquelle l’utilisateur tente d’accéder est conservée pour permettre
à l’utilisateur de retourner à cette page en cas d’authentification réussie avec :
État de l’historique de navigation dans ASP.NET Core 7.0 ou version ultérieure.
Chaîne de requête dans ASP.NET Core 6.0 ou version antérieure.

Inspectez le composant RedirectToLogin dans source de référence .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Composant LoginDisplay
Le composant LoginDisplay ( Shared/LoginDisplay.razor ) est affiché dans le composant
MainLayout ( Shared/MainLayout.razor ) et gère les comportements suivants :

Pour les utilisateurs authentifiés :


Affiche le nom d’utilisateur actuel.
Propose un lien vers la page de profil utilisateur dans ASP.NET Core Identity.
Propose un bouton pour se déconnecter de l’application.
Pour les utilisateurs anonymes :
Permet de s’inscrire.
Permet de se connecter.

En raison des changements apportés au framework dans les différentes versions


d’ASP.NET Core, la syntaxe Razor du composant LoginDisplay n’est pas affichée dans
cette section. Pour inspecter la syntaxe du composant d’une version donnée, utilisez
l’une des approches suivantes :

Créez une application provisionnée pour l’authentification à partir du modèle de


projet Blazor WebAssembly par défaut correspondant à la version d’ASP.NET Core
que vous prévoyez d’utiliser. Inspectez le composant LoginDisplay dans
l’application générée.

Inspectez le composant LoginDisplay dans la source de référence . Le contenu


mis en modèle pour Hosted égal à true est utilisé.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Composant Authentication
La page produite par le composant Authentication ( Pages/Authentication.razor )
définit les routes nécessaires à la gestion des différentes phases d’authentification.

Le composant RemoteAuthenticatorView :

Est fourni par le package


Microsoft.AspNetCore.Components.WebAssembly.Authentication .
Gère l’exécution des actions appropriées à chaque phase de l’authentification.

razor

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="Action" />

@code {
[Parameter]
public string? Action { get; set; }
}
7 Notes

Les types références Nullable (NRT) et l’analyse statique de l’état nul du


compilateur .NET sont pris en charge dans ASP.NET Core 6.0 ou version ultérieure.
Avant la publication d’ASP.NET Core 6.0, le type string apparaissait sans la
désignation de type nul ( ? ).

Résoudre des problèmes

Journalisation
Cette section s’applique à ASP.NET Core 7.0 ou version ultérieure.

Pour activer la journalisation du débogage ou des traces dans le cadre de


l’authentification Blazor WebAssembly, consultez Journalisation ASP.NET Core Blazor.

Erreurs courantes
Mauvaise configuration de l’application ou du fournisseur d’identité (Identity
Provider)

Les erreurs les plus courantes sont provoquées par une configuration incorrecte.
Voici quelques exemples :
Selon les besoins du scénario, une autorité, une instance, un ID de locataire, un
domaine de locataire, un ID client ou un URI de redirection manquant ou
incorrect empêche une application d’authentifier les clients.
Une étendue de jeton d’accès incorrecte empêche les clients d’accéder aux
points de terminaison d’API web du serveur.
Des autorisations d’API serveur incorrectes ou manquantes empêchent les
clients d’accéder aux points de terminaison d’API web du serveur.
Exécution de l’application sur un autre port que celui configuré dans l’URI de
redirection de l’inscription d’application du fournisseur d’identité (Identity
Provider).

Les sections de configuration présentes dans les conseils d’aide de cet article
montrent des exemples de configuration appropriée. Examinez attentivement
chaque section de l’article à la recherche d’une mauvaise configuration de
l’application et du fournisseur d’identité.

Si la configuration semble correcte :


Analysez les journaux des applications.

Examinez le trafic réseau entre l’application cliente et le fournisseur d’identité


ou l’application serveur à l’aide des outils de développement du navigateur.
Bien souvent, un message d’erreur exact ou un message indiquant la cause du
problème est retourné au client par le fournisseur d’identité ou l’application
serveur, une fois qu’une demande a été effectuée. Vous trouverez des conseils
d’aide sur les outils de développement dans les articles suivants :
Google Chrome (documentation Google)
Microsoft Edge
Mozilla Firefox (documentation Mozilla)

Décodez le contenu d’un jeton JWT (JSON Web Token) utilisé pour authentifier
un client ou accéder à une API web serveur, selon l’endroit où le problème se
produit. Pour plus d’informations, consultez Inspecter le contenu d’un jeton JWT
(JSON Web Token).

L’équipe de documentation peut répondre aux commentaires et bogues relatifs


aux articles (ouvrez un problème à partir de la section de commentaires de cette
page). Toutefois, elle ne peut pas fournir de support produit. Plusieurs forums de
support publics sont disponibles pour vous aider à résoudre les problèmes liés à
une application. Nous recommandons ce qui suit :
Dépassement de capacité de la pile (étiquette : blazor)
Équipe ASP.NET Core Slack
Blazor Gitter

Les forums précédents ne sont pas détenus ou contrôlés par Microsoft.

Pour les rapports de bogues de framework reproductibles, non liés à la sécurité,


non sensibles et non confidentiels, ouvrez un problème auprès de l’unité de
produit ASP.NET Core . N’ouvrez pas de problème auprès de l’unité de produit
tant que vous n’avez pas investigué de manière approfondie la cause du problème,
sans pouvoir le résoudre par vous-même ou avec l’aide de la communauté sur un
forum de support public. L’unité de produit ne peut pas résoudre les problèmes
d’applications individuelles qui sont défaillantes en raison d’une mauvaise
configuration ou de cas d’usage impliquant des services tiers. Si un rapport est de
nature sensible ou confidentielle, ou s’il décrit une faille de sécurité potentielle
(exploitable par des attaquants) dans le produit, consultez Signaler des problèmes
de sécurité et des bogues (référentiel GitHub dotnet/aspnetcore) .

Client non autorisé pour ME-ID


Info : Échec de l’autorisation
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]. Ces
conditions n’ont pas été remplies :
DenyAnonymousAuthorizationRequirement : Nécessite un utilisateur
authentifié.

Erreur de rappel de la connexion à partir de ME-ID :


Erreur : unauthorized_client
Description : AADB2C90058: The provided application is not configured to
allow public clients.

Pour résoudre l’erreur :

1. Dans le portail Azure, accédez au manifeste de l’application.


2. Affectez à l’attribut allowPublicClient la valeur null ou true .

Cookies et données de site


Les Cookies et les données de site peuvent persister entre les mises à jour d’application,
et interférer avec les tests et la résolution des problèmes. Effacez ce qui suit quand vous
apportez des changements au code d’application, au compte d’utilisateur du fournisseur
ou à la configuration de l’application :

cookies de connexion de l’utilisateur


cookies d’application
Données de site mises en cache et stockées

Il existe une approche qui permet d’empêcher les cookie et les données de site
persistants d’interférer avec les tests et la résolution des problèmes. Elle consiste à :

Configurer un navigateur
Utilisez un navigateur de test que vous pouvez configurer pour supprimer tous
les cookies et toutes les données de site à chaque fois qu’il se ferme.
Vérifiez que le navigateur est fermé manuellement ou par l’IDE chaque fois
qu’un changement est apporté à la configuration de l’application, de l’utilisateur
de test ou du fournisseur.
Utilisez une commande personnalisée pour ouvrir un navigateur en mode InPrivate
ou Incognito dans Visual Studio :
Ouvrez la boîte de dialogue Parcourir avec à partir du bouton Exécuter de
Visual Studio.
Cliquez sur le bouton Ajouter.
Indiquez le chemin de votre navigateur dans le champ Programme. Les chemins
d’exécutables suivants sont des emplacements d’installation classiques de
Windows 10. Si votre navigateur est installé à un autre emplacement, ou si vous
n’utilisez pas Windows 10, indiquez le chemin de l’exécutable du navigateur.
Microsoft Edge : C:\Program Files
(x86)\Microsoft\Edge\Application\msedge.exe

Google Chrome : C:\Program Files


(x86)\Google\Chrome\Application\chrome.exe

Mozilla Firefox : C:\Program Files\Mozilla Firefox\firefox.exe


Dans le champ Arguments, indiquez l’option de ligne de commande utilisée par
le navigateur pour qu’il s’ouvre en mode InPrivate ou Incognito. Certains
navigateurs nécessitent l’URL de l’application.
Microsoft Edge : Utilisez -inprivate .
Google Chrome : Utilisez --incognito --new-window {URL} , où l’espace
réservé {URL} correspond à l’URL à ouvrir (par exemple
https://localhost:5001 ).

Mozilla Firefox : Utilisez -private -url {URL} , où l’espace réservé {URL}


correspond à l’URL à ouvrir (par exemple https://localhost:5001 ).
Indiquez un nom dans le champ Nom convivial. Par exemple, Firefox Auth
Testing

Cliquez sur le bouton OK.


Pour éviter d’avoir à sélectionner le profil de navigateur pour chaque itération
de test avec une application, définissez le profil en tant que profil par défaut
avec le bouton Par défaut.
Vérifiez que le navigateur est fermé par l’IDE chaque fois qu’un changement est
apporté à la configuration de l’application, de l’utilisateur de test ou du
fournisseur.

Mises à niveau d’application


Une application fonctionnelle peut échouer immédiatement après la mise à niveau du
kit SDK .NET Core sur l’ordinateur de développement ou la modification des versions de
package au sein de l’application. Dans certains cas, les packages incohérents peuvent
bloquer une application quand vous effectuez des mises à niveau majeures. Vous
pouvez résoudre la plupart de ces problèmes en suivant les instructions suivantes :

1. Effacez les caches de package NuGet du système local en exécutant dotnet nuget
locals all --clear à partir d’un interpréteur de commandes.
2. Supprimez les dossiers bin et obj du projet.
3. Restaurez et regénérez le projet.
4. Supprimez tous les fichiers du dossier de déploiement sur le serveur avant de
redéployer l’application.

7 Notes

L’utilisation de versions de package incompatibles avec le framework cible de


l’application n’est pas prise en charge. Pour plus d’informations sur un package,
utilisez la Galerie NuGet ou l’Explorateur de packages FuGet .

Inspecter l’utilisateur
Le composant User suivant peut être utilisé directement dans les applications, ou servir
de base à une personnalisation supplémentaire.

User.razor :

razor

@page "/user"
@attribute [Authorize]
@using System.Text.Json
@using System.Security.Claims
@inject IAccessTokenProvider AuthorizationService

<h1>@AuthenticatedUser?.Identity?.Name</h1>

<h2>Claims</h2>

@foreach (var claim in AuthenticatedUser?.Claims ?? Array.Empty<Claim>())


{
<p class="claim">@(claim.Type): @claim.Value</p>
}

<h2>Access token</h2>

<p id="access-token">@AccessToken?.Value</p>

<h2>Access token claims</h2>

@foreach (var claim in GetAccessTokenClaims())


{
<p>@(claim.Key): @claim.Value.ToString()</p>
}

@if (AccessToken != null)


{
<h2>Access token expires</h2>

<p>Current time: <span id="current-time">@DateTimeOffset.Now</span></p>


<p id="access-token-expires">@AccessToken.Expires</p>

<h2>Access token granted scopes (as reported by the API)</h2>

@foreach (var scope in AccessToken.GrantedScopes)


{
<p>Scope: @scope</p>
}
}

@code {
[CascadingParameter]
private Task<AuthenticationState> AuthenticationState { get; set; }

public ClaimsPrincipal AuthenticatedUser { get; set; }


public AccessToken AccessToken { get; set; }

protected override async Task OnInitializedAsync()


{
await base.OnInitializedAsync();
var state = await AuthenticationState;
var accessTokenResult = await
AuthorizationService.RequestAccessToken();

if (!accessTokenResult.TryGetToken(out var token))


{
throw new InvalidOperationException(
"Failed to provision the access token.");
}

AccessToken = token;

AuthenticatedUser = state.User;
}

protected IDictionary<string, object> GetAccessTokenClaims()


{
if (AccessToken == null)
{
return new Dictionary<string, object>();
}

// header.payload.signature
var payload = AccessToken.Value.Split(".")[1];
var base64Payload = payload.Replace('-', '+').Replace('_', '/')
.PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');

return JsonSerializer.Deserialize<IDictionary<string, object>>(


Convert.FromBase64String(base64Payload));
}
}

Inspecter le contenu d’un jeton JWT (JSON Web Token)


Pour décoder un jeton JWT (JSON Web Token), utilisez l’outil jwt.ms de Microsoft. Les
valeurs de l’IU ne quittent jamais votre navigateur.

Exemple de jeton JWT codé (raccourci pour des raisons d’affichage) :

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0
djhkbE5QNC1j...
bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzp
D-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-
yBMKV2_nXA25Q

Exemple JWT décodé par l’outil pour une application qui s’authentifie auprès d’Azure
AAD B2C :

JSON

{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1610059429,
"nbf": 1610055829,
"ver": "1.0",
"iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-
226dcc9ad298/v2.0/",
"sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
"aud": "70bde375-fce3-4b82-984a-b247d823a03f",
"nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
"iat": 1610055829,
"auth_time": 1610055822,
"idp": "idp.com",
"tfp": "B2C_1_signupsignin"
}.[Signature]

Ressources supplémentaires
Autres scénarios de sécurité ASP.NET Core Blazor WebAssembly
Requêtes d’API web non authentifiées ou non autorisées dans une application avec
un client par défaut sécurisé
Configurer ASP.NET Core pour une utilisation avec des serveurs proxy et des
équilibreurs de charge. Cet article fournit des conseils sur les aspects suivants :
Utilisation du middleware Forwarded Headers pour préserver les informations
de schéma HTTPS sur les serveurs proxy et les réseaux internes.
Autres scénarios et cas d’usage, notamment la configuration manuelle de
schéma, la modification des chemins de requêtes pour assurer le bon routage
des requêtes et le transfert du schéma de requête pour les proxys inverses Linux
et non IIS.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Sécuriser une application autonome
ASP.NET Core Blazor WebAssembly avec
des comptes Microsoft
Article • 09/02/2024

Cet article explique comment créer une applicationBlazor WebAssembly autonome qui
utilise comptes Microsoft avec Microsoft Entra (ME-ID) pour l’authentification.

Pour obtenir une couverture supplémentaire du scénario de sécurité après avoir lu cet
article, consultez ASP.NET Core Blazor WebAssembly scénarios de sécurité
supplémentaires.

Procédure pas à pas


Les sous-sections de la procédure pas à pas expliquent comment :

Créer un locataire dans Azure


Inscrire une application auprès d’Azure
Créer l’application Blazor
Exécuter l’application

Créer un locataire dans Azure


Suivez les conseils d’aide fournis dans Démarrage rapide : Configurer un locataire pour
créer un locataire dans ME-ID.

Inscrire une application auprès d’Azure


Inscrivez une application ME-ID :

1. Accédez à Microsoft Entra ID dans le portail Azure. Sélectionnez Inscriptions


d’applications dans la barre latérale. Sélectionnez le bouton Nouvelle inscription.
2. Fournissez un Nom pour l’application (par exemple, Blazor comptes MS ME-ID
autonomes).
3. Dans types de comptes pris en charge, sélectionnez Comptes dans n’importe
quel annuaire organisationnel (n’importe quel répertoire Microsoft Entra ID
multilocataire).
4. Affectez à la liste déroulante URI de redirection la valeur Application monopage
(SPA), puis indiquez l’URI de redirection suivant :
https://localhost/authentication/login-callback . Si vous connaissez l’URI de

redirection de production de l’hôte par défaut Azure (par exemple


azurewebsites.net ) ou de l’hôte de domaine personnalisé (par exemple
contoso.com ), vous pouvez également ajouter l’URI de redirection de production

en même temps que vous en fournissant l’URI de redirection localhost . Veillez à


inclure le numéro de port des autres ports que le port :443 dans les URI de
redirection de production que vous ajoutez.
5. Si vous utilisez un domaine d’éditeur non vérifié, décochez la case
Autorisations>Octroyer le consentement administrateur aux autorisations
openid et offline_access. Si le domaine d’éditeur est vérifié, cette case à cocher
n’est pas présente.
6. Sélectionnez Inscription.

7 Notes

Il n’est pas nécessaire de fournir le numéro de port d’un URI de redirection


localhost ME-ID. Pour plus d’informations, consultez Restrictions et limitations de

l’URI de redirection (URL de réponse) : exceptions localhost (documentation


Entra).

Notez l’ID d’application (client) (par exemple 41451fa7-82d9-4673-8fa5-69eff5a761fd ).

Dans Authentification>Configurations de plateforme>Application monopage :

1. Vérifiez que l’URI de redirection https://localhost/authentication/login-callback


est présent.
2. Dans la section Octroi implicite, vérifiez que les cases des jetons d’accès et des
jetons d’ID ne sont pas cochées. L’octroi implicite n’est pas recommandé pour les
applications Blazor qui utilisent MSAL v2.0 ou version ultérieure. Pour plus
d’informations, consultez Sécuriser ASP.NET Core Blazor WebAssembly.
3. Les autres valeurs par défaut de l’application sont acceptables dans le cadre de
cette expérience.
4. Cliquez sur le bouton Enregistrer si vous avez apporté des modifications.

Créer l’application Blazor


Créez l'application. Remplacez les espaces réservés dans la commande suivante par les
informations notées plus tôt, puis exécutez cette commande dans un interpréteur de
commandes :

CLI .NET
dotnet new blazorwasm -au SingleOrg --client-id "{CLIENT ID}" --tenant-id
"common" -o {PROJECT NAME}

ノ Agrandir le tableau

Espace réservé Nom du portail Azure Exemple

{PROJECT NAME} — BlazorSample

{CLIENT ID} ID d’application (client) 41451fa7-82d9-4673-8fa5-69eff5a761fd

L’emplacement de sortie spécifié avec l’option -o|--output crée un dossier de projet s’il
n’existe pas, et devient partie intégrante du nom du projet.

Ajoutez une paire de MsalProviderOptions pour openid et


offline_access DefaultAccessTokenScopes :

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("openid");
options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access");
});

Exécuter l’application
Utilisez l’une des approches suivantes pour exécuter l’application :

Visual Studio
Sélectionnez le bouton Run.
Utilisez Déboguer>Démarrer le débogage dans le menu.
Appuyez sur F5 .
Interpréteur de commandes .NET CLI : exécutez la commande dotnet run à partir
du dossier de l’application.

Parties de l’application
Cette section décrit les parties d’une application générées à partir du modèle de projet
Blazor WebAssembly ainsi que la façon dont l’application est configurée. Il n’existe
aucun conseil d’aide spécifique à suivre dans cette section pour une application de base,
si vous avez créé l’application en suivant les conseils d’aide de la section Procédure pas
à pas. Les conseils d’aide de cette section sont utiles pour mettre à jour une application
afin d’authentifier et d’autoriser les utilisateurs. Toutefois, il existe une autre approche
pour la mise à jour d’une application. Elle consiste à créer une application à partir des
conseils d’aide de la section Procédure pas à pas, et à déplacer les composants, les
classes et les ressources de l’application à mettre à jour vers la nouvelle application.

Package d’authentification
Quand une application est créée pour utiliser des comptes professionnels ou scolaires
( SingleOrg ), l’application reçoit automatiquement une référence de package pour la
bibliothèque d’authentification Microsoft
(Microsoft.Authentication.WebAssembly.Msal ). Le package fournit un ensemble de
primitives qui permettent à l’application d’authentifier les utilisateurs et d’obtenir des
jetons pour appeler les API protégées.

Si vous ajoutez l’authentification à une application, ajoutez manuellement le package


Microsoft.Authentication.WebAssembly.Msal à l’application.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Le package Microsoft.Authentication.WebAssembly.Msal ajoute de façon transitive le


package Microsoft.AspNetCore.Components.WebAssembly.Authentication à
l’application.

Prise en charge du service d’authentification


La prise en charge de l’authentification des utilisateurs est inscrite auprès du conteneur
de services avec la méthode d’extension AddMsalAuthentication fournie par le package
Microsoft.Authentication.WebAssembly.Msal . Cette méthode configure tous les
services nécessaires à l’application pour interagir avec le fournisseur d’identité (Identity
Provider).

Dans le fichier Program :

C#
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
});

La méthode AddMsalAuthentication accepte un rappel pour configurer les paramètres


nécessaires à l’authentification d’une application. Les valeurs requises pour configurer
l’application peuvent être obtenues à partir de la configuration ME-ID lorsque vous
inscrivez l’application.

Configuration wwwroot/appsettings.json
La configuration est fournie par le fichier wwwroot/appsettings.json :

JSON

{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/common",
"ClientId": "{CLIENT ID}",
"ValidateAuthority": true
}
}

Exemple :

JSON

{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/common",
"ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
"ValidateAuthority": true
}
}

Étendues de jeton d’accès


Le modèle Blazor WebAssembly ne configure pas automatiquement l’application afin
qu’elle demande un jeton d’accès pour une API sécurisée. Pour provisionner un jeton
d’accès dans le cadre du flux de connexion, ajoutez l’étendue aux étendues de jeton
d’accès par défaut de MsalProviderOptions :
C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

Spécifiez des étendues supplémentaires avec AdditionalScopesToConsent :

C#

options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE
URI}");

Pour plus d’informations, consultez les sections suivantes de l’article Scénarios


supplémentaires :

Demander des jetons d’accès supplémentaires


Attacher des jetons aux requêtes sortantes
Démarrage rapide : Configurer une application pour exposer des API web

Mode de connexion
Le framework utilise par défaut le mode de connexion par fenêtre indépendante, et
revient au mode de connexion par redirection si une fenêtre indépendante ne peut pas
être ouverte. Configurez MSAL pour utiliser le mode de connexion par redirection en
affectant la valeur redirect à la propriété LoginMode de MsalProviderOptions :

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.LoginMode = "redirect";
});

Le paramètre par défaut est popup , et la valeur de chaîne ne respecte pas la casse.

Fichier Imports
L’espace de noms Microsoft.AspNetCore.Components.Authorization est rendu
disponible dans l’ensemble de l’application via le fichier _Imports.razor :
razor

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}
@using {APPLICATION ASSEMBLY}.Shared

Page d'index
La page d’index ( wwwroot/index.html ) comprend un script qui définit
AuthenticationService en JavaScript. AuthenticationService gère les détails de bas

niveau du protocole OIDC. L’application appelle de manière interne les méthodes


définies dans le script pour effectuer les opérations d’authentification.

HTML

<script
src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationServic
e.js"></script>

Composant d’application
Le composant App ( App.razor ) est similaire au composant App présent dans les
applications Blazor Server :

Le composant AuthorizeRouteView vérifie que l’utilisateur actuel est autorisé à


accéder à une page donnée, ou affiche le composant RedirectToLogin .
Le composant RedirectToLogin gère la redirection des utilisateurs non autorisés
vers la page de connexion.

En raison des changements apportés au framework dans les différentes versions


d’ASP.NET Core, la syntaxe Razor du composant App ( App.razor ) n’est pas affichée dans
cette section. Pour inspecter la syntaxe du composant d’une version donnée, utilisez
l’une des approches suivantes :

Créez une application provisionnée pour l’authentification à partir du modèle de


projet Blazor WebAssembly par défaut correspondant à la version d’ASP.NET Core
que vous prévoyez d’utiliser. Inspectez le composant App ( App.razor ) dans
l’application générée.

Inspectez le composant App ( App.razor ) dans source de référence . Sélectionnez


la version dans le sélecteur de branche, puis recherchez le composant dans le
dossier ProjectTemplates du référentiel, car il a été déplacé au fil des années.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Composant RedirectToLogin
Le composant RedirectToLogin ( Shared/RedirectToLogin.razor ) :

Gère la redirection des utilisateurs non autorisés vers la page de connexion.


L’URL actuelle à laquelle l’utilisateur tente d’accéder est conservée pour permettre
à l’utilisateur de retourner à cette page en cas d’authentification réussie avec :
État de l’historique de navigation dans ASP.NET Core dans .NET 7 ou version
ultérieure.
Chaîne de requête dans ASP.NET Core dans .NET 6 ou version antérieure.

Inspectez le composant RedirectToLogin dans source de référence .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .
Composant LoginDisplay
Le composant LoginDisplay ( Shared/LoginDisplay.razor ) est affiché dans le composant
MainLayout ( Shared/MainLayout.razor ) et gère les comportements suivants :

Pour les utilisateurs authentifiés :


Affiche le nom d’utilisateur actuel.
Propose un lien vers la page de profil utilisateur dans ASP.NET Core Identity.
Propose un bouton pour se déconnecter de l’application.
Pour les utilisateurs anonymes :
Permet de s’inscrire.
Permet de se connecter.

En raison des changements apportés au framework dans les différentes versions


d’ASP.NET Core, la syntaxe Razor du composant LoginDisplay n’est pas affichée dans
cette section. Pour inspecter la syntaxe du composant d’une version donnée, utilisez
l’une des approches suivantes :

Créez une application provisionnée pour l’authentification à partir du modèle de


projet Blazor WebAssembly par défaut correspondant à la version d’ASP.NET Core
que vous prévoyez d’utiliser. Inspectez le composant LoginDisplay dans
l’application générée.

Inspectez le composant LoginDisplay dans la source de référence . Le contenu


mis en modèle pour Hosted égal à true est utilisé.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Composant Authentication
La page produite par le composant Authentication ( Pages/Authentication.razor )
définit les routes nécessaires à la gestion des différentes phases d’authentification.
Le composant RemoteAuthenticatorView :

Est fourni par le package


Microsoft.AspNetCore.Components.WebAssembly.Authentication .
Gère l’exécution des actions appropriées à chaque phase de l’authentification.

razor

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="Action" />

@code {
[Parameter]
public string? Action { get; set; }
}

7 Notes

Les types références Null (NRT) et l’analyse statique de l’état nul du compilateur
.NET sont pris en charge dans ASP.NET Core dans .NET 6 ou version ultérieure.
Avant la publication d’ASP.NET Core dans .NET 6, le type string apparaissait sans
la désignation de type nul ( ? ).

Résolution des problèmes

Journalisation
Cette section s’applique à ASP.NET Core dans .NET 7 ou version ultérieure.

Pour activer la journalisation du débogage ou des traces dans le cadre de


l’authentification Blazor WebAssembly, consultez Journalisation ASP.NET Core Blazor.

Erreurs courantes
Mauvaise configuration de l’application ou du fournisseur d’identité (Identity
Provider)

Les erreurs les plus courantes sont provoquées par une configuration incorrecte.
Voici quelques exemples :
Selon les besoins du scénario, une autorité, une instance, un ID de locataire, un
domaine de locataire, un ID client ou un URI de redirection manquant ou
incorrect empêche une application d’authentifier les clients.
Une étendue de jeton d’accès incorrecte empêche les clients d’accéder aux
points de terminaison d’API web du serveur.
Des autorisations d’API serveur incorrectes ou manquantes empêchent les
clients d’accéder aux points de terminaison d’API web du serveur.
Exécution de l’application sur un autre port que celui configuré dans l’URI de
redirection de l’inscription d’application du fournisseur d’identité (Identity
Provider).

Les sections de configuration présentes dans les conseils d’aide de cet article
montrent des exemples de configuration appropriée. Examinez attentivement
chaque section de l’article à la recherche d’une mauvaise configuration de
l’application et du fournisseur d’identité.

Si la configuration semble correcte :

Analysez les journaux des applications.

Examinez le trafic réseau entre l’application cliente et le fournisseur d’identité


ou l’application serveur à l’aide des outils de développement du navigateur.
Bien souvent, un message d’erreur exact ou un message indiquant la cause du
problème est retourné au client par le fournisseur d’identité ou l’application
serveur, une fois qu’une demande a été effectuée. Vous trouverez des conseils
d’aide sur les outils de développement dans les articles suivants :
Google Chrome (documentation Google)
Microsoft Edge
Mozilla Firefox (documentation Mozilla)

Décodez le contenu d’un jeton JWT (JSON Web Token) utilisé pour authentifier
un client ou accéder à une API web serveur, selon l’endroit où le problème se
produit. Pour plus d’informations, consultez Inspecter le contenu d’un jeton JWT
(JSON Web Token).

L’équipe de documentation peut répondre aux commentaires et bogues relatifs


aux articles (ouvrez un problème à partir de la section de commentaires de cette
page). Toutefois, elle ne peut pas fournir de support produit. Plusieurs forums de
support publics sont disponibles pour vous aider à résoudre les problèmes liés à
une application. Nous recommandons ce qui suit :
Dépassement de capacité de la pile (étiquette : blazor)
Équipe ASP.NET Core Slack
Blazor Gitter
Les forums précédents ne sont pas détenus ou contrôlés par Microsoft.

Pour les rapports de bogues de framework reproductibles, non liés à la sécurité,


non sensibles et non confidentiels, ouvrez un problème auprès de l’unité de
produit ASP.NET Core . N’ouvrez pas de problème auprès de l’unité de produit
tant que vous n’avez pas investigué de manière approfondie la cause du problème,
sans pouvoir le résoudre par vous-même ou avec l’aide de la communauté sur un
forum de support public. L’unité de produit ne peut pas résoudre les problèmes
d’applications individuelles qui sont défaillantes en raison d’une mauvaise
configuration ou de cas d’usage impliquant des services tiers. Si un rapport est de
nature sensible ou confidentielle, ou s’il décrit une faille de sécurité potentielle
(exploitable par des attaquants) dans le produit, consultez Signaler des problèmes
de sécurité et des bogues (référentiel GitHub dotnet/aspnetcore) .

Client non autorisé pour ME-ID

Info : Échec de l’autorisation


Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]. Ces
conditions n’ont pas été remplies :
DenyAnonymousAuthorizationRequirement : Nécessite un utilisateur
authentifié.

Erreur de rappel de la connexion à partir de ME-ID :


Erreur : unauthorized_client
Description : AADB2C90058: The provided application is not configured to
allow public clients.

Pour résoudre l’erreur :

1. Dans le portail Azure, accédez au manifeste de l’application.


2. Affectez à l’attribut allowPublicClient la valeur null ou true .

Cookies et données de site


Les Cookies et les données de site peuvent persister entre les mises à jour d’application,
et interférer avec les tests et la résolution des problèmes. Effacez ce qui suit quand vous
apportez des changements au code d’application, au compte d’utilisateur du fournisseur
ou à la configuration de l’application :

cookies de connexion de l’utilisateur


cookies d’application
Données de site mises en cache et stockées
Il existe une approche qui permet d’empêcher les cookie et les données de site
persistants d’interférer avec les tests et la résolution des problèmes. Elle consiste à :

Configurer un navigateur
Utilisez un navigateur de test que vous pouvez configurer pour supprimer tous
les cookies et toutes les données de site à chaque fois qu’il se ferme.
Vérifiez que le navigateur est fermé manuellement ou par l’IDE chaque fois
qu’un changement est apporté à la configuration de l’application, de l’utilisateur
de test ou du fournisseur.
Utilisez une commande personnalisée pour ouvrir un navigateur en mode InPrivate
ou Incognito dans Visual Studio :
Ouvrez la boîte de dialogue Parcourir avec à partir du bouton Exécuter de
Visual Studio.
Cliquez sur le bouton Ajouter.
Indiquez le chemin de votre navigateur dans le champ Programme. Les chemins
d’exécutables suivants sont des emplacements d’installation classiques de
Windows 10. Si votre navigateur est installé à un autre emplacement, ou si vous
n’utilisez pas Windows 10, indiquez le chemin de l’exécutable du navigateur.
Microsoft Edge : C:\Program Files
(x86)\Microsoft\Edge\Application\msedge.exe

Google Chrome : C:\Program Files


(x86)\Google\Chrome\Application\chrome.exe
Mozilla Firefox : C:\Program Files\Mozilla Firefox\firefox.exe
Dans le champ Arguments, indiquez l’option de ligne de commande utilisée par
le navigateur pour qu’il s’ouvre en mode InPrivate ou Incognito. Certains
navigateurs nécessitent l’URL de l’application.
Microsoft Edge : Utilisez -inprivate .
Google Chrome : Utilisez --incognito --new-window {URL} , où l’espace
réservé {URL} correspond à l’URL à ouvrir (par exemple
https://localhost:5001 ).

Mozilla Firefox : Utilisez -private -url {URL} , où l’espace réservé {URL}


correspond à l’URL à ouvrir (par exemple https://localhost:5001 ).
Indiquez un nom dans le champ Nom convivial. Par exemple, Firefox Auth
Testing

Cliquez sur le bouton OK.


Pour éviter d’avoir à sélectionner le profil de navigateur pour chaque itération
de test avec une application, définissez le profil en tant que profil par défaut
avec le bouton Par défaut.
Vérifiez que le navigateur est fermé par l’IDE chaque fois qu’un changement est
apporté à la configuration de l’application, de l’utilisateur de test ou du
fournisseur.

Mises à niveau d’application


Une application fonctionnelle peut échouer immédiatement après la mise à niveau du
kit SDK .NET Core sur l’ordinateur de développement ou la modification des versions de
package au sein de l’application. Dans certains cas, les packages incohérents peuvent
bloquer une application quand vous effectuez des mises à niveau majeures. Vous
pouvez résoudre la plupart de ces problèmes en suivant les instructions suivantes :

1. Effacez les caches de package NuGet du système local en exécutant dotnet nuget
locals all --clear à partir d’un interpréteur de commandes.
2. Supprimez les dossiers bin et obj du projet.
3. Restaurez et regénérez le projet.
4. Supprimez tous les fichiers du dossier de déploiement sur le serveur avant de
redéployer l’application.

7 Notes

L’utilisation de versions de package incompatibles avec le framework cible de


l’application n’est pas prise en charge. Pour plus d’informations sur un package,
utilisez la Galerie NuGet ou l’Explorateur de packages FuGet .

Inspecter l’utilisateur
Le composant User suivant peut être utilisé directement dans les applications, ou servir
de base à une personnalisation supplémentaire.

User.razor :

razor

@page "/user"
@attribute [Authorize]
@using System.Text.Json
@using System.Security.Claims
@inject IAccessTokenProvider AuthorizationService

<h1>@AuthenticatedUser?.Identity?.Name</h1>

<h2>Claims</h2>

@foreach (var claim in AuthenticatedUser?.Claims ?? Array.Empty<Claim>())


{
<p class="claim">@(claim.Type): @claim.Value</p>
}

<h2>Access token</h2>

<p id="access-token">@AccessToken?.Value</p>

<h2>Access token claims</h2>

@foreach (var claim in GetAccessTokenClaims())


{
<p>@(claim.Key): @claim.Value.ToString()</p>
}

@if (AccessToken != null)


{
<h2>Access token expires</h2>

<p>Current time: <span id="current-time">@DateTimeOffset.Now</span></p>


<p id="access-token-expires">@AccessToken.Expires</p>

<h2>Access token granted scopes (as reported by the API)</h2>

@foreach (var scope in AccessToken.GrantedScopes)


{
<p>Scope: @scope</p>
}
}

@code {
[CascadingParameter]
private Task<AuthenticationState> AuthenticationState { get; set; }

public ClaimsPrincipal AuthenticatedUser { get; set; }


public AccessToken AccessToken { get; set; }

protected override async Task OnInitializedAsync()


{
await base.OnInitializedAsync();
var state = await AuthenticationState;
var accessTokenResult = await
AuthorizationService.RequestAccessToken();

if (!accessTokenResult.TryGetToken(out var token))


{
throw new InvalidOperationException(
"Failed to provision the access token.");
}

AccessToken = token;

AuthenticatedUser = state.User;
}

protected IDictionary<string, object> GetAccessTokenClaims()


{
if (AccessToken == null)
{
return new Dictionary<string, object>();
}

// header.payload.signature
var payload = AccessToken.Value.Split(".")[1];
var base64Payload = payload.Replace('-', '+').Replace('_', '/')
.PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');

return JsonSerializer.Deserialize<IDictionary<string, object>>(


Convert.FromBase64String(base64Payload));
}
}

Inspecter le contenu d’un jeton JWT (JSON Web Token)


Pour décoder un jeton JWT (JSON Web Token), utilisez l’outil jwt.ms de Microsoft. Les
valeurs de l’IU ne quittent jamais votre navigateur.

Exemple de jeton JWT codé (raccourci pour des raisons d’affichage) :

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0
djhkbE5QNC1j...
bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzp
D-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-
yBMKV2_nXA25Q

Exemple JWT décodé par l’outil pour une application qui s’authentifie auprès d’Azure
AAD B2C :

JSON

{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1610059429,
"nbf": 1610055829,
"ver": "1.0",
"iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-
226dcc9ad298/v2.0/",
"sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
"aud": "70bde375-fce3-4b82-984a-b247d823a03f",
"nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
"iat": 1610055829,
"auth_time": 1610055822,
"idp": "idp.com",
"tfp": "B2C_1_signupsignin"
}.[Signature]

Ressources supplémentaires
Autres scénarios de sécurité ASP.NET Core Blazor WebAssembly
Créer une version personnalisée de la bibliothèque JavaScript Authentication.MSAL
Requêtes d’API web non authentifiées ou non autorisées dans une application avec
un client par défaut sécurisé
ASP.NET Core Blazor WebAssembly avec les groupes et rôles Microsoft Entra ID
Démarrage rapide : Inscrire une application à l’aide de la plateforme d’identités
Microsoft
Démarrage rapide : Configurer une application pour exposer des API web

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Sécuriser une application autonome
ASP.NET Core Blazor WebAssembly avec
Microsoft Entra ID
Article • 09/02/2024

Cet article explique comment créer une applicationBlazor WebAssembly autonome qui
utilise Microsoft Entra ID (ME-ID) pour l’authentification.

Pour obtenir une couverture supplémentaire du scénario de sécurité après avoir lu cet
article, consultez ASP.NET Core Blazor WebAssembly scénarios de sécurité
supplémentaires.

Procédure pas à pas


Les sous-sections de la procédure pas à pas expliquent comment :

Créer un locataire dans Azure


Inscrire une application auprès d’Azure
Créer l’application Blazor
Exécuter l’application

Créer un locataire dans Azure


Suivez les conseils d’aide fournis dans Démarrage rapide : Configurer un locataire pour
créer un locataire dans ME-ID.

Inscrire une application auprès d’Azure


Inscrivez une application ME-ID :

1. Accédez à Microsoft Entra ID dans le portail Azure . Sélectionnez


Applications>Inscriptions d’applications dans la barre latérale. Sélectionnez le
bouton Nouvelle inscription.
2. Fournissez un nom pour l’application (par exemple, BlazorME-ID autonome).
3. Choisissez un types de comptes pris en charge. Vous pouvez sélectionner
Comptes dans cet annuaire organisationnel uniquement pour cette expérience.
4. Définissez la liste déroulante URI de redirection sur Application monopage (SPA)
et fournissez l’URI de redirection suivante :
https://localhost/authentication/login-callback . Si vous connaissez l’URI de
redirection de production de l’hôte par défaut Azure (par exemple
azurewebsites.net ) ou de l’hôte de domaine personnalisé (par exemple

contoso.com ), vous pouvez également ajouter l’URI de redirection de production

en même temps que vous en fournissant l’URI de redirection localhost . Veillez à


inclure le numéro de port des autres ports que le port :443 dans les URI de
redirection de production que vous ajoutez.
5. Si vous utilisez un domaine d’éditeur non vérifié, décochez la case
Autorisations>Octroyer le consentement administrateur aux autorisations
openid et offline_access. Si le domaine d’éditeur est vérifié, cette case à cocher
n’est pas présente.
6. Sélectionnez Inscription.

7 Notes

Il n’est pas nécessaire de fournir le numéro de port d’un URI de redirection


localhost ME-ID. Pour plus d’informations, consultez Restrictions et limitations de

l’URI de redirection (URL de réponse) : exceptions localhost (documentation


Entra).

Enregistrez les informations suivantes :

ID d’application (client) (par exemple 41451fa7-82d9-4673-8fa5-69eff5a761fd )


ID de répertoire (locataire) (par exemple, e86c78e2-8bb4-4c41-aefd-918e0565a45e )

Dans Authentification>Configurations de la plateforme>Application monopage :

1. Vérifiez que l’URI de redirection https://localhost/authentication/login-callback


est présent.
2. Dans la section Octroi implicite, vérifiez que les cases des jetons d’accès et des
jetons d’ID ne sont pas cochées. L’octroi implicite n’est pas recommandé pour les
applications Blazor qui utilisent MSAL v2.0 ou version ultérieure. Pour plus
d’informations, consultez Sécuriser ASP.NET Core Blazor WebAssembly.
3. Les autres valeurs par défaut de l’application sont acceptables dans le cadre de
cette expérience.
4. Cliquez sur le bouton Enregistrer si vous avez apporté des modifications.

Créer l’application Blazor


Créez l’application dans un dossier vide. Remplacez les espaces réservés dans la
commande suivante par les informations notées plus tôt, puis exécutez la commande
dans un interpréteur de commandes :
CLI .NET

dotnet new blazorwasm -au SingleOrg --client-id "{CLIENT ID}" -o {PROJECT


NAME} --tenant-id "{TENANT ID}"

ノ Agrandir le tableau

Espace réservé Nom du portail Azure Exemple

{PROJECT NAME} — BlazorSample

{CLIENT ID} ID d’application (client) 41451fa7-82d9-4673-8fa5-69eff5a761fd

{TENANT ID} ID du répertoire (locataire) e86c78e2-8bb4-4c41-aefd-918e0565a45e

L’emplacement de sortie spécifié avec l’option -o|--output crée un dossier de projet s’il
n’existe pas, et devient partie intégrante du nom du projet.

Ajoutez un MsalProviderOptions pour l’autorisation User.Read avec


DefaultAccessTokenScopes :

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes
.Add("https://graph.microsoft.com/User.Read");
});

Exécuter l’application
Utilisez l’une des approches suivantes pour exécuter l’application :

Visual Studio
Sélectionnez le bouton Run.
Utilisez Déboguer>Démarrer le débogage dans le menu.
Appuyez sur F5 .
Interpréteur de commandes .NET CLI : exécutez la commande dotnet run à partir
du dossier de l’application.

Parties de l’application
Cette section décrit les parties d’une application générées à partir du modèle de projet
Blazor WebAssembly ainsi que la façon dont l’application est configurée. Il n’existe
aucun conseil d’aide spécifique à suivre dans cette section pour une application de base,
si vous avez créé l’application en suivant les conseils d’aide de la section Procédure pas
à pas. Les conseils d’aide de cette section sont utiles pour mettre à jour une application
afin d’authentifier et d’autoriser les utilisateurs. Toutefois, il existe une autre approche
pour la mise à jour d’une application. Elle consiste à créer une application à partir des
conseils d’aide de la section Procédure pas à pas, et à déplacer les composants, les
classes et les ressources de l’application à mettre à jour vers la nouvelle application.

Package d’authentification
Quand une application est créée pour utiliser des comptes professionnels ou scolaires
( SingleOrg ), l’application reçoit automatiquement une référence de package pour la
bibliothèque d’authentification Microsoft
(Microsoft.Authentication.WebAssembly.Msal ). Le package fournit un ensemble de
primitives qui permettent à l’application d’authentifier les utilisateurs et d’obtenir des
jetons pour appeler les API protégées.

Si vous ajoutez l’authentification à une application, ajoutez manuellement le package


Microsoft.Authentication.WebAssembly.Msal à l’application.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Le package Microsoft.Authentication.WebAssembly.Msal ajoute de façon transitive le


package Microsoft.AspNetCore.Components.WebAssembly.Authentication à
l’application.

Prise en charge du service d’authentification


La prise en charge de l’authentification des utilisateurs est inscrite dans le conteneur de
service avec la AddMsalAuthentication méthode d’extension fournie par le package
Microsoft.Authentication.WebAssembly.Msal . Cette méthode configure les services
nécessaires à l’application pour interagir avec le fournisseur d’identité (Identity
Provider).
Dans le fichier Program :

C#

builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
});

La méthode AddMsalAuthentication accepte un rappel pour configurer les paramètres


nécessaires à l’authentification d’une application. Les valeurs requises pour configurer
l’application peuvent être obtenues à partir de la configuration ME-ID lorsque vous
inscrivez l’application.

Configuration wwwroot/appsettings.json
La configuration est fournie par le fichier wwwroot/appsettings.json :

JSON

{
"AzureAd": {
"Authority": "https://login.microsoftonline.com/{TENANT ID}",
"ClientId": "{CLIENT ID}",
"ValidateAuthority": true
}
}

Exemple :

JSON

{
"AzureAd": {
"Authority":
"https://login.microsoftonline.com/e86c78e2-...-918e0565a45e",
"ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
"ValidateAuthority": true
}
}

Étendues de jeton d’accès


Le modèle Blazor WebAssembly ne configure pas automatiquement l’application afin
qu’elle demande un jeton d’accès pour une API sécurisée. Pour provisionner un jeton
d’accès dans le cadre du flux de connexion, ajoutez l’étendue aux étendues de jeton
d’accès par défaut de MsalProviderOptions :

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

Spécifiez des étendues supplémentaires avec AdditionalScopesToConsent :

C#

options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE
URI}");

Pour plus d’informations, consultez les ressources suivantes :

Demander des jetons d’accès supplémentaires


Attacher des jetons aux requêtes sortantes
Démarrage rapide : Configurer une application pour exposer des API web
Étendues de jetons d’accès pour Microsoft API Graph

Mode de connexion
Le framework utilise par défaut le mode de connexion par fenêtre indépendante, et
revient au mode de connexion par redirection si une fenêtre indépendante ne peut pas
être ouverte. Configurez MSAL pour utiliser le mode de connexion par redirection en
affectant la valeur redirect à la propriété LoginMode de MsalProviderOptions :

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.LoginMode = "redirect";
});

Le paramètre par défaut est popup , et la valeur de chaîne ne respecte pas la casse.
Fichier Imports
L’espace de noms Microsoft.AspNetCore.Components.Authorization est rendu
disponible dans l’ensemble de l’application via le fichier _Imports.razor :

razor

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}
@using {APPLICATION ASSEMBLY}.Shared

Page d'index
La page d’index ( wwwroot/index.html ) comprend un script qui définit
AuthenticationService en JavaScript. AuthenticationService gère les détails de bas

niveau du protocole OIDC. L’application appelle de manière interne les méthodes


définies dans le script pour effectuer les opérations d’authentification.

HTML

<script
src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationServic
e.js"></script>

Composant d’application
Le composant App ( App.razor ) est similaire au composant App présent dans les
applications Blazor Server :

Le composant AuthorizeRouteView vérifie que l’utilisateur actuel est autorisé à


accéder à une page donnée, ou affiche le composant RedirectToLogin .
Le composant RedirectToLogin gère la redirection des utilisateurs non autorisés
vers la page de connexion.

En raison des changements apportés au framework dans les différentes versions


d’ASP.NET Core, la syntaxe Razor du composant App ( App.razor ) n’est pas affichée dans
cette section. Pour inspecter la syntaxe du composant d’une version donnée, utilisez
l’une des approches suivantes :

Créez une application provisionnée pour l’authentification à partir du modèle de


projet Blazor WebAssembly par défaut correspondant à la version d’ASP.NET Core
que vous prévoyez d’utiliser. Inspectez le composant App ( App.razor ) dans
l’application générée.

Inspectez le composant App ( App.razor ) dans source de référence . Sélectionnez


la version dans le sélecteur de branche, puis recherchez le composant dans le
dossier ProjectTemplates du référentiel, car il a été déplacé au fil des années.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Composant RedirectToLogin
Le composant RedirectToLogin ( Shared/RedirectToLogin.razor ) :

Gère la redirection des utilisateurs non autorisés vers la page de connexion.


L’URL actuelle à laquelle l’utilisateur tente d’accéder est conservée pour permettre
à l’utilisateur de retourner à cette page en cas d’authentification réussie avec :
État de l’historique de navigation dans ASP.NET Core 7.0 ou version ultérieure.
Chaîne de requête dans ASP.NET Core 6.0 ou version antérieure.

Inspectez le composant RedirectToLogin dans source de référence .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Composant LoginDisplay
Le composant LoginDisplay ( Shared/LoginDisplay.razor ) est affiché dans le composant
MainLayout ( Shared/MainLayout.razor ) et gère les comportements suivants :

Pour les utilisateurs authentifiés :


Affiche le nom d’utilisateur actuel.
Propose un lien vers la page de profil utilisateur dans ASP.NET Core Identity.
Propose un bouton pour se déconnecter de l’application.
Pour les utilisateurs anonymes :
Permet de s’inscrire.
Permet de se connecter.

En raison des changements apportés au framework dans les différentes versions


d’ASP.NET Core, la syntaxe Razor du composant LoginDisplay n’est pas affichée dans
cette section. Pour inspecter la syntaxe du composant d’une version donnée, utilisez
l’une des approches suivantes :

Créez une application provisionnée pour l’authentification à partir du modèle de


projet Blazor WebAssembly par défaut correspondant à la version d’ASP.NET Core
que vous prévoyez d’utiliser. Inspectez le composant LoginDisplay dans
l’application générée.

Inspectez le composant LoginDisplay dans la source de référence . Le contenu


mis en modèle pour Hosted égal à true est utilisé.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Composant Authentication
La page produite par le composant Authentication ( Pages/Authentication.razor )
définit les routes nécessaires à la gestion des différentes phases d’authentification.

Le composant RemoteAuthenticatorView :

Est fourni par le package


Microsoft.AspNetCore.Components.WebAssembly.Authentication .
Gère l’exécution des actions appropriées à chaque phase de l’authentification.

razor

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="Action" />

@code {
[Parameter]
public string? Action { get; set; }
}

7 Notes

Les types références Nullable (NRT) et l’analyse statique de l’état nul du


compilateur .NET sont pris en charge dans ASP.NET Core 6.0 ou version ultérieure.
Avant la publication d’ASP.NET Core 6.0, le type string apparaissait sans la
désignation de type nul ( ? ).

Résoudre des problèmes

Journalisation
Cette section s’applique à ASP.NET Core 7.0 ou version ultérieure.

Pour activer la journalisation du débogage ou des traces dans le cadre de


l’authentification Blazor WebAssembly, consultez Journalisation ASP.NET Core Blazor.

Erreurs courantes
Mauvaise configuration de l’application ou du fournisseur d’identité (Identity
Provider)
Les erreurs les plus courantes sont provoquées par une configuration incorrecte.
Voici quelques exemples :
Selon les besoins du scénario, une autorité, une instance, un ID de locataire, un
domaine de locataire, un ID client ou un URI de redirection manquant ou
incorrect empêche une application d’authentifier les clients.
Une étendue de jeton d’accès incorrecte empêche les clients d’accéder aux
points de terminaison d’API web du serveur.
Des autorisations d’API serveur incorrectes ou manquantes empêchent les
clients d’accéder aux points de terminaison d’API web du serveur.
Exécution de l’application sur un autre port que celui configuré dans l’URI de
redirection de l’inscription d’application du fournisseur d’identité (Identity
Provider).

Les sections de configuration présentes dans les conseils d’aide de cet article
montrent des exemples de configuration appropriée. Examinez attentivement
chaque section de l’article à la recherche d’une mauvaise configuration de
l’application et du fournisseur d’identité.

Si la configuration semble correcte :

Analysez les journaux des applications.

Examinez le trafic réseau entre l’application cliente et le fournisseur d’identité


ou l’application serveur à l’aide des outils de développement du navigateur.
Bien souvent, un message d’erreur exact ou un message indiquant la cause du
problème est retourné au client par le fournisseur d’identité ou l’application
serveur, une fois qu’une demande a été effectuée. Vous trouverez des conseils
d’aide sur les outils de développement dans les articles suivants :
Google Chrome (documentation Google)
Microsoft Edge
Mozilla Firefox (documentation Mozilla)

Décodez le contenu d’un jeton JWT (JSON Web Token) utilisé pour authentifier
un client ou accéder à une API web serveur, selon l’endroit où le problème se
produit. Pour plus d’informations, consultez Inspecter le contenu d’un jeton JWT
(JSON Web Token).

L’équipe de documentation peut répondre aux commentaires et bogues relatifs


aux articles (ouvrez un problème à partir de la section de commentaires de cette
page). Toutefois, elle ne peut pas fournir de support produit. Plusieurs forums de
support publics sont disponibles pour vous aider à résoudre les problèmes liés à
une application. Nous recommandons ce qui suit :
Dépassement de capacité de la pile (étiquette : blazor)
Équipe ASP.NET Core Slack
Blazor Gitter

Les forums précédents ne sont pas détenus ou contrôlés par Microsoft.

Pour les rapports de bogues de framework reproductibles, non liés à la sécurité,


non sensibles et non confidentiels, ouvrez un problème auprès de l’unité de
produit ASP.NET Core . N’ouvrez pas de problème auprès de l’unité de produit
tant que vous n’avez pas investigué de manière approfondie la cause du problème,
sans pouvoir le résoudre par vous-même ou avec l’aide de la communauté sur un
forum de support public. L’unité de produit ne peut pas résoudre les problèmes
d’applications individuelles qui sont défaillantes en raison d’une mauvaise
configuration ou de cas d’usage impliquant des services tiers. Si un rapport est de
nature sensible ou confidentielle, ou s’il décrit une faille de sécurité potentielle
(exploitable par des attaquants) dans le produit, consultez Signaler des problèmes
de sécurité et des bogues (référentiel GitHub dotnet/aspnetcore) .

Client non autorisé pour ME-ID

Info : Échec de l’autorisation


Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]. Ces
conditions n’ont pas été remplies :
DenyAnonymousAuthorizationRequirement : Nécessite un utilisateur
authentifié.

Erreur de rappel de la connexion à partir de ME-ID :


Erreur : unauthorized_client
Description : AADB2C90058: The provided application is not configured to
allow public clients.

Pour résoudre l’erreur :

1. Dans le portail Azure, accédez au manifeste de l’application.


2. Affectez à l’attribut allowPublicClient la valeur null ou true .

Cookies et données de site


Les Cookies et les données de site peuvent persister entre les mises à jour d’application,
et interférer avec les tests et la résolution des problèmes. Effacez ce qui suit quand vous
apportez des changements au code d’application, au compte d’utilisateur du fournisseur
ou à la configuration de l’application :
cookies de connexion de l’utilisateur
cookies d’application
Données de site mises en cache et stockées

Il existe une approche qui permet d’empêcher les cookie et les données de site
persistants d’interférer avec les tests et la résolution des problèmes. Elle consiste à :

Configurer un navigateur
Utilisez un navigateur de test que vous pouvez configurer pour supprimer tous
les cookies et toutes les données de site à chaque fois qu’il se ferme.
Vérifiez que le navigateur est fermé manuellement ou par l’IDE chaque fois
qu’un changement est apporté à la configuration de l’application, de l’utilisateur
de test ou du fournisseur.
Utilisez une commande personnalisée pour ouvrir un navigateur en mode InPrivate
ou Incognito dans Visual Studio :
Ouvrez la boîte de dialogue Parcourir avec à partir du bouton Exécuter de
Visual Studio.
Cliquez sur le bouton Ajouter.
Indiquez le chemin de votre navigateur dans le champ Programme. Les chemins
d’exécutables suivants sont des emplacements d’installation classiques de
Windows 10. Si votre navigateur est installé à un autre emplacement, ou si vous
n’utilisez pas Windows 10, indiquez le chemin de l’exécutable du navigateur.
Microsoft Edge : C:\Program Files
(x86)\Microsoft\Edge\Application\msedge.exe

Google Chrome : C:\Program Files


(x86)\Google\Chrome\Application\chrome.exe

Mozilla Firefox : C:\Program Files\Mozilla Firefox\firefox.exe


Dans le champ Arguments, indiquez l’option de ligne de commande utilisée par
le navigateur pour qu’il s’ouvre en mode InPrivate ou Incognito. Certains
navigateurs nécessitent l’URL de l’application.
Microsoft Edge : Utilisez -inprivate .
Google Chrome : Utilisez --incognito --new-window {URL} , où l’espace
réservé {URL} correspond à l’URL à ouvrir (par exemple
https://localhost:5001 ).

Mozilla Firefox : Utilisez -private -url {URL} , où l’espace réservé {URL}


correspond à l’URL à ouvrir (par exemple https://localhost:5001 ).
Indiquez un nom dans le champ Nom convivial. Par exemple, Firefox Auth
Testing

Cliquez sur le bouton OK.


Pour éviter d’avoir à sélectionner le profil de navigateur pour chaque itération
de test avec une application, définissez le profil en tant que profil par défaut
avec le bouton Par défaut.
Vérifiez que le navigateur est fermé par l’IDE chaque fois qu’un changement est
apporté à la configuration de l’application, de l’utilisateur de test ou du
fournisseur.

Mises à niveau d’application


Une application fonctionnelle peut échouer immédiatement après la mise à niveau du
kit SDK .NET Core sur l’ordinateur de développement ou la modification des versions de
package au sein de l’application. Dans certains cas, les packages incohérents peuvent
bloquer une application quand vous effectuez des mises à niveau majeures. Vous
pouvez résoudre la plupart de ces problèmes en suivant les instructions suivantes :

1. Effacez les caches de package NuGet du système local en exécutant dotnet nuget
locals all --clear à partir d’un interpréteur de commandes.
2. Supprimez les dossiers bin et obj du projet.
3. Restaurez et regénérez le projet.
4. Supprimez tous les fichiers du dossier de déploiement sur le serveur avant de
redéployer l’application.

7 Notes

L’utilisation de versions de package incompatibles avec le framework cible de


l’application n’est pas prise en charge. Pour plus d’informations sur un package,
utilisez la Galerie NuGet ou l’Explorateur de packages FuGet .

Inspecter l’utilisateur
Le composant User suivant peut être utilisé directement dans les applications, ou servir
de base à une personnalisation supplémentaire.

User.razor :

razor

@page "/user"
@attribute [Authorize]
@using System.Text.Json
@using System.Security.Claims
@inject IAccessTokenProvider AuthorizationService
<h1>@AuthenticatedUser?.Identity?.Name</h1>

<h2>Claims</h2>

@foreach (var claim in AuthenticatedUser?.Claims ?? Array.Empty<Claim>())


{
<p class="claim">@(claim.Type): @claim.Value</p>
}

<h2>Access token</h2>

<p id="access-token">@AccessToken?.Value</p>

<h2>Access token claims</h2>

@foreach (var claim in GetAccessTokenClaims())


{
<p>@(claim.Key): @claim.Value.ToString()</p>
}

@if (AccessToken != null)


{
<h2>Access token expires</h2>

<p>Current time: <span id="current-time">@DateTimeOffset.Now</span></p>


<p id="access-token-expires">@AccessToken.Expires</p>

<h2>Access token granted scopes (as reported by the API)</h2>

@foreach (var scope in AccessToken.GrantedScopes)


{
<p>Scope: @scope</p>
}
}

@code {
[CascadingParameter]
private Task<AuthenticationState> AuthenticationState { get; set; }

public ClaimsPrincipal AuthenticatedUser { get; set; }


public AccessToken AccessToken { get; set; }

protected override async Task OnInitializedAsync()


{
await base.OnInitializedAsync();
var state = await AuthenticationState;
var accessTokenResult = await
AuthorizationService.RequestAccessToken();

if (!accessTokenResult.TryGetToken(out var token))


{
throw new InvalidOperationException(
"Failed to provision the access token.");
}
AccessToken = token;

AuthenticatedUser = state.User;
}

protected IDictionary<string, object> GetAccessTokenClaims()


{
if (AccessToken == null)
{
return new Dictionary<string, object>();
}

// header.payload.signature
var payload = AccessToken.Value.Split(".")[1];
var base64Payload = payload.Replace('-', '+').Replace('_', '/')
.PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');

return JsonSerializer.Deserialize<IDictionary<string, object>>(


Convert.FromBase64String(base64Payload));
}
}

Inspecter le contenu d’un jeton JWT (JSON Web Token)


Pour décoder un jeton JWT (JSON Web Token), utilisez l’outil jwt.ms de Microsoft. Les
valeurs de l’IU ne quittent jamais votre navigateur.

Exemple de jeton JWT codé (raccourci pour des raisons d’affichage) :

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0
djhkbE5QNC1j...
bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzp
D-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-
yBMKV2_nXA25Q

Exemple JWT décodé par l’outil pour une application qui s’authentifie auprès d’Azure
AAD B2C :

JSON

{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1610059429,
"nbf": 1610055829,
"ver": "1.0",
"iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-
226dcc9ad298/v2.0/",
"sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
"aud": "70bde375-fce3-4b82-984a-b247d823a03f",
"nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
"iat": 1610055829,
"auth_time": 1610055822,
"idp": "idp.com",
"tfp": "B2C_1_signupsignin"
}.[Signature]

Ressources supplémentaires
Autres scénarios de sécurité ASP.NET Core Blazor WebAssembly
Créer une version personnalisée de la bibliothèque JavaScript Authentication.MSAL
Requêtes d’API web non authentifiées ou non autorisées dans une application avec
un client par défaut sécurisé
ASP.NET Core Blazor WebAssembly avec les groupes et rôles Microsoft Entra ID
Plateforme d’identités Microsoft et Microsoft Entra ID avec ASP.NET Core
Documentation sur la plateforme d’identités Microsoft
Meilleures pratiques de sécurité pour les propriétés d’application dans Microsoft
Entra ID

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Sécuriser une application autonome
ASP.NET Core Blazor WebAssembly avec
Azure Active Directory B2C
Article • 09/02/2024

Cet article explique comment créer une application Blazor WebAssembly autonome qui
utilise AAD (Azure Active Directory) B2C à des fins d’authentification.

Pour obtenir une couverture supplémentaire du scénario de sécurité après avoir lu cet
article, consultez ASP.NET Core Blazor WebAssembly scénarios de sécurité
supplémentaires.

Procédure pas à pas


Les sous-sections de la procédure pas à pas expliquent comment :

Créer un locataire dans Azure


Inscrire une application auprès d’Azure
Créer l’application Blazor
Exécuter l’application

Créer un locataire dans Azure


Suivez les conseils d’aide fournis dans Tutoriel : Créer un locataire Azure Active Directory
B2C pour créer un locataire AAD B2C.

Avant de suivre les instructions de cet article, vérifiez que vous avez sélectionné le
répertoire approprié pour le locataire AAD B2C.

Inscrire une application auprès d’Azure


Inscrivez une application AAD B2C :

1. Accédez à Azure AD B2C dans le portail Azure. Sélectionnez Inscriptions


d’applications dans la barre latérale. Sélectionnez le bouton Nouvelle inscription.
2. Indiquez un nom pour l’application (par exemple Application Blazor AAD B2C
autonome).
3. Pour types de comptes pris en charge, sélectionnez l’option multilocataire :
Comptes dans n’importe quel annuaire organisationnel ou n’importe quel
fournisseur d’identité. Pour authentifier les utilisateurs avec Azure AD B2C.
4. Affectez à la liste déroulante URI de redirection la valeur Application monopage
(SPA), puis indiquez l’URI de redirection suivant :
https://localhost/authentication/login-callback . Si vous connaissez l’URI de

redirection de production de l’hôte par défaut Azure (par exemple


azurewebsites.net ) ou de l’hôte de domaine personnalisé (par exemple
contoso.com ), vous pouvez également ajouter l’URI de redirection de production

en même temps que vous en fournissant l’URI de redirection localhost . Veillez à


inclure le numéro de port des autres ports que le port :443 dans les URI de
redirection de production que vous ajoutez.
5. Si vous utilisez un domaine d’éditeur non vérifié, vérifiez que l’option
Autorisations>Octroyer le consentement administrateur aux autorisations
openid et offline_access est sélectionnée. Si le domaine d’éditeur est vérifié, cette
case à cocher n’est pas présente.
6. Sélectionnez Inscription.

7 Notes

Il n’est pas nécessaire de fournir le numéro de port d’un URI de redirection


localhost AAD B2C. Pour plus d’informations, consultez Restrictions et limitations

de l’URI de redirection (URL de réponse) : exceptions localhost (documentation


Entra).

Enregistrez les informations suivantes :

ID d’application (client) (par exemple 41451fa7-82d9-4673-8fa5-69eff5a761fd ).


Instance AAD B2C (par exemple https://contoso.b2clogin.com/ , qui inclut la barre
oblique de fin) : L’instance est le schéma et l’hôte d’une inscription d’application
Azure B2C, que vous pouvez trouver en ouvrant la fenêtre Points de terminaison à
partir de la page Inscriptions d’applications dans le portail Azure.
Domaine AAD B2C principal/d’éditeur/de locataire (par exemple
contoso.onmicrosoft.com ) : le domaine est disponible en tant que domaine

d’éditeur dans le panneau Personnalisation du portail Azure de l’application


inscrite.

Dans Authentification>Configurations de plateforme>Application monopage :

1. Vérifiez que l’URI de redirection https://localhost/authentication/login-callback


est présent.
2. Dans la section Octroi implicite, vérifiez que les cases des jetons d’accès et des
jetons d’ID ne sont pas cochées. L’octroi implicite n’est pas recommandé pour les
applications Blazor qui utilisent MSAL v2.0 ou version ultérieure. Pour plus
d’informations, consultez Sécuriser ASP.NET Core Blazor WebAssembly.
3. Les autres valeurs par défaut de l’application sont acceptables dans le cadre de
cette expérience.
4. Cliquez sur le bouton Enregistrer si vous avez apporté des modifications.

Dans Home>Azure AD B2C>Flux utilisateur :

Créer un flux d’utilisateur d’inscription et de connexion

Au minimum, sélectionnez l’attribut utilisateur revendications d’application >nom


d’affichage pour remplir le context.User.Identity?.Name / context.User.Identity.Name
dans le composant LoginDisplay ( Shared/LoginDisplay.razor ).

Notez le nom du flux utilisateur créé pour les étapes d’inscription et de connexion de
l’application (par exemple B2C_1_signupsignin ).

Créer l’application Blazor


Dans un dossier vide, remplacez les espaces réservés dans la commande suivante par les
informations notées plus tôt, puis exécutez la commande dans un interpréteur de
commandes :

CLI .NET

dotnet new blazorwasm -au IndividualB2C --aad-b2c-instance "{AAD B2C


INSTANCE}" --client-id "{CLIENT ID}" --domain "{TENANT DOMAIN}" -o {PROJECT
NAME} -ssp "{SIGN UP OR SIGN IN POLICY}"

ノ Agrandir le tableau

Espace réservé Nom du portail Azure Exemple

{AAD B2C Instance https://contoso.b2clogin.com/ (barre


INSTANCE} oblique de fin incluse)

{PROJECT NAME} — BlazorSample

{CLIENT ID} ID d’application (client) 41451fa7-82d9-4673-8fa5-69eff5a761fd

{SIGN UP OR SIGN Flux utilisateur pour B2C_1_signupsignin1


IN POLICY} l’inscription/la connexion

{TENANT DOMAIN} Domaine principal/d’éditeur/de contoso.onmicrosoft.com


locataire
L’emplacement de sortie spécifié avec l’option -o|--output crée un dossier de projet s’il
n’existe pas, et devient partie intégrante du nom du projet.

Ajoutez une paire de MsalProviderOptions pour openid et


offline_access DefaultAccessTokenScopes :

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("openid");
options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access");
});

Une fois l’application créée, vous devez pouvoir :

Connectez-vous à l’application à l’aide d’un compte d’utilisateur Microsoft Entra


ID.
Demander des jetons d’accès pour les API Microsoft. Pour plus d'informations, voir
:
Étendues de jeton d’accès
Démarrage rapide : Configurer une application pour exposer des API web.

Exécuter l’application
Utilisez l’une des approches suivantes pour exécuter l’application :

Visual Studio
Sélectionnez le bouton Run.
Utilisez Déboguer>Démarrer le débogage dans le menu.
Appuyez sur F5 .
Interpréteur de commandes .NET CLI : exécutez la commande dotnet run à partir
du dossier de l’application.

Parties de l’application
Cette section décrit les parties d’une application générées à partir du modèle de projet
Blazor WebAssembly ainsi que la façon dont l’application est configurée. Il n’existe
aucun conseil d’aide spécifique à suivre dans cette section pour une application de base,
si vous avez créé l’application en suivant les conseils d’aide de la section Procédure pas
à pas. Les conseils d’aide de cette section sont utiles pour mettre à jour une application
afin d’authentifier et d’autoriser les utilisateurs. Toutefois, il existe une autre approche
pour la mise à jour d’une application. Elle consiste à créer une application à partir des
conseils d’aide de la section Procédure pas à pas, et à déplacer les composants, les
classes et les ressources de l’application à mettre à jour vers la nouvelle application.

Package d’authentification
Quand une application est créée pour utiliser un compte B2C individuel ( IndividualB2C ),
l’application reçoit automatiquement une référence de package pour la bibliothèque
d’authentification Microsoft (Microsoft.Authentication.WebAssembly.Msal ). Le
package fournit un ensemble de primitives qui permettent à l’application d’authentifier
les utilisateurs et d’obtenir des jetons pour appeler les API protégées.

Si vous ajoutez l’authentification à une application, ajoutez manuellement le package


Microsoft.Authentication.WebAssembly.Msal à l’application.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Le package Microsoft.Authentication.WebAssembly.Msal ajoute de façon transitive le


package Microsoft.AspNetCore.Components.WebAssembly.Authentication à
l’application.

Prise en charge du service d’authentification


La prise en charge de l’authentification des utilisateurs est inscrite auprès du conteneur
de services avec la méthode d’extension AddMsalAuthentication fournie par le package
Microsoft.Authentication.WebAssembly.Msal . Cette méthode configure tous les
services nécessaires à l’application pour interagir avec le fournisseur d’identité (Identity
Provider).

Dans le fichier Program :

C#

builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAdB2C",
options.ProviderOptions.Authentication);
});

La méthode AddMsalAuthentication accepte un rappel pour configurer les paramètres


nécessaires à l’authentification d’une application. Les valeurs requises pour configurer
l’application peuvent être obtenues à partir de la configuration lorsque vous inscrivez
l’application.

La configuration est fournie par le fichier wwwroot/appsettings.json :

JSON

{
"AzureAdB2C": {
"Authority": "{AAD B2C INSTANCE}{TENANT DOMAIN}/{SIGN UP OR SIGN IN
POLICY}",
"ClientId": "{CLIENT ID}",
"ValidateAuthority": false
}
}

Dans la configuration précédente, {AAD B2C INSTANCE} inclut une barre oblique de fin.

Exemple :

JSON

{
"AzureAdB2C": {
"Authority":
"https://contoso.b2clogin.com/contoso.onmicrosoft.com/B2C_1_signupsignin1",
"ClientId": "41451fa7-82d9-4673-8fa5-69eff5a761fd",
"ValidateAuthority": false
}
}

Étendues de jeton d’accès


Le modèle Blazor WebAssembly ne configure pas automatiquement l’application afin
qu’elle demande un jeton d’accès pour une API sécurisée. Pour provisionner un jeton
d’accès dans le cadre du flux de connexion, ajoutez l’étendue aux étendues de jeton
d’accès par défaut de MsalProviderOptions :

C#
builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});

Spécifiez des étendues supplémentaires avec AdditionalScopesToConsent :

C#

options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE
URI}");

Pour plus d’informations, consultez les sections suivantes de l’article Scénarios


supplémentaires :

Demander des jetons d’accès supplémentaires


Attacher des jetons aux requêtes sortantes

Mode de connexion
Le framework utilise par défaut le mode de connexion par fenêtre indépendante, et
revient au mode de connexion par redirection si une fenêtre indépendante ne peut pas
être ouverte. Configurez MSAL pour utiliser le mode de connexion par redirection en
affectant la valeur redirect à la propriété LoginMode de MsalProviderOptions :

C#

builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.LoginMode = "redirect";
});

Le paramètre par défaut est popup , et la valeur de chaîne ne respecte pas la casse.

Fichier Imports
L’espace de noms Microsoft.AspNetCore.Components.Authorization est rendu
disponible dans l’ensemble de l’application via le fichier _Imports.razor :

razor
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}
@using {APPLICATION ASSEMBLY}.Shared

Page d'index
La page d’index ( wwwroot/index.html ) comprend un script qui définit
AuthenticationService en JavaScript. AuthenticationService gère les détails de bas

niveau du protocole OIDC. L’application appelle de manière interne les méthodes


définies dans le script pour effectuer les opérations d’authentification.

HTML

<script
src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationServic
e.js"></script>

Composant d’application
Le composant App ( App.razor ) est similaire au composant App présent dans les
applications Blazor Server :

Le composant AuthorizeRouteView vérifie que l’utilisateur actuel est autorisé à


accéder à une page donnée, ou affiche le composant RedirectToLogin .
Le composant RedirectToLogin gère la redirection des utilisateurs non autorisés
vers la page de connexion.

En raison des changements apportés au framework dans les différentes versions


d’ASP.NET Core, la syntaxe Razor du composant App ( App.razor ) n’est pas affichée dans
cette section. Pour inspecter la syntaxe du composant d’une version donnée, utilisez
l’une des approches suivantes :

Créez une application provisionnée pour l’authentification à partir du modèle de


projet Blazor WebAssembly par défaut correspondant à la version d’ASP.NET Core
que vous prévoyez d’utiliser. Inspectez le composant App ( App.razor ) dans
l’application générée.

Inspectez le composant App ( App.razor ) dans source de référence . Sélectionnez


la version dans le sélecteur de branche, puis recherchez le composant dans le
dossier ProjectTemplates du référentiel, car il a été déplacé au fil des années.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Composant RedirectToLogin
Le composant RedirectToLogin ( Shared/RedirectToLogin.razor ) :

Gère la redirection des utilisateurs non autorisés vers la page de connexion.


L’URL actuelle à laquelle l’utilisateur tente d’accéder est conservée pour permettre
à l’utilisateur de retourner à cette page en cas d’authentification réussie avec :
État de l’historique de navigation dans ASP.NET Core, dans .NET 7 ou une
version ultérieure.
Une chaîne de requête dans ASP.NET Core, dans .NET 6 ou une version
antérieure.

Inspectez le composant RedirectToLogin dans source de référence .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .
Composant LoginDisplay
Le composant LoginDisplay ( Shared/LoginDisplay.razor ) est affiché dans le composant
MainLayout ( Shared/MainLayout.razor ) et gère les comportements suivants :

Pour les utilisateurs authentifiés :


Affiche le nom d’utilisateur actuel.
Propose un lien vers la page de profil utilisateur dans ASP.NET Core Identity.
Propose un bouton pour se déconnecter de l’application.
Pour les utilisateurs anonymes :
Permet de s’inscrire.
Permet de se connecter.

En raison des changements apportés au framework dans les différentes versions


d’ASP.NET Core, la syntaxe Razor du composant LoginDisplay n’est pas affichée dans
cette section. Pour inspecter la syntaxe du composant d’une version donnée, utilisez
l’une des approches suivantes :

Créez une application provisionnée pour l’authentification à partir du modèle de


projet Blazor WebAssembly par défaut correspondant à la version d’ASP.NET Core
que vous prévoyez d’utiliser. Inspectez le composant LoginDisplay dans
l’application générée.

Inspectez le composant LoginDisplay dans la source de référence . Le contenu


mis en modèle pour Hosted égal à true est utilisé.

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

Composant Authentication
La page produite par le composant Authentication ( Pages/Authentication.razor )
définit les routes nécessaires à la gestion des différentes phases d’authentification.
Le composant RemoteAuthenticatorView :

Est fourni par le package


Microsoft.AspNetCore.Components.WebAssembly.Authentication .
Gère l’exécution des actions appropriées à chaque phase de l’authentification.

razor

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="Action" />

@code {
[Parameter]
public string? Action { get; set; }
}

7 Notes

L’Analyse statique des types de référence nullables (NRT) et de l’état null du


compilateur .NET est prise en charge dans ASP.NET Core, dans .NET 6 ou une
version ultérieure. Avant la mise en production d’ASP.NET Core dans .NET 6, le type
string apparaissait sans la désignation de type nul ( ? ).

Stratégies personnalisées
La bibliothèque d’authentification Microsoft
(Microsoft.Authentication.WebAssembly.Msal, package NuGet ) ne prend pas en
charge stratégies personnalisées AAD B2C par défaut.

Résoudre des problèmes

Journalisation
Cette section s’applique à ASP.NET Core, dans .NET 7 ou une version ultérieure.

Pour activer la journalisation du débogage ou des traces dans le cadre de


l’authentification Blazor WebAssembly, consultez Journalisation ASP.NET Core Blazor.

Erreurs courantes
Mauvaise configuration de l’application ou du fournisseur d’identité (Identity
Provider)

Les erreurs les plus courantes sont provoquées par une configuration incorrecte.
Voici quelques exemples :
Selon les besoins du scénario, une autorité, une instance, un ID de locataire, un
domaine de locataire, un ID client ou un URI de redirection manquant ou
incorrect empêche une application d’authentifier les clients.
Une étendue de jeton d’accès incorrecte empêche les clients d’accéder aux
points de terminaison d’API web du serveur.
Des autorisations d’API serveur incorrectes ou manquantes empêchent les
clients d’accéder aux points de terminaison d’API web du serveur.
Exécution de l’application sur un autre port que celui configuré dans l’URI de
redirection de l’inscription d’application du fournisseur d’identité (Identity
Provider).

Les sections de configuration présentes dans les conseils d’aide de cet article
montrent des exemples de configuration appropriée. Examinez attentivement
chaque section de l’article à la recherche d’une mauvaise configuration de
l’application et du fournisseur d’identité.

Si la configuration semble correcte :

Analysez les journaux des applications.

Examinez le trafic réseau entre l’application cliente et le fournisseur d’identité


ou l’application serveur à l’aide des outils de développement du navigateur.
Bien souvent, un message d’erreur exact ou un message indiquant la cause du
problème est retourné au client par le fournisseur d’identité ou l’application
serveur, une fois qu’une demande a été effectuée. Vous trouverez des conseils
d’aide sur les outils de développement dans les articles suivants :
Google Chrome (documentation Google)
Microsoft Edge
Mozilla Firefox (documentation Mozilla)

Décodez le contenu d’un jeton JWT (JSON Web Token) utilisé pour authentifier
un client ou accéder à une API web serveur, selon l’endroit où le problème se
produit. Pour plus d’informations, consultez Inspecter le contenu d’un jeton JWT
(JSON Web Token).

L’équipe de documentation peut répondre aux commentaires et bogues relatifs


aux articles (ouvrez un problème à partir de la section de commentaires de cette
page). Toutefois, elle ne peut pas fournir de support produit. Plusieurs forums de
support publics sont disponibles pour vous aider à résoudre les problèmes liés à
une application. Nous recommandons ce qui suit :
Dépassement de capacité de la pile (étiquette : blazor)
Équipe ASP.NET Core Slack
Blazor Gitter

Les forums précédents ne sont pas détenus ou contrôlés par Microsoft.

Pour les rapports de bogues de framework reproductibles, non liés à la sécurité,


non sensibles et non confidentiels, ouvrez un problème auprès de l’unité de
produit ASP.NET Core . N’ouvrez pas de problème auprès de l’unité de produit
tant que vous n’avez pas investigué de manière approfondie la cause du problème,
sans pouvoir le résoudre par vous-même ou avec l’aide de la communauté sur un
forum de support public. L’unité de produit ne peut pas résoudre les problèmes
d’applications individuelles qui sont défaillantes en raison d’une mauvaise
configuration ou de cas d’usage impliquant des services tiers. Si un rapport est de
nature sensible ou confidentielle, ou s’il décrit une faille de sécurité potentielle
(exploitable par des attaquants) dans le produit, consultez Signaler des problèmes
de sécurité et des bogues (référentiel GitHub dotnet/aspnetcore) .

Client non autorisé pour ME-ID

Info : Échec de l’autorisation


Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]. Ces
conditions n’ont pas été remplies :
DenyAnonymousAuthorizationRequirement : Nécessite un utilisateur
authentifié.

Erreur de rappel de la connexion à partir de ME-ID :


Erreur : unauthorized_client
Description : AADB2C90058: The provided application is not configured to
allow public clients.

Pour résoudre l’erreur :

1. Dans le portail Azure, accédez au manifeste de l’application.


2. Affectez à l’attribut allowPublicClient la valeur null ou true .

Cookies et données de site


Les Cookies et les données de site peuvent persister entre les mises à jour d’application,
et interférer avec les tests et la résolution des problèmes. Effacez ce qui suit quand vous
apportez des changements au code d’application, au compte d’utilisateur du fournisseur
ou à la configuration de l’application :

cookies de connexion de l’utilisateur


cookies d’application
Données de site mises en cache et stockées

Il existe une approche qui permet d’empêcher les cookie et les données de site
persistants d’interférer avec les tests et la résolution des problèmes. Elle consiste à :

Configurer un navigateur
Utilisez un navigateur de test que vous pouvez configurer pour supprimer tous
les cookies et toutes les données de site à chaque fois qu’il se ferme.
Vérifiez que le navigateur est fermé manuellement ou par l’IDE chaque fois
qu’un changement est apporté à la configuration de l’application, de l’utilisateur
de test ou du fournisseur.
Utilisez une commande personnalisée pour ouvrir un navigateur en mode InPrivate
ou Incognito dans Visual Studio :
Ouvrez la boîte de dialogue Parcourir avec à partir du bouton Exécuter de
Visual Studio.
Cliquez sur le bouton Ajouter.
Indiquez le chemin de votre navigateur dans le champ Programme. Les chemins
d’exécutables suivants sont des emplacements d’installation classiques de
Windows 10. Si votre navigateur est installé à un autre emplacement, ou si vous
n’utilisez pas Windows 10, indiquez le chemin de l’exécutable du navigateur.
Microsoft Edge : C:\Program Files
(x86)\Microsoft\Edge\Application\msedge.exe

Google Chrome : C:\Program Files


(x86)\Google\Chrome\Application\chrome.exe

Mozilla Firefox : C:\Program Files\Mozilla Firefox\firefox.exe


Dans le champ Arguments, indiquez l’option de ligne de commande utilisée par
le navigateur pour qu’il s’ouvre en mode InPrivate ou Incognito. Certains
navigateurs nécessitent l’URL de l’application.
Microsoft Edge : Utilisez -inprivate .
Google Chrome : Utilisez --incognito --new-window {URL} , où l’espace
réservé {URL} correspond à l’URL à ouvrir (par exemple
https://localhost:5001 ).

Mozilla Firefox : Utilisez -private -url {URL} , où l’espace réservé {URL}


correspond à l’URL à ouvrir (par exemple https://localhost:5001 ).
Indiquez un nom dans le champ Nom convivial. Par exemple, Firefox Auth
Testing
Cliquez sur le bouton OK.
Pour éviter d’avoir à sélectionner le profil de navigateur pour chaque itération
de test avec une application, définissez le profil en tant que profil par défaut
avec le bouton Par défaut.
Vérifiez que le navigateur est fermé par l’IDE chaque fois qu’un changement est
apporté à la configuration de l’application, de l’utilisateur de test ou du
fournisseur.

Mises à niveau d’application


Une application fonctionnelle peut échouer immédiatement après la mise à niveau du
kit SDK .NET Core sur l’ordinateur de développement ou la modification des versions de
package au sein de l’application. Dans certains cas, les packages incohérents peuvent
bloquer une application quand vous effectuez des mises à niveau majeures. Vous
pouvez résoudre la plupart de ces problèmes en suivant les instructions suivantes :

1. Effacez les caches de package NuGet du système local en exécutant dotnet nuget
locals all --clear à partir d’un interpréteur de commandes.
2. Supprimez les dossiers bin et obj du projet.
3. Restaurez et regénérez le projet.
4. Supprimez tous les fichiers du dossier de déploiement sur le serveur avant de
redéployer l’application.

7 Notes

L’utilisation de versions de package incompatibles avec le framework cible de


l’application n’est pas prise en charge. Pour plus d’informations sur un package,
utilisez la Galerie NuGet ou l’Explorateur de packages FuGet .

Inspecter l’utilisateur
Le composant User suivant peut être utilisé directement dans les applications, ou servir
de base à une personnalisation supplémentaire.

User.razor :

razor

@page "/user"
@attribute [Authorize]
@using System.Text.Json
@using System.Security.Claims
@inject IAccessTokenProvider AuthorizationService

<h1>@AuthenticatedUser?.Identity?.Name</h1>

<h2>Claims</h2>

@foreach (var claim in AuthenticatedUser?.Claims ?? Array.Empty<Claim>())


{
<p class="claim">@(claim.Type): @claim.Value</p>
}

<h2>Access token</h2>

<p id="access-token">@AccessToken?.Value</p>

<h2>Access token claims</h2>

@foreach (var claim in GetAccessTokenClaims())


{
<p>@(claim.Key): @claim.Value.ToString()</p>
}

@if (AccessToken != null)


{
<h2>Access token expires</h2>

<p>Current time: <span id="current-time">@DateTimeOffset.Now</span></p>


<p id="access-token-expires">@AccessToken.Expires</p>

<h2>Access token granted scopes (as reported by the API)</h2>

@foreach (var scope in AccessToken.GrantedScopes)


{
<p>Scope: @scope</p>
}
}

@code {
[CascadingParameter]
private Task<AuthenticationState> AuthenticationState { get; set; }

public ClaimsPrincipal AuthenticatedUser { get; set; }


public AccessToken AccessToken { get; set; }

protected override async Task OnInitializedAsync()


{
await base.OnInitializedAsync();
var state = await AuthenticationState;
var accessTokenResult = await
AuthorizationService.RequestAccessToken();

if (!accessTokenResult.TryGetToken(out var token))


{
throw new InvalidOperationException(
"Failed to provision the access token.");
}

AccessToken = token;

AuthenticatedUser = state.User;
}

protected IDictionary<string, object> GetAccessTokenClaims()


{
if (AccessToken == null)
{
return new Dictionary<string, object>();
}

// header.payload.signature
var payload = AccessToken.Value.Split(".")[1];
var base64Payload = payload.Replace('-', '+').Replace('_', '/')
.PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');

return JsonSerializer.Deserialize<IDictionary<string, object>>(


Convert.FromBase64String(base64Payload));
}
}

Inspecter le contenu d’un jeton JWT (JSON Web Token)


Pour décoder un jeton JWT (JSON Web Token), utilisez l’outil jwt.ms de Microsoft. Les
valeurs de l’IU ne quittent jamais votre navigateur.

Exemple de jeton JWT codé (raccourci pour des raisons d’affichage) :

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0
djhkbE5QNC1j...
bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzp
D-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-
yBMKV2_nXA25Q

Exemple JWT décodé par l’outil pour une application qui s’authentifie auprès d’Azure
AAD B2C :

JSON

{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1610059429,
"nbf": 1610055829,
"ver": "1.0",
"iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-
226dcc9ad298/v2.0/",
"sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
"aud": "70bde375-fce3-4b82-984a-b247d823a03f",
"nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
"iat": 1610055829,
"auth_time": 1610055822,
"idp": "idp.com",
"tfp": "B2C_1_signupsignin"
}.[Signature]

Ressources supplémentaires
Autres scénarios de sécurité ASP.NET Core Blazor WebAssembly
Créer une version personnalisée de la bibliothèque JavaScript Authentication.MSAL
Requêtes d’API web non authentifiées ou non autorisées dans une application avec
un client par défaut sécurisé
Authentification cloud avec Azure Active Directory B2C dans ASP.NET Core
Tutoriel : Créer un locataire Azure Active Directory B2C
Tutoriel : inscrire une application dans Azure Active Directory B2C
Documentation sur la plateforme d’identités Microsoft

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Autres scénarios de sécurité ASP.NET
Core Blazor WebAssembly
Article • 06/12/2023

Cet article décrit d’autres scénarios de sécurité pour les applications Blazor
WebAssembly.

Attacher des jetons aux requêtes sortantes


AuthorizationMessageHandler est un DelegatingHandler utilisé pour traiter les jetons
d’accès. Les jetons sont acquis à l’aide du service IAccessTokenProvider, qui est inscrit
par l’infrastructure. Si un jeton ne peut pas être acquis, un
AccessTokenNotAvailableException est levé. AccessTokenNotAvailableException a une
méthode Redirect qui accède à AccessTokenResult.InteractiveRequestUrl en utilisant le
AccessTokenResult.InteractionOptions donné pour permettre l’actualisation du jeton
d’accès.

Pour plus de commodité, l’infrastructure fournit le


BaseAddressAuthorizationMessageHandler préconfiguré avec l’adresse de base de
l’application en tant qu’URL autorisée. Les jetons d’accès sont ajoutés uniquement
lorsque l’URI de requête se trouve dans l’URI de base de l’application. Lorsque les URI
de requête sortants ne se trouvent pas dans l’URI de base de l’application, utilisez une
classe AuthorizationMessageHandler personnalisée (recommandée) ou configurez le
AuthorizationMessageHandler.

7 Notes

En plus de la configuration de l’application cliente pour l’accès à l’API serveur, l’API


serveur doit également autoriser les requêtes cross-origin (CORS) lorsque le client
et le serveur ne résident pas à la même adresse de base. Pour plus d’informations
sur la configuration de CORS côté serveur, consultez la section Partage de
ressources Cross-Origin (CORS) plus loin dans cet article.

Dans l’exemple suivant :

AddHttpClient ajoute IHttpClientFactory et les services associés à la collection de


services et configure un HttpClient nommé ( WebAPI ). HttpClient.BaseAddress est
l’adresse de base de l’URI de ressource lors de l’envoi de requêtes.
IHttpClientFactory est fourni par le package NuGet Microsoft.Extensions.Http .
BaseAddressAuthorizationMessageHandler est le DelegatingHandler utilisé pour
traiter les jetons d’accès. Les jetons d’accès sont ajoutés uniquement lorsque l’URI
de requête se trouve dans l’URI de base de l’application.
IHttpClientFactory.CreateClient crée et configure une instance HttpClient pour les
requêtes sortantes utilisant la configuration qui correspond au HttpClient nommé
( WebAPI ).

Dans l’exemple suivant, HttpClientFactoryServiceCollectionExtensions.AddHttpClient est


une extension dans Microsoft.Extensions.Http. Ajoutez le package à une application qui
ne le référence pas déjà.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

C#

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient("WebAPI",
client => client.BaseAddress = new
Uri("https://www.example.com/base"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()


.CreateClient("WebAPI"));

Le HttpClient configuré est utilisé pour effectuer des requêtes autorisées à l’aide du
modèle try-catch :

razor

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http

...

protected override async Task OnInitializedAsync()


{
try
{
var examples =
await Http.GetFromJsonAsync<ExampleType[]>("ExampleAPIMethod");

...
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}

Scénarios de requête d’authentification


personnalisée
Les scénarios suivants montrent comment personnaliser les requêtes d’authentification
et comment obtenir le chemin de connexion à partir des options d’authentification.

Personnaliser le processus de connexion


Gérez les paramètres supplémentaires d’une requête de connexion avec les méthodes
suivantes une ou plusieurs fois sur une nouvelle instance de InteractiveRequestOptions :

TryAddAdditionalParameter
TryRemoveAdditionalParameter
TryGetAdditionalParameter

Dans l’exemple de composant LoginDisplay suivant, des paramètres supplémentaires


sont ajoutés à la requête de connexion :

prompt est défini sur login : force l’utilisateur à entrer ses informations

d’identification sur cette requête, ce qui annule l’authentification unique.


loginHint est défini sur peter@contoso.com : pré-remplit le champ nom

d’utilisateur/adresse e-mail de la page de connexion pour que l’utilisateur soit


peter@contoso.com . Les applications utilisent souvent ce paramètre lors de la

réauthentification, après avoir déjà extrait le nom d’utilisateur d’une connexion


précédente à l’aide de la revendication preferred_username .

Shared/LoginDisplay.razor :

C#

@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation

<AuthorizeView>
<Authorized>
Hello, @context.User.Identity?.Name!
<button @onclick="BeginLogOut">Log out</button>
</Authorized>
<NotAuthorized>
<button @onclick="BeginLogIn">Log in</button>
</NotAuthorized>
</AuthorizeView>

@code{
public void BeginLogOut()
{
Navigation.NavigateToLogout("authentication/logout");
}

public void BeginLogIn()


{
InteractiveRequestOptions requestOptions =
new()
{
Interaction = InteractionType.SignIn,
ReturnUrl = Navigation.Uri,
};

requestOptions.TryAddAdditionalParameter("prompt", "login");
requestOptions.TryAddAdditionalParameter("loginHint",
"peter@contoso.com");

Navigation.NavigateToLogin("authentication/login", requestOptions);
}
}

Pour plus d'informations, reportez-vous aux ressources suivantes :

InteractiveRequestOptions
Liste de paramètres de requête contextuelle

Personnaliser les options avant d’obtenir un jeton de


façon interactive
Si un AccessTokenNotAvailableException se produit, gérez des paramètres
supplémentaires pour une nouvelle requête de jeton d’accès du fournisseur d’identité
avec les méthodes suivantes une ou plusieurs fois sur une nouvelle instance de
InteractiveRequestOptions :

TryAddAdditionalParameter
TryRemoveAdditionalParameter
TryGetAdditionalParameter

Dans l’exemple suivant qui obtient des données JSON via l’API web, des paramètres
supplémentaires sont ajoutés à la requête de redirection si un jeton d’accès n’est pas
disponible (AccessTokenNotAvailableException est levée) :

prompt est défini sur login : force l’utilisateur à entrer ses informations

d’identification sur cette requête, ce qui annule l’authentification unique.


loginHint est défini sur peter@contoso.com : pré-remplit le champ nom

d’utilisateur/adresse e-mail de la page de connexion pour que l’utilisateur soit


peter@contoso.com . Les applications utilisent souvent ce paramètre lors de la

réauthentification, après avoir déjà extrait le nom d’utilisateur d’une connexion


précédente à l’aide de la revendication preferred_username .

C#

try
{
var examples = await Http.GetFromJsonAsync<ExampleType[]>
("ExampleAPIMethod");

...
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect(requestOptions => {
requestOptions.TryAddAdditionalParameter("prompt", "login");
requestOptions.TryAddAdditionalParameter("loginHint",
"peter@contoso.com");
});
}

L’exemple précédent part du principe que :

Présence d’une instruction @using / using pour l’API dans l’espace de noms
Microsoft.AspNetCore.Components.WebAssembly.Authentication.
HttpClient injecté en tant que Http .

Pour plus d'informations, reportez-vous aux ressources suivantes :

InteractiveRequestOptions
Liste de paramètres de requête contextuelle
Personnaliser les options lors de l’utilisation d’un
IAccessTokenProvider

Si l’obtention d’un jeton échoue lors de l’utilisation d’un IAccessTokenProvider, gérez


des paramètres supplémentaires pour une nouvelle requête de jeton d’accès du
fournisseur d’identité avec les méthodes suivantes une ou plusieurs fois sur une
nouvelle instance de InteractiveRequestOptions :

TryAddAdditionalParameter
TryRemoveAdditionalParameter
TryGetAdditionalParameter

Dans l’exemple suivant qui tente d’obtenir un jeton d’accès pour l’utilisateur, des
paramètres supplémentaires sont ajoutés à la requête de connexion si la tentative
d’obtention d’un jeton échoue quand TryGetToken est appelé :

prompt est défini sur login : force l’utilisateur à entrer ses informations

d’identification sur cette requête, ce qui annule l’authentification unique.


loginHint est défini sur peter@contoso.com : pré-remplit le champ nom

d’utilisateur/adresse e-mail de la page de connexion pour que l’utilisateur soit


peter@contoso.com . Les applications utilisent souvent ce paramètre lors de la

réauthentification, après avoir déjà extrait le nom d’utilisateur d’une connexion


précédente à l’aide de la revendication preferred_username .

C#

var tokenResult = await TokenProvider.RequestAccessToken(


new AccessTokenRequestOptions
{
Scopes = new[] { ... }
});

if (!tokenResult.TryGetToken(out var token))


{
tokenResult.InteractionOptions.TryAddAdditionalParameter("prompt",
"login");
tokenResult.InteractionOptions.TryAddAdditionalParameter("loginHint",
"peter@contoso.com");

Navigation.NavigateToLogin(accessTokenResult.InteractiveRequestUrl,
accessTokenResult.InteractionOptions);
}

L’exemple précédent part du principe que :


Présence d’une instruction @using / using pour l’API dans l’espace de noms
Microsoft.AspNetCore.Components.WebAssembly.Authentication.
IAccessTokenProvider injecté en tant que TokenProvider .

Pour plus d'informations, reportez-vous aux ressources suivantes :

InteractiveRequestOptions
Liste de paramètres de requête contextuelle

Se déconnecter avec une URL de retour personnalisée


L’exemple suivant déconnecte l’utilisateur et le retourne au point de terminaison
/goodbye :

C#

Navigation.NavigateToLogout("authentication/logout", "goodbye");

Obtenir le chemin de connexion à partir des options


d’authentification
Obtenez le chemin de connexion configuré à partir de RemoteAuthenticationOptions :

C#

var loginPath =

RemoteAuthOptions.Get(Options.DefaultName).AuthenticationPaths.LogInPath;

L’exemple précédent part du principe que :

Présence d’une instruction @using / using pour l’API dans les espaces de noms
suivants :
Microsoft.AspNetCore.Components.WebAssembly.Authentication
Microsoft.Extensions.Options
IOptionsSnapshot<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>
> injecté en tant que RemoteAuthOptions .

Classe AuthorizationMessageHandler personnalisée


L’aide de cette section est recommandée pour les applications clientes qui effectuent des
requêtes sortantes vers des URI qui ne se trouvent pas dans l’URI de base de l’application.
Dans l’exemple suivant, une classe personnalisée étend AuthorizationMessageHandler
pour une utilisation en tant que DelegatingHandler pour un HttpClient.
ConfigureHandler configure ce gestionnaire pour autoriser les requêtes HTTP sortantes
à l’aide d’un jeton d’accès. Le jeton d’accès est attaché uniquement si au moins l’une des
URL autorisées est une base de l’URI de requête (HttpRequestMessage.RequestUri).

C#

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler


{
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigation)
: base(provider, navigation)
{
ConfigureHandler(
authorizedUrls: new[] { "https://www.example.com/base" },
scopes: new[] { "example.read", "example.write" });
}
}

Dans le code précédent, les étendues example.read et example.write sont des exemples
génériques qui ne sont pas destinés à refléter des étendues valides pour un fournisseur
particulier.

Dans le fichier Program , CustomAuthorizationMessageHandler est inscrit en tant que


service temporaire et est configuré en tant que DelegatingHandler pour les instances
HttpResponseMessage sortantes effectuées par un HttpClient nommé.

Dans l’exemple suivant, HttpClientFactoryServiceCollectionExtensions.AddHttpClient est


une extension dans Microsoft.Extensions.Http. Ajoutez le package à une application qui
ne le référence pas déjà.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

C#
builder.Services.AddTransient<CustomAuthorizationMessageHandler>();

builder.Services.AddHttpClient("WebAPI",
client => client.BaseAddress = new
Uri("https://www.example.com/base"))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

7 Notes

Dans l’exemple précédent, le


CustomAuthorizationMessageHandler DelegatingHandler est inscrit en tant que

service temporaire pour AddHttpMessageHandler. L’inscription temporaire est


recommandée pour IHttpClientFactory, qui gère ses propres étendues de DI. Pour
plus d'informations, reportez-vous aux ressources suivantes :

Classes de composants de base de l’utilitaire pour gérer une étendue de DI


Détecter les éléments jetables temporaires côté client

Le HttpClient configuré est utilisé pour effectuer des requêtes autorisées à l’aide du
modèle try-catch. Lorsque le client est créé avec CreateClient (package
Microsoft.Extensions.Http ), le HttpClient consiste en des instances fournies qui
incluent des jetons d’accès lors de l’envoi de requêtes à l’API du serveur. Si l’URI de
requête est un URI relatif, comme dans l’exemple suivant ( ExampleAPIMethod ), il est
combiné avec le BaseAddress lorsque l’application cliente effectue la demande :

razor

@inject IHttpClientFactory ClientFactory

...

@code {
protected override async Task OnInitializedAsync()
{
try
{
var client = ClientFactory.CreateClient("WebAPI");

var examples =
await client.GetFromJsonAsync<ExampleType[]>
("ExampleAPIMethod");

...
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
}

Configurer AuthorizationMessageHandler
AuthorizationMessageHandler peut être configuré avec des URL autorisées, des
étendues et une URL de retour à l’aide de la méthode ConfigureHandler.
ConfigureHandler configure le gestionnaire pour autoriser les requêtes HTTP sortantes à
l’aide d’un jeton d’accès. Le jeton d’accès est attaché uniquement si au moins l’une des
URL autorisées est une base de l’URI de requête (HttpRequestMessage.RequestUri). Si
l’URI de requête est un URI relatif, il est combiné avec le BaseAddress.

Dans l’exemple suivant, AuthorizationMessageHandler configure un HttpClient dans le


fichier Program :

C#

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddScoped(sp => new HttpClient(


sp.GetRequiredService<AuthorizationMessageHandler>()
.ConfigureHandler(
authorizedUrls: new[] { "https://www.example.com/base" },
scopes: new[] { "example.read", "example.write" }))
{
BaseAddress = new Uri("https://www.example.com/base")
});

Dans le code précédent, les étendues example.read et example.write sont des exemples
génériques qui ne sont pas destinés à refléter des étendues valides pour un fournisseur
particulier.

HttpClient typé
Un client typé peut être défini pour gérer tous les problèmes d’acquisition HTTP et de
jeton au sein d’une seule classe.

WeatherForecastClient.cs :

C#
using System.Net.Http.Json;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using static {ASSEMBLY NAME}.Data;

public class WeatherForecastClient


{
private readonly HttpClient http;
private WeatherForecast[]? forecasts;

public WeatherForecastClient(HttpClient http)


{
this.http = http;
}

public async Task<WeatherForecast[]> GetForecastAsync()


{
try
{
forecasts = await http.GetFromJsonAsync<WeatherForecast[]>(
"WeatherForecast");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}

return forecasts ?? Array.Empty<WeatherForecast>();


}
}

Dans l’exemple précédent, le type WeatherForecast est une classe statique qui contient
les données de prévisions météorologiques. L’espace réservé {ASSEMBLY NAME} est le
nom de l’assembly de l’application (par exemple, using static BlazorSample.Data; ).

Dans l’exemple suivant, HttpClientFactoryServiceCollectionExtensions.AddHttpClient est


une extension dans Microsoft.Extensions.Http. Ajoutez le package à une application qui
ne le référence pas déjà.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Dans le fichier Program :


C#

using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddHttpClient<WeatherForecastClient>(
client => client.BaseAddress = new
Uri("https://www.example.com/base"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

Dans un composant qui extrait les données météorologiques :

razor

@inject WeatherForecastClient Client

...

protected override async Task OnInitializedAsync()


{
forecasts = await Client.GetForecastAsync();
}

Configurer le gestionnaire HttpClient


Le gestionnaire peut être davantage configuré avec ConfigureHandler pour les requêtes
HTTP sortantes.

Dans l’exemple suivant, HttpClientFactoryServiceCollectionExtensions.AddHttpClient est


une extension dans Microsoft.Extensions.Http. Ajoutez le package à une application qui
ne le référence pas déjà.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Dans le fichier Program :

C#
builder.Services.AddHttpClient<WeatherForecastClient>(
client => client.BaseAddress = new
Uri("https://www.example.com/base"))
.AddHttpMessageHandler(sp =>
sp.GetRequiredService<AuthorizationMessageHandler>()
.ConfigureHandler(
authorizedUrls: new [] { "https://www.example.com/base" },
scopes: new[] { "example.read", "example.write" }));

Dans le code précédent, les étendues example.read et example.write sont des exemples
génériques qui ne sont pas destinés à refléter des étendues valides pour un fournisseur
particulier.

Requêtes d’API web non authentifiées ou non


autorisées dans une application avec un client
sécurisé par défaut
Une application qui utilise normalement une valeur par défaut HttpClient sécurisée peut
également effectuer des requêtes d’API web non authentifiées ou non autorisées en
configurant un HttpClient nommé.

Dans l’exemple suivant, HttpClientFactoryServiceCollectionExtensions.AddHttpClient est


une extension dans Microsoft.Extensions.Http. Ajoutez le package à une application qui
ne le référence pas déjà.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Dans le fichier Program :

C#

builder.Services.AddHttpClient("WebAPI.NoAuthenticationClient",
client => client.BaseAddress = new Uri("https://www.example.com/base"));

L’inscription précédente s’ajoute à l’inscription par défaut HttpClient sécurisée existante.


Un composant crée le HttpClient à partir du IHttpClientFactory (package
Microsoft.Extensions.Http ) pour effectuer des requêtes non authentifiées ou non
autorisées :

razor

@inject IHttpClientFactory ClientFactory

...

@code {
protected override async Task OnInitializedAsync()
{
var client =
ClientFactory.CreateClient("WebAPI.NoAuthenticationClient");

var examples = await client.GetFromJsonAsync<ExampleType[]>(


"ExampleNoAuthentication");

...
}
}

7 Notes

Le contrôleur dans l’API de serveur, ExampleNoAuthenticationController pour


l’exemple précédent, n’est pas marqué avec l’attribut [Authorize].

La décision d’utiliser un client sécurisé ou un client non sécurisé comme instance


HttpClient par défaut incombe au développeur. Une façon de prendre cette décision
consiste à prendre en compte le nombre de points de terminaison authentifiés ou non
authentifiés que l’application contacte. Si la majorité des requêtes de l’application sont
destinées à sécuriser les points de terminaison d’API, utilisez l’instance HttpClient
authentifiée comme valeur par défaut. Sinon, inscrivez l’instance HttpClient non
authentifiée comme valeur par défaut.

Une autre approche pour utiliser le IHttpClientFactory consiste à créer un client typé
pour un accès non authentifié aux points de terminaison anonymes.

Requête de jetons d’accès supplémentaires


Les jetons d’accès peuvent être obtenus manuellement en
appelant IAccessTokenProvider.RequestAccessToken. Dans l’exemple suivant, une
étendue supplémentaire est requise par une application pour la valeur par
défaut HttpClient. L’exemple de bibliothèque d’authentification Microsoft (MSAL)
configure l’étendue avec MsalProviderOptions :

Dans le fichier Program :

C#

builder.Services.AddMsalAuthentication(options =>
{
...

options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE
1}");
options.ProviderOptions.AdditionalScopesToConsent.Add("{CUSTOM SCOPE
2}");
}

Les espaces réservés {CUSTOM SCOPE 1} et {CUSTOM SCOPE 2} dans l’exemple précédent
sont des étendues personnalisées.

La méthode IAccessTokenProvider.RequestAccessToken fournit une surcharge qui


permet à une application d’approvisionner un jeton d’accès avec un ensemble donné
d’étendues.

Dans un composant Razor :

razor

@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider

...

var tokenResult = await TokenProvider.RequestAccessToken(


new AccessTokenRequestOptions
{
Scopes = new[] { "{CUSTOM SCOPE 1}", "{CUSTOM SCOPE 2}" }
});

if (tokenResult.TryGetToken(out var token))


{
...
}

Les espaces réservés {CUSTOM SCOPE 1} et {CUSTOM SCOPE 2} dans l’exemple précédent
sont des étendues personnalisées.

AccessTokenResult.TryGetToken retourne :
true avec le token pour utilisation.
false si le jeton n’est pas récupéré.

CORS (Cross Origin Resource Sharing)


Lors de l’envoi d’informations d’identification (cookies/en-têtes d’autorisation) sur des
requêtes CORS, l’en-tête Authorization doit être autorisé par la stratégie CORS.

La stratégie suivante inclut la configuration pour :

Origines de la requête ( http://localhost:5000 , https://localhost:5001 ).


N’importe quelle méthode (verbe).
En-têtes Content-Type et Authorization . Pour autoriser un en-tête personnalisé
(par exemple, x-custom-header ), répertoriez l’en-tête lors de l’appel de
WithHeaders.
Informations d’identification définies par le code JavaScript côté client (propriété
credentials définie sur include ).

C#

app.UseCors(policy =>
policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
.AllowAnyMethod()
.WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization,
"x-custom-header")
.AllowCredentials());

Pour plus d’informations, consultez Activer les demandes cross-origin (CORS) dans
ASP.NET Core et le composant testeur de requêtes HTTP de l’exemple d’application
( Components/HTTPRequestTester.razor ).

Gérer les erreurs de requête de jeton


Lorsqu’une application monopage (SPA) authentifie un utilisateur à l’aide d’OpenID
Connect (OIDC), l’état d’authentification est conservé localement au sein de la SPA et
dans le fournisseur (IP) Identity sous la forme d’une session cookie définie à la suite de
la fourniture par l’utilisateur de ses informations d’identification.

Les jetons émis par l’IP pour l’utilisateur sont généralement valides pendant de courtes
périodes, environ une heure normalement, de sorte que l’application cliente doit
récupérer régulièrement de nouveaux jetons. Dans le cas contraire, l’utilisateur serait
déconnecté après l’expiration des jetons accordés. Dans la plupart des cas, les clients
OIDC peuvent approvisionner de nouveaux jetons sans exiger que l’utilisateur
s’authentifie à nouveau grâce à l’état d’authentification ou à la « session » qui est
conservée dans l’IP.

Dans certains cas, le client ne peut pas obtenir de jeton sans interaction de l’utilisateur,
par exemple, lorsque, pour une raison quelconque, l’utilisateur se déconnecte
explicitement de l’IP. Ce scénario se produit si un utilisateur visite
https://login.microsoftonline.com et se déconnecte. Dans ces scénarios, l’application

ne sait pas immédiatement que l’utilisateur s’est déconnecté. Tout jeton que le client
détient peut ne plus être valide. En outre, le client ne peut pas approvisionner un
nouveau jeton sans interaction de l’utilisateur après l’expiration du jeton actuel.

Ces scénarios ne sont pas spécifiques à l’authentification basée sur des jetons. Ils font
partie de la nature des SPA. Une SPA utilisant des cookies ne parvient également pas à
appeler une API de serveur si l’authentification cookie est supprimée.

Lorsqu’une application effectue des appels d’API auprès de ressources protégées, vous
devez connaître les éléments suivants :

Pour approvisionner un nouveau jeton d’accès pour appeler l’API, l’utilisateur peut
être amené à s’authentifier à nouveau.
Même si le client a un jeton qui semble être valide, l’appel au serveur peut
échouer, car le jeton a été révoqué par l’utilisateur.

Lorsque l’application demande un jeton, il existe deux résultats possibles :

La requête réussit et l’application a un jeton valide.


La requête échoue et l’application doit authentifier à nouveau l’utilisateur pour
obtenir un nouveau jeton.

En cas d’échec d’une requête de jeton, vous devez décider si vous souhaitez enregistrer
un état actuel avant d’effectuer une redirection. Plusieurs approches existent pour
enregistrer l’état avec des niveaux de complexité croissants :

Enregistrez l’état actuel de la page dans le stockage de session. Pendant la


méthode de cycle de vie OnInitializedAsync (OnInitializedAsync), contrôlez si l’état
peut être restauré avant de continuer.
Ajoutez un paramètre de chaîne de requête et utilisez-le comme un moyen de
signaler à l’application qu’elle doit réhydrater l’état précédemment enregistré.
Ajoutez un paramètre de chaîne de requête avec un identificateur unique pour
enregistrer des données dans le stockage de session sans risquer de collisions avec
d’autres éléments.
Enregistrer l’état de l’application avant une
opération d’authentification avec le stockage
de session
L’exemple suivant montre comment vous :

Conservez l’état avant de rediriger vers la page de connexion.


Récupérez l’état précédent après l’authentification à l’aide d’un paramètre de
chaîne de requête.

razor

...
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
@inject IJSRuntime JS
@inject NavigationManager Navigation

<EditForm Model="User" @onsubmit="OnSaveAsync">


<label>User
<InputText @bind-Value="User.Name" />
</label>
<label>Last name
<InputText @bind-Value="User.LastName" />
</label>
</EditForm>

@code {
public class Profile
{
public string? Name { get; set; }
public string? LastName { get; set; }
}

public Profile User { get; set; } = new Profile();

protected override async Task OnInitializedAsync()


{
var currentQuery = new Uri(Navigation.Uri).Query;

if (currentQuery.Contains("state=resumeSavingProfile"))
{
User = await JS.InvokeAsync<Profile>("sessionStorage.getItem",
"resumeSavingProfile");
}
}

public async Task OnSaveAsync()


{
var http = new HttpClient();
http.BaseAddress = new Uri(Navigation.BaseUri);

var resumeUri = Navigation.Uri + $"?state=resumeSavingProfile";

var tokenResult = await TokenProvider.RequestAccessToken(


new AccessTokenRequestOptions
{
ReturnUrl = resumeUri
});

if (tokenResult.TryGetToken(out var token))


{
http.DefaultRequestHeaders.Add("Authorization",
$"Bearer {token.Value}");
await http.PostAsJsonAsync("Save", User);
}
else
{
await JS.InvokeVoidAsync("sessionStorage.setItem",
"resumeSavingProfile", User);
Navigation.NavigateTo(tokenResult.InteractiveRequestUrl);
}
}
}

Enregistrer l’état de l’application avant une


opération d’authentification avec le stockage
de session et un conteneur d’état
Lors d’une opération d’authentification, il existe des cas dans lesquels vous souhaitez
enregistrer l’état de l’application avant que le navigateur ne soit redirigé vers l’IP.
Cela peut être le cas lorsque vous utilisez un conteneur d’état et que vous souhaitez
restaurer l’état après la réussite de l’authentification. Vous pouvez utiliser un objet d’état
d’authentification personnalisé pour conserver un état spécifique à l’application ou une
référence à celui-ci et restaurer cet état une fois l’opération d’authentification terminée.
L'exemple suivant illustre l’approche.

Une classe de conteneur d’état est créée dans l’application avec des propriétés pour
conserver les valeurs d’état de l’application. Dans l’exemple suivant, le conteneur est
utilisé pour conserver la valeur de compteur du composant ( Counter.razor ) du modèle
de projet Blazor Counter par défaut. Les méthodes de sérialisation et de désérialisation
du conteneur sont basées sur System.Text.Json.

C#
using System.Text.Json;

public class StateContainer


{
public int CounterValue { get; set; }

public string GetStateForLocalStorage()


{
return JsonSerializer.Serialize(this);
}

public void SetStateFromLocalStorage(string locallyStoredState)


{
var deserializedState =
JsonSerializer.Deserialize<StateContainer>(locallyStoredState);

CounterValue = deserializedState.CounterValue;
}
}

Le composant Counter utilise le conteneur d’état pour conserver la valeur currentCount


en dehors du composant :

razor

@page "/counter"
@inject StateContainer State

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

protected override void OnInitialized()


{
if (State.CounterValue > 0)
{
currentCount = State.CounterValue;
}
}

private void IncrementCount()


{
currentCount++;
State.CounterValue = currentCount;
}
}
Créez un ApplicationAuthenticationState à partir de RemoteAuthenticationState.
Fournissez une propriété Id , qui sert d’identificateur pour l’état stocké localement.

ApplicationAuthenticationState.cs :

C#

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class ApplicationAuthenticationState : RemoteAuthenticationState


{
public string? Id { get; set; }
}

Le composant Authentication ( Authentication.razor ) enregistre et restaure l’état de


l’application à l’aide du stockage de session local avec les méthodes de sérialisation et
de désérialisation StateContainer , GetStateForLocalStorage et
SetStateFromLocalStorage :

razor

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IJSRuntime JS
@inject StateContainer State

<RemoteAuthenticatorViewCore Action="@Action"

TAuthenticationState="ApplicationAuthenticationState"
AuthenticationState="AuthenticationState"
OnLogInSucceeded="RestoreState"
OnLogOutSucceeded="RestoreState" />

@code {
[Parameter]
public string? Action { get; set; }

public ApplicationAuthenticationState AuthenticationState { get; set; }


=
new ApplicationAuthenticationState();

protected override async Task OnInitializedAsync()


{
if
(RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn,
Action) ||

RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
Action))
{
AuthenticationState.Id = Guid.NewGuid().ToString();

await JS.InvokeVoidAsync("sessionStorage.setItem",
AuthenticationState.Id, State.GetStateForLocalStorage());
}
}

private async Task RestoreState(ApplicationAuthenticationState state)


{
if (state.Id != null)
{
var locallyStoredState = await JS.InvokeAsync<string>(
"sessionStorage.getItem", state.Id);

if (locallyStoredState != null)
{
State.SetStateFromLocalStorage(locallyStoredState);
await JS.InvokeVoidAsync("sessionStorage.removeItem",
state.Id);
}
}
}
}

Cet exemple utilise l’ID Microsoft Entra (ME-ID) pour l’authentification. Dans le fichier
Program :

Le ApplicationAuthenticationState est configuré comme type


RemoteAuthenticationState Bibliothèque d’authentification Microsoft (MSAL).

Le conteneur d’état est inscrit dans le conteneur de service.

C#

builder.Services.AddMsalAuthentication<ApplicationAuthenticationState>
(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
});

builder.Services.AddSingleton<StateContainer>();

Personnaliser les itinéraires d’application


Par défaut, la bibliothèque
Microsoft.AspNetCore.Components.WebAssembly.Authentication utilise les itinéraires
indiqués dans le tableau suivant pour représenter différents états d’authentification.
ノ Expand table

Route Objectif

authentication/login Déclenche une opération de connexion.

authentication/login- Gère le résultat de toute opération de connexion.


callback

authentication/login-failed Affiche les messages d’erreur lorsque l’opération de connexion


échoue pour une raison quelconque.

authentication/logout Déclenche une opération de déconnexion.

authentication/logout- Gère le résultat d’une opération de déconnexion.


callback

authentication/logout- Affiche les messages d’erreur lorsque l’opération de connexion


failed échoue pour une raison quelconque.

authentication/logged-out Indique que l’utilisateur a réussi à se déconnecter.

authentication/profile Déclenche une opération de modification du profil utilisateur.

authentication/register Déclenche une opération d’inscription d’un nouvel utilisateur.

Les itinéraires indiqués dans le tableau précédent sont configurables


via RemoteAuthenticationOptions<TRemoteAuthenticationProviderOptions>.Authentica
tionPaths. Lorsque vous définissez des options pour fournir des itinéraires personnalisés,
confirmez que l’application dispose d’un itinéraire qui gère chaque chemin d’accès.

Dans l’exemple suivant, tous les chemins d’accès sont précédés de /security .

Composant Authentication ( Authentication.razor ) :

razor

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="@Action" />

@code{
[Parameter]
public string? Action { get; set; }
}

Dans le fichier Program :


C#

builder.Services.AddApiAuthorization(options => {
options.AuthenticationPaths.LogInPath = "security/login";
options.AuthenticationPaths.LogInCallbackPath = "security/login-
callback";
options.AuthenticationPaths.LogInFailedPath = "security/login-failed";
options.AuthenticationPaths.LogOutPath = "security/logout";
options.AuthenticationPaths.LogOutCallbackPath = "security/logout-
callback";
options.AuthenticationPaths.LogOutFailedPath = "security/logout-failed";
options.AuthenticationPaths.LogOutSucceededPath = "security/logged-out";
options.AuthenticationPaths.ProfilePath = "security/profile";
options.AuthenticationPaths.RegisterPath = "security/register";
});

Si l’exigence appelle des chemins d’accès complètement différents, définissez les


itinéraires comme décrit précédemment et affichez le RemoteAuthenticatorView avec un
paramètre d’action explicite :

razor

@page "/register"

<RemoteAuthenticatorView Action="@RemoteAuthenticationActions.Register" />

Vous êtes autorisé à diviser l’interface utilisateur en différentes pages si vous choisissez
de le faire.

Personnaliser l’interface utilisateur


d’authentification
RemoteAuthenticatorView inclut un ensemble par défaut de fragments d’interface
utilisateur pour chaque état d’authentification. Chaque état peut être personnalisé en
transmettant un RenderFragment personnalisé. Pour personnaliser le texte affiché
pendant le processus de connexion initial, vous pouvez modifier
le RemoteAuthenticatorView comme suit.

Composant Authentication ( Authentication.razor ) :

razor

@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action">
<LoggingIn>
You are about to be redirected to https://login.microsoftonline.com.
</LoggingIn>
</RemoteAuthenticatorView>

@code{
[Parameter]
public string? Action { get; set; }
}

Le RemoteAuthenticatorView a un fragment qui peut être utilisé par itinéraire


d’authentification indiqué dans le tableau suivant.

ノ Expand table

Route Fragment

authentication/login <LoggingIn>

authentication/login-callback <CompletingLoggingIn>

authentication/login-failed <LogInFailed>

authentication/logout <LogOut>

authentication/logout-callback <CompletingLogOut>

authentication/logout-failed <LogOutFailed>

authentication/logged-out <LogOutSucceeded>

authentication/profile <UserProfile>

authentication/register <Registering>

Personnaliser l’utilisateur
Les utilisateurs liés à l’application peuvent être personnalisés.

Personnaliser l’utilisateur avec une revendication de


charge utile
Dans l’exemple suivant, les utilisateurs authentifiés de l’application reçoivent une
revendication amr pour chacune des méthodes d’authentification de l’utilisateur. La
revendication amr identifie la façon dont l’objet du jeton a été authentifié dans les
revendications de charge utile de la plateforme Identity Microsoft v1.0. L’exemple utilise
une classe de compte d’utilisateur personnalisée basée sur RemoteUserAccount.

Créez une classe qui étend la classe RemoteUserAccount. L’exemple suivant définit la
propriété AuthenticationMethod sur le tableau de valeurs de propriété amr JSON de
l’utilisateur. AuthenticationMethod est renseigné automatiquement par l’infrastructure
lorsque l’utilisateur est authentifié.

C#

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

public class CustomUserAccount : RemoteUserAccount


{
[JsonPropertyName("amr")]
public string[]? AuthenticationMethod { get; set; }
}

Créez une fabrique qui étend AccountClaimsPrincipalFactory<TAccount> pour créer des


revendications à partir des méthodes d’authentification de l’utilisateur stockées dans
CustomUserAccount.AuthenticationMethod :

C#

using System.Security.Claims;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomAccountFactory


: AccountClaimsPrincipalFactory<CustomUserAccount>
{
public CustomAccountFactory(NavigationManager navigation,
IAccessTokenProviderAccessor accessor) : base(accessor)
{
}

public override async ValueTask<ClaimsPrincipal> CreateUserAsync(


CustomUserAccount account, RemoteAuthenticationUserOptions options)
{
var initialUser = await base.CreateUserAsync(account, options);

if (initialUser.Identity != null &&


initialUser.Identity.IsAuthenticated)
{
var userIdentity = (ClaimsIdentity)initialUser.Identity;

if (account.AuthenticationMethod is not null)


{
foreach (var value in account.AuthenticationMethod)
{
userIdentity.AddClaim(new Claim("amr", value));
}
}
}

return initialUser;
}
}

Inscrivez le CustomAccountFactory pour le fournisseur d’authentification en cours


d’utilisation. L’une des inscriptions suivantes est valide :

AddOidcAuthentication :

C#

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddOidcAuthentication<RemoteAuthenticationState,
CustomUserAccount>(options =>
{
...
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState,
CustomUserAccount, CustomAccountFactory>();

AddMsalAuthentication :

C#

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
CustomUserAccount>(options =>
{
...
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState,
CustomUserAccount, CustomAccountFactory>();

AddApiAuthorization :

C#
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

...

builder.Services.AddApiAuthorization<RemoteAuthenticationState,
CustomUserAccount>(options =>
{
...
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState,
CustomUserAccount, CustomAccountFactory>();

Groupes et rôles de sécurité ME-ID avec une classe de


compte d’utilisateur personnalisée
Pour obtenir un autre exemple qui fonctionne avec des groupes de sécurité ME-ID et
des rôles d’administrateur ME-ID, ainsi qu’une classe de compte d’utilisateur
personnalisée, consultez ASP.NET Core Blazor WebAssembly avec les groupes et rôles ID
Microsoft Entra.

Authentifier des utilisateurs pour appeler uniquement des


API tierces protégées
Authentifiez l’utilisateur avec un flux OAuth côté client par rapport au fournisseur d’API
tiers :

C#

builder.services.AddOidcAuthentication(options => { ... });

Dans ce scénario :

Le serveur hébergeant l’application ne joue pas de rôle.


Les API sur le serveur ne peuvent pas être protégées.
L’application peut uniquement appeler des API tierces protégées.

Authentifier des utilisateurs avec un fournisseur tiers et


appeler des API protégées sur le serveur hôte et le tiers
Configurez Identity avec un fournisseur de connexion tiers. Obtenez les jetons requis
pour l’accès à l’API tierce et enregistrez-les.
Lorsqu’un utilisateur se connecte, Identity collecte les jetons d’accès et d’actualisation
dans le cadre du processus d’authentification. À ce stade, il existe deux approches
disponibles pour effectuer des appels d’API à des API tierces.

Utiliser un jeton d’accès au serveur pour récupérer le jeton d’accès


tiers

Utilisez le jeton d’accès généré sur le serveur pour récupérer le jeton d’accès tiers à
partir d’un point de terminaison d’API serveur. À partir de là, utilisez le jeton d’accès
tiers pour appeler des ressources d’API tierces directement à partir du Identity sur le
client.

Nous déconseillons cette approche. Cette approche nécessite de traiter le jeton d’accès
tiers comme s’il avait été généré pour un client public. En termes OAuth, l’application
publique n’a pas de secret client, car elle ne peut pas être approuvée pour stocker les
secrets en toute sécurité, et le jeton d’accès est produit pour un client confidentiel.
Un client confidentiel est un client qui a une clé secrète client et est supposé être en
mesure de stocker des secrets en toute sécurité.

Le jeton d’accès tiers peut se voir accorder des étendues supplémentaires pour
effectuer des opérations sensibles en fonction du fait que le tiers a émis le jeton
pour un client plus fiable.
De même, les jetons d’actualisation ne doivent pas être émis à un client qui n’est
pas approuvé, car cela donne au client un accès illimité, sauf si d’autres restrictions
sont mises en place.

Effectuer des appels d’API à partir du client vers l’API serveur afin
d’appeler des API tierces
Effectuez un appel d’API à partir du client vers l’API du serveur. À partir du serveur,
récupérez le jeton d’accès pour la ressource d’API tierce et émettez tout appel
nécessaire.

Nous recommandons cette approche. Bien que cette approche nécessite un tronçon
réseau supplémentaire via le serveur pour appeler une API tierce, elle aboutit finalement
à une expérience plus sûre :

Le serveur peut stocker des jetons d’actualisation et s’assurer que l’application ne


perd pas l’accès aux ressources tierces.
L’application ne peut pas divulguer de jetons d’accès du serveur qui peuvent
contenir des autorisations plus sensibles.
Utiliser des points de terminaison OpenID
Connect (OIDC) v2.0
La bibliothèque d’authentification et les modèles de projet Blazor utilisent des points de
terminaison OpenID Connect (OIDC) v1.0. Pour utiliser un point de terminaison v2.0,
configurez l’option JWT Bearer JwtBearerOptions.Authority. Dans l’exemple suivant, ME-
ID est configuré pour v2.0 en ajoutant un segment v2.0 à la propriété Authority :

C#

using Microsoft.AspNetCore.Authentication.JwtBearer;

...

builder.Services.Configure<JwtBearerOptions>(
JwtBearerDefaults.AuthenticationScheme,
options =>
{
options.Authority += "/v2.0";
});

Vous pouvez également définir le paramètre dans le fichier de paramètres de


l’application ( appsettings.json ) :

JSON

{
"Local": {
"Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
...
}
}

Si l’ajout d’un segment à l’autorité n’est pas approprié pour le fournisseur OIDC de
l’application, par exemple avec des fournisseurs non ME-ID, définissez directement la
propriété Authority. Définissez la propriété dans JwtBearerOptions ou dans le fichier de
paramètres de l’application ( appsettings.json ) avec la clé Authority .

La liste des revendications dans le jeton d’ID change pour les points de terminaison v2.0.
Pour plus d’informations, consultez Pourquoi mettre à jour vers la plateforme d'identités
Microsoft (v2.0) ?.
Remplacer l’implémentation
AuthenticationService
Les sous-sections suivantes expliquent comment remplacer :

Toute implémentation JavaScript AuthenticationService .


La bibliothèque d’authentification Microsoft pour JavaScript ( MSAL.js ).

Remplacer toute implémentation JavaScript


AuthenticationService

Créez une bibliothèque JavaScript pour gérer vos détails d’authentification


personnalisée.

2 Avertissement

L’aide de cette section est un détail d’implémentation de la valeur par


défaut RemoteAuthenticationService<TRemoteAuthenticationState,TAccount,TPr
oviderOptions>. Le code TypeScript de cette section s’applique spécifiquement à
ASP.NET Core 7.0 et est susceptible d’être modifié sans préavis dans les prochaines
versions de ASP.NET Core.

TypeScript

// .NET makes calls to an AuthenticationService object in the Window.


declare global {
interface Window { AuthenticationService: AuthenticationService }
}

export interface AuthenticationService {


// Init is called to initialize the AuthenticationService.
public static init(settings: UserManagerSettings &
AuthorizeServiceSettings, logger: any) : Promise<void>;

// Gets the currently authenticated user.


public static getUser() : Promise<{[key: string] : string }>;

// Tries to get an access token silently.


public static getAccessToken(options: AccessTokenRequestOptions) :
Promise<AccessTokenResult>;

// Tries to sign in the user or get an access token interactively.


public static signIn(context: AuthenticationContext) :
Promise<AuthenticationResult>;
// Handles the sign-in process when a redirect is used.
public static async completeSignIn(url: string) :
Promise<AuthenticationResult>;

// Signs the user out.


public static signOut(context: AuthenticationContext) :
Promise<AuthenticationResult>;

// Handles the signout callback when a redirect is used.


public static async completeSignOut(url: string) :
Promise<AuthenticationResult>;
}

// The rest of these interfaces match their C# definitions.

export interface AccessTokenRequestOptions {


scopes: string[];
returnUrl: string;
}

export interface AccessTokenResult {


status: AccessTokenResultStatus;
token?: AccessToken;
}

export interface AccessToken {


value: string;
expires: Date;
grantedScopes: string[];
}

export enum AccessTokenResultStatus {


Success = 'Success',
RequiresRedirect = 'RequiresRedirect'
}

export enum AuthenticationResultStatus {


Redirect = 'Redirect',
Success = 'Success',
Failure = 'Failure',
OperationCompleted = 'OperationCompleted'
};

export interface AuthenticationResult {


status: AuthenticationResultStatus;
state?: unknown;
message?: string;
}

export interface AuthenticationContext {


state?: unknown;
interactiveRequest: InteractiveAuthenticationRequest;
}

export interface InteractiveAuthenticationRequest {


scopes?: string[];
additionalRequestParameters?: { [key: string]: any };
};

Vous pouvez importer la bibliothèque en supprimant la balise d’origine <script> et en


ajoutant une balise <script> qui charge la bibliothèque personnalisée. L’exemple
suivant illustre le remplacement de la balise par défaut <script> par une balise qui
charge une bibliothèque nommée CustomAuthenticationService.js à partir du dossier
wwwroot/js .

Dans wwwroot/index.html avant le script Blazor ( _framework/blazor.webassembly.js ) à


l’intérieur de la balise fermante </body> :

diff

- <script
src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationServic
e.js"></script>
+ <script src="js/CustomAuthenticationService.js"></script>

Pour plus d’informations, consultez AuthenticationService.tsdans le référentiel GitHub


dotnet/aspnetcore .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Remplacer la bibliothèque d’authentification Microsoft


pour JavaScript ( MSAL.js )
Si une application nécessite une version personnalisée de la bibliothèque
d’authentification Microsoft pour JavaScript (MSAL.js) , procédez comme suit :

1. Vérifiez que le système dispose du dernier kit SDK .NET de développeur ou


obtenez et installez le dernier SDK de développeur à partir de SDK .NET Core :
Programmes d’installation et fichiers binaires . La configuration des flux NuGet
internes n’est pas requise pour ce scénario.
2. Configurez le référentiel GitHub dotnet/aspnetcore pour le développement en
suivant la documentation de la page Générer ASP.NET Core à partir de la source .
Dupliquez et clonez ou téléchargez une archive ZIP du dépôt GitHub
dotnet/aspnetcore .
3. Ouvrez le fichier
src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json et

définissez la version souhaitée de @azure/msal-browser . Pour obtenir une liste des


versions publiées, visitez le site web npm @azure/msal-browser et sélectionnez
l’onglet Versions.
4. Générez le projet Authentication.Msal dans le dossier
src/Components/WebAssembly/Authentication.Msal/src avec la commande yarn
build dans un interpréteur de commandes.

5. Si l’application utilise des ressources compressées (Brotli/Gzip), compressez le


fichier Interop/dist/Release/AuthenticationService.js .
6. Copiez le fichier AuthenticationService.js et les versions compressées ( .br / .gz )
du fichier, le cas échéant, à partir du dossier Interop/dist/Release dans le dossier
de l’application
publish/wwwroot/_content/Microsoft.Authentication.WebAssembly.Msal dans les

ressources publiées de l’application.

Passer les options du fournisseur personnalisé


Définissez une classe pour passer les données à la bibliothèque JavaScript sous-jacente.

) Important

La structure de la classe doit correspondre à ce que la bibliothèque attend quand


JSON est sérialisé avec System.Text.Json.

L’exemple suivant illustre une classe ProviderOptions avec des


attributsJsonPropertyName correspondant aux attentes d’une bibliothèque de
fournisseur personnalisé hypothétique :

C#

public class ProviderOptions


{
public string? Authority { get; set; }
public string? MetadataUrl { get; set; }
[JsonPropertyName("client_id")]
public string? ClientId { get; set; }

public IList<string> DefaultScopes { get; } =


new List<string> { "openid", "profile" };

[JsonPropertyName("redirect_uri")]
public string? RedirectUri { get; set; }

[JsonPropertyName("post_logout_redirect_uri")]
public string? PostLogoutRedirectUri { get; set; }

[JsonPropertyName("response_type")]
public string? ResponseType { get; set; }

[JsonPropertyName("response_mode")]
public string? ResponseMode { get; set; }
}

Inscrivez les options du fournisseur dans le système DI et configurez les valeurs


appropriées :

C#

builder.Services.AddRemoteAuthentication<RemoteAuthenticationState,
RemoteUserAccount,
ProviderOptions>(options => {
options.Authority = "...";
options.MetadataUrl = "...";
options.ClientId = "...";
options.DefaultScopes = new List<string> { "openid", "profile",
"myApi" };
options.RedirectUri = "https://localhost:5001/authentication/login-
callback";
options.PostLogoutRedirectUri =
"https://localhost:5001/authentication/logout-callback";
options.ResponseType = "...";
options.ResponseMode = "...";
});

L’exemple précédent définit des URI de redirection avec des littéraux de chaîne
standard. Les alternatives suivantes sont disponibles :

TryCreate utilisant IWebAssemblyHostEnvironment.BaseAddress :

C#

Uri.TryCreate(
$"{builder.HostEnvironment.BaseAddress}authentication/login-
callback",
UriKind.Absolute, out var redirectUri);
options.RedirectUri = redirectUri;

Configuration du générateur d’hôte :

C#

options.RedirectUri = builder.Configuration["RedirectUri"];

wwwroot/appsettings.json :

JSON

{
"RedirectUri": "https://localhost:5001/authentication/login-callback"
}

Ressources supplémentaires
Utiliser l’API Graph avec ASP.NET Core Blazor WebAssembly
HttpClient et HttpRequestMessage avec des options de requête d’API Fetch

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Groupes Microsoft Entra (ME-ID), rôles
d’administrateur et rôles d’application
Article • 09/02/2024

Cet article explique comment configurer Blazor WebAssembly pour utiliser des groupes
et des rôles Microsoft Entra ID.

Microsoft Entra (ME-ID) fournit plusieurs approches d’autorisation qui peuvent être
combinées avec ASP.NET Core Identity:

Groupes
Sécurité
Microsoft 365
Distribution
Rôles
Rôles d’administrateur ME-ID
Rôles d'application

Les conseils de cet article s’appliquent aux scénarios de déploiement Blazor


WebAssembly ME-ID décrits dans les rubriques suivantes :

Autonome avec des comptes Microsoft


Autonome avec ME-ID

Les conseils de l’article fournissent des instructions pour les applications clients et
serveurs :

CLIENT : Applications Autonome Blazor WebAssembly.


SERVEUR : Applications API/API Web du serveur ASP.NET Core. Vous pouvez
ignorer les conseils SERVEUR tout au long de l’article pour une application Blazor
WebAssembly autonome.

Les exemples de cet article tirent parti des nouvelles fonctionnalités .NET/C#. Lors de
l’utilisation des exemples avec .NET 7 ou antérieur, des modifications mineures sont
requises. Toutefois, les exemples de texte et de code relatifs à l’interaction avec ME-ID et
Microsoft Graph sont identiques pour toutes les versions de ASP.NET Core.

Prérequis
Les conseils de cet article implémentent l’API Microsoft Graph conformément aux
instructions du Kit de développement logiciel (SDK) Graph dans Utiliser l’API Graph avec
ASP.NET Core Blazor WebAssembly. Suivez les instructions d’implémentation du Kit de
développement logiciel (SDK) Graph pour configurer l’application et la tester afin de
confirmer que l’application peut obtenir des données de l’API Graph pour un compte
d’utilisateur de test. En outre, consultez les liens croisés de l’article sur la sécurité de
l’API Graph pour passer en revue les concepts de sécurité de Microsoft Graph.

Lorsque vous effectuez des tests avec le Kit de développement logiciel (SDK) Graph
localement, nous vous recommandons d’utiliser une nouvelle session de navigateur
privée/incognito pour chaque test afin d’éviter que les cookies persistants n’interfèrent
avec les tests. Pour plus d’informations, consultez Sécuriser une application autonome
ASP.NET Core Blazor WebAssembly avec Microsoft Entra ID.

Portées
Pour autoriser les appels de l’API Microsoft Graph pour les données de profil utilisateur,
d’attribution de rôle et d’appartenance à un groupe :

Une application CLIENT est configurée avec l’étendue User.Read


( https://graph.microsoft.com/User.Read ) dans le portail Azure.
Une application SERVEUR est configurée avec l’étendue GroupMember.Read.All
( https://graph.microsoft.com/GroupMember.Read.All ) dans le portail Azure.

Les étendues précédentes sont requises en plus des étendues requises dans les
scénarios de déploiement ME-ID décrits précédemment (autonome avec les comptes
Microsoft ou autonome avec ME-ID).

Pour plus d’informations, consultez la documentation de référence sur les autorisations


Microsoft Graph.

7 Notes

Les mots « permission » et « étendue » sont utilisés indifféremment dans le portail


Azure et dans différents ensembles de documentation Microsoft et de
documentation externe. Cet article utilise le mot « étendue » pour les autorisations
attribuées à une application dans le portail Azure.

Attribut des revendications d’appartenance à


un groupe
Dans le manifeste de l’application dans le portail Azure pour les applications CLIENT et
SERVEUR, définissez groupMembershipClaimsl’attribut sur All . La valeur de All aboutit
à l’envoi par ME-ID de tous les groupes de sécurité, groupes de distribution et rôles de
l’utilisateur connecté dans la revendication des ID connus (wids) :

1. Ouvrez l’inscription du portail Azure de l’application.


2. Sélectionnez Gérer>Manifeste dans la barre latérale.
3. Recherchez l’attribut groupMembershipClaims .
4. Définissez la valeur sur All ( "groupMembershipClaims": "All" ).
5. Cliquez sur le bouton Enregistrer si vous avez apporté des modifications.

Compte d’utilisateur personnalisé


Affectez des utilisateurs à des groupes de sécurité ME-ID et des rôles d’administrateur
ME-ID dans le portail Azure.

Les exemples de cet article :

Supposons qu’un utilisateur est affecté à ME-ID rôle d’administrateur de facturation


dans le locataire ME-ID du portail Azure pour l’autorisation d’accéder aux données
d’API du serveur.
Utilisez des politiques d’autorisation pour contrôler l’accès au sein des applications
CLIENT et SERVEUR .

Dans l’application CLIENT, étendez RemoteUserAccount pour inclure les propriétés


pour :

Roles : tableau des rôles d’application ME-ID (couvert dans la section Rôles

d’application )
Wids : Rôles d’administrateur ME-ID dans revendication d’ID connues (wids)
Oid : revendication d’identificateur d’objet immuable (oid) immuable (identifie de

manière unique un utilisateur au sein et entre les locataires)

CustomUserAccount.cs :

C#

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

namespace BlazorSample;

public class CustomUserAccount : RemoteUserAccount


{
[JsonPropertyName("roles")]
public List<string>? Roles { get; set; }

[JsonPropertyName("wids")]
public List<string>? Wids { get; set; }

[JsonPropertyName("oid")]
public string? Oid { get; set; }
}

Ajoutez une référence de package à l’application CLIENT pour Microsoft.Graph .

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Ajoutez les classes et la configuration de l’utilitaire du Kit de développement logiciel


(SDK) Graph dans les instructions du Kit de développement logiciel (SDK) Graph de
l’article Utiliser l’API Graph avec ASP.NET CoreBlazor WebAssembly. Spécifiez l’étendue
User.Read du jeton d’accès comme illustré dans l’article avec son exemple de fichier
wwwroot/appsettings.json .

Ajoutez la fabrique de compte d’utilisateur personnalisée suivante à l’application


CLIENT. La fabrique d’utilisateur personnalisée est utilisée pour établir :

Revendications de rôle d’application ( appRole ) (couvertes dans la section Rôles


d’application).
Revendications de rôle d’administrateur ME-ID ( directoryRole ).
Exemple de revendications de données de profil utilisateur pour le numéro de
téléphone mobile de l’utilisateur ( mobilePhone ) et l’emplacement du bureau
( officeLocation ).
Revendications de groupe ME-ID ( directoryGroup ).
Un ILogger ( logger ) pour plus de commodité dans le cas où vous souhaitez
enregistrer des informations ou des erreurs.

CustomAccountFactory.cs :

C#

using System.Security.Claims;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using Microsoft.Graph;

namespace BlazorSample;

public class CustomAccountFactory(IAccessTokenProviderAccessor accessor,


IServiceProvider serviceProvider,
ILogger<CustomAccountFactory> logger)
: AccountClaimsPrincipalFactory<CustomUserAccount>(accessor)
{
private readonly ILogger<CustomAccountFactory> logger = logger;
private readonly IServiceProvider serviceProvider = serviceProvider;

public override async ValueTask<ClaimsPrincipal> CreateUserAsync(


CustomUserAccount account,
RemoteAuthenticationUserOptions options)
{
var initialUser = await base.CreateUserAsync(account, options);

if (initialUser.Identity is not null &&


initialUser.Identity.IsAuthenticated)
{
var userIdentity = initialUser.Identity as ClaimsIdentity;

if (userIdentity is not null)


{
account?.Roles?.ForEach((role) =>
{
userIdentity.AddClaim(new Claim("appRole", role));
});

account?.Wids?.ForEach((wid) =>
{
userIdentity.AddClaim(new Claim("directoryRole", wid));
});

try
{
var client = ActivatorUtilities
.CreateInstance<GraphServiceClient>
(serviceProvider);
var request = client.Me.Request();
var user = await request.GetAsync();

if (user is not null)


{
userIdentity.AddClaim(new Claim("mobilephone",
user.MobilePhone ?? "(000) 000-0000"));
userIdentity.AddClaim(new Claim("officelocation",
user.OfficeLocation ?? "Not set"));
}

var requestMemberOf =
client.Users[account?.Oid].MemberOf;
var memberships = await
requestMemberOf.Request().GetAsync();

if (memberships is not null)


{
foreach (var entry in memberships)
{
if (entry.ODataType == "#microsoft.graph.group")
{
userIdentity.AddClaim(
new Claim("directoryGroup", entry.Id));
}
}
}
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
}

return initialUser;
}
}

Le code précédent n’inclut pas d’appartenances transitives. Si l’application nécessite des


revendications d’appartenance directe et transitive à un groupe, remplacez la propriété
MemberOf ( IUserMemberOfCollectionWithReferencesRequestBuilder ) par

TransitiveMemberOf ( IUserTransitiveMemberOfCollectionWithReferencesRequestBuilder ).

Le code précédent ignore les revendications d’appartenance au groupe ( groups ) qui


sont des rôles d’administrateur ME-ID (type #microsoft.graph.directoryRole ), car les
valeurs GUID retournées par la plateforme d’identités Microsoft sont des ID
d’administrateur ME-ID des ID d’entité et non ID de modèle de rôle. Les ID d’entité ne
sont pas stables entre les locataires dans la plateforme d'identités Microsoft et ne
doivent pas être utilisés pour créer des politiques d’autorisation pour les utilisateurs
dans les applications. Utilisez toujours les ID de modèle de rôle pour les rôles
d’administrateur ME-ID fournis par wids revendications.

Dans l’application CLIENT, configurez l’authentification MSAL pour utiliser la fabrique de


compte d’utilisateur personnalisée.

Vérifiez que le fichier Program utilise l’espace de noms


Microsoft.AspNetCore.Components.WebAssembly.Authentication :

C#
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

Mettez à jour l’appel AddMsalAuthentication de la manière suivante. Notez que le


RemoteUserAccount de l’infrastructure Blazor est remplacé par le CustomUserAccount de
l’application pour l’authentification MSAL et la fabrique principale des revendications de
compte :

C#

builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState,
CustomUserAccount,
CustomAccountFactory>();

Vérifiez la présence du code du Kit de développement logiciel (SDK) Graph décrit par
l’article Utiliser l’API Graph avec ASP.NET Core Blazor WebAssembly et vérifiez que la
configuration wwwroot/appsettings.json est correcte conformément aux instructions du
Kit de développement logiciel (SDK) Graph :

C#

var baseUrl = string.Join("/",


builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"],
builder.Configuration.GetSection("MicrosoftGraph")["Version"]);
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
.Get<List<string>>();

builder.Services.AddGraphClient(baseUrl, scopes);

wwwroot/appsettings.json :

JSON

{
"MicrosoftGraph": {
"BaseUrl": "https://graph.microsoft.com",
"Version: "v1.0",
"Scopes": [
"user.read"
]
}
}

Configuration de l’autorisation
Dans l'application CLIENT, créez une stratégie pour chaque rôle d'application, rôle
d'administrateur ME-ID ou groupe de sécurité dans le fichier Program . L’exemple suivant
crée une stratégie pour le rôle d’administrateur de facturation ME-ID :

C#

builder.Services.AddAuthorizationCore(options =>
{
options.AddPolicy("BillingAdministrator", policy =>
policy.RequireClaim("directoryRole",
"b0f54661-2d74-4c50-afa3-1ec803f12efe"));
});

Pour obtenir la liste complète des ID pour les rôles d’administrateur ME-ID, consultez ID
de modèle de rôle dans la documentation Entra. Pour plus d’informations sur les
politiques d’autorisation, consultez Autorisation basée sur des politiques dans ASP.NET
Core.

Dans les exemples suivants, l’application CLIENT utilise la politique précédente pour
autoriser l’utilisateur.

Le composantAuthorizeView fonctionne avec la politique :

razor

<AuthorizeView Policy="BillingAdministrator">
<Authorized>
<p>
The user is in the 'Billing Administrator' ME-ID Administrator
Role
and can see this content.
</p>
</Authorized>
<NotAuthorized>
<p>
The user is NOT in the 'Billing Administrator' role and sees
this
content.
</p>
</NotAuthorized>
</AuthorizeView>
L’accès à un composant entier peut être basé sur la politique à l’aide d’une directive
d’attribut[Authorize] (AuthorizeAttribute) :

razor

@page "/"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "BillingAdministrator")]

Si l’utilisateur n’est pas autorisé, il est redirigé vers la page de connexion ME-ID.

Une vérification de politique peut également être effectuée dans le code avec une
logique procédurale.

CheckPolicy.razor :

razor

@page "/checkpolicy"
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

<h1>Check Policy</h1>

<p>This component checks a policy in code.</p>

<button @onclick="CheckPolicy">Check 'BillingAdministrator' policy</button>

<p>Policy Message: @policyMessage</p>

@code {
private string policyMessage = "Check hasn't been made yet.";

[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }

private async Task CheckPolicy()


{
var user = (await authenticationStateTask).User;

if ((await AuthorizationService.AuthorizeAsync(user,
"BillingAdministrator")).Succeeded)
{
policyMessage = "Yes! The 'BillingAdministrator' policy is
met.";
}
else
{
policyMessage = "No! 'BillingAdministrator' policy is NOT met.";
}
}
}

Autoriser l’accès à l’API serveur/API web


Une application API SERVER peut autoriser les utilisateurs à accéder à des points de
terminaison d’API sécurisés avec stratégies d’autorisation pour les groupes de sécurité,
les rôles d’administrateur ME-ID et les rôles d’application lorsqu’un jeton d’accès
contient les revendications groups , wids et role . L'exemple suivant crée une stratégie
pour le rôle d'administrateur de facturation ME-ID dans le fichier Program à l'aide des
revendications wids (ID bien connus/ID de modèle de rôle) :

C#

builder.Services.AddAuthorization(options =>
{
options.AddPolicy("BillingAdministrator", policy =>
policy.RequireClaim("wids", "b0f54661-2d74-4c50-afa3-
1ec803f12efe"));
});

Pour obtenir la liste complète des ID pour les rôles d’administrateur ME-ID, consultez ID
de modèle de rôle dans la documentation Azure. Pour plus d’informations sur les
politiques d’autorisation, consultez Autorisation basée sur des politiques dans ASP.NET
Core.

L’accès à un contrôleur dans l’application SERVEUR peut être basé sur l’utilisation d’un
attribut[Authorize] portant le nom de la politique (documentation de l’API :
AuthorizeAttribute).

L’exemple suivant limite l’accès aux données de facturation du BillingDataController


aux Administrateurs de facturation Azure avec un nom de
politique : BillingAdministrator

C#

using Microsoft.AspNetCore.Authorization;

C#

[Authorize(Policy = "BillingAdministrator")]
[ApiController]
[Route("[controller]")]
public class BillingDataController : ControllerBase
{
...
}

Pour plus d’informations, consultez Autorisation basée sur une stratégie dans ASP.NET
Core.

Rôles d'application
Pour configurer l’application dans le portail Azure afin de fournir des revendications
d’appartenance aux rôles d’application, consultez Ajouter des rôles d’application dans
votre application et les recevoir dans le jeton dans la documentation Entra.

L’exemple suivant suppose que les applications CLIENT et SERVEUR sont configurées
avec deux rôles et que les rôles sont attribués à un utilisateur de test :

Admin

Developer

7 Notes

Lors du développement d’une paire client-serveur d’applications autonomes (une


application Blazor WebAssembly autonome et une application API serveur/API Web
ASP.NET Core), la propriété manifeste appRoles des inscriptions d’applications du
portail Azure client et serveur doit inclure les mêmes rôles configurés. Après avoir
établi les rôles dans le manifeste de l’application cliente, copiez-les dans leur
intégralité dans le manifeste de l’application serveur. Si vous ne mettez pas en
miroir le manifeste appRoles entre les inscriptions des applications client et serveur,
les revendications de rôle ne sont pas établies pour les utilisateurs authentifiés de
l’API serveur/API web, même si leur jeton d’accès contient les entrées correctes
dans les revendications role .

Bien que vous ne puissiez pas attribuer de rôles à des groupes sans compte Microsoft
Entra ID Premium, vous pouvez attribuer des rôles aux utilisateurs et recevoir une
revendication role pour les utilisateurs disposant d’un compte Azure standard. Les
conseils de cette section ne nécessitent pas de compte ME-ID Premium.

Si vous disposez d’un compte Azure de niveau Premium, Gérer>Rôles d’application


s’affiche dans la barre latérale d’inscription de l’application du portail Azure. Suivez les
instructions dans Ajouter des rôles d’application dans votre application et les recevoir
dans le jeton pour configurer les rôles de l’application.
Si vous n’avez pas de compte Azure de niveau Premium, modifiez le manifeste de
l’application dans le portail Azure. Suivez les instructions de rôles d’application :
implémentation pour établir manuellement les rôles de l’application dans l’entrée
appRoles du fichier manifeste. Enregistrez les modifications dans le fichier.

Voici un exemple d’entrée appRoles qui crée des rôles Admin et Developer . Ces
exemples de rôles sont utilisés plus loin dans l’exemple de cette section au niveau du
composant pour implémenter des restrictions d’accès :

JSON

"appRoles": [
{
"allowedMemberTypes": [
"User"
],
"description": "Administrators manage developers.",
"displayName": "Admin",
"id": "584e483a-7101-404b-9bb1-83bf9463e335",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "Admin"
},
{
"allowedMemberTypes": [
"User"
],
"description": "Developers write code.",
"displayName": "Developer",
"id": "82770d35-2a93-4182-b3f5-3d7bfe9dfe46",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "Developer"
}
],

7 Notes

Vous pouvez générer des GUID avec un programme de générateur de GUID en


ligne (résultat de recherche Google pour « générateur de guid ») .

Pour attribuer un rôle à un utilisateur (ou à un groupe si vous disposez d’un compte
Azure de niveau Premium) :

1. Accédez à applications d’entreprise dans la zone ME-ID du portail Azure.


2. Sélectionnez l’application. Sélectionnez Gérer>Utilisateurs et groupes dans la
barre latérale.
3. Cochez la case pour un ou plusieurs comptes d’utilisateur.
4. Dans le menu situé au-dessus de la liste des utilisateurs, sélectionnez Modifier
l’affectation.
5. Pour l’entrée Sélectionner un rôle, sélectionnez Aucun sélectionné.
6. Choisissez un rôle dans la liste et utilisez le bouton Sélectionner pour le
sélectionner.
7. Utilisez le bouton Attribuer en bas de l’écran pour attribuer le rôle.

Plusieurs rôles sont attribués dans le portail Azure en ajoutant un utilisateur pour
chaque attribution de rôle supplémentaire. Utilisez le bouton Ajouter un
utilisateur/groupe en haut de la liste des utilisateurs pour rajouter un utilisateur. Utilisez
les étapes précédentes pour attribuer un autre rôle à l’utilisateur. Vous pouvez répéter
ce processus autant de fois que nécessaire pour ajouter des rôles supplémentaires à un
utilisateur (ou un groupe).

Le CustomAccountFactory indiqué dans la section Compte d’utilisateur personnalisé est


configuré pour agir sur une revendication role avec une valeur de tableau JSON.
Ajoutez et inscrivez le CustomAccountFactory dans l’application CLIENT, comme indiqué
dans la section Compte d’utilisateur personnalisé. Il n’est pas nécessaire de fournir du
code pour supprimer la revendication role d’origine, car elle est automatiquement
supprimée par l’infrastructure.

Dans le fichier Program d'une application CLIENT, précisez la revendication nommée


" appRole " comme revendication de rôle pour les contrôles ClaimsPrincipal.IsInRole :

C#

builder.Services.AddMsalAuthentication(options =>
{
...

options.UserOptions.RoleClaim = "appRole";
});

7 Notes

Si vous préférez utiliser la revendication directoryRoles (AJOUTER des rôles


d’administrateur), attribuez « directoryRoles » au
RemoteAuthenticationUserOptions.RoleClaim.
Dans le fichier Program d'une application SERVEUR, spécifiez la revendication nommée
" http://schemas.microsoft.com/ws/2008/06/identity/claims/role " comme revendication
de rôle pour les contrôles ClaimsPrincipal.IsInRole :

C#

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(options =>
{
Configuration.Bind("AzureAd", options);
options.TokenValidationParameters.RoleClaimType =
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
},
options => { Configuration.Bind("AzureAd", options); });

7 Notes

Quand un seul schéma d’authentification est inscrit, il est automatiquement utilisé


en tant que schéma par défaut de l’application. Il n’est pas nécessaire d’indiquer le
schéma pour AddAuthentication ou via AuthenticationOptions. Pour plus
d’informations, consultez Vue d’ensemble de l’authentification ASP.NET Core ainsi
que l’Annonce ASP.NET Core (aspnet/Announcements #490) .

7 Notes

Si vous préférez utiliser la revendication wids (AJOUTER des rôles d’administrateur),


attribuez « wids » au TokenValidationParameters.RoleClaimType.

Une fois que vous avez effectué les étapes précédentes pour créer et attribuer des rôles
à des utilisateurs (ou des groupes si vous disposez d’un compte Azure de niveau
Premium) et que vous avez implémenté le CustomAccountFactory avec le Kit de
développement logiciel (SDK) Graph, comme expliqué plus haut dans cet article et dans
Utiliser l’API Graph avec ASP.NET Core Blazor WebAssembly, vous devez voir une
revendication appRole pour chaque rôle attribué auquel un utilisateur connecté est
affecté (ou les rôles attribués aux groupes dont il est membre). Exécutez l’application
avec un utilisateur de test pour vérifier que la ou les revendications sont présentes
comme prévu. Lorsque vous effectuez des tests avec le Kit de développement logiciel
(SDK) Graph localement, nous vous recommandons d’utiliser une nouvelle session de
navigateur privée/incognito pour chaque test afin d’éviter que les cookies persistants
n’interfèrent avec les tests. Pour plus d’informations, consultez Sécuriser une application
autonome ASP.NET Core Blazor WebAssembly avec Microsoft Entra ID.
Les approches d’autorisation des composants sont fonctionnelles à ce stade. Tous les
mécanismes d’autorisation dans les composants de l’application CLIENT peuvent utiliser
le rôle Admin pour autoriser l’utilisateur :

ComponentAuthorizeView

razor

<AuthorizeView Roles="Admin">

Directive d’attribut[Authorize] (AuthorizeAttribute)

razor

@attribute [Authorize(Roles = "Admin")]

Logique procédurale

C#

var authState = await


AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;

if (user.IsInRole("Admin")) { ... }

Plusieurs tests de rôle sont pris en charge :

Exiger que l’utilisateur soit dans le rôle Admin ou Developer avec le composant
AuthorizeView :

razor

<AuthorizeView Roles="Admin, Developer">


...
</AuthorizeView>

Exiger que l’utilisateur soit à la fois dans les rôles Admin et Developer avec le
composant AuthorizeView :

razor

<AuthorizeView Roles="Admin">
<AuthorizeView Roles="Developer" Context="innerContext">
...
</AuthorizeView>
</AuthorizeView>

Pour plus d'informations sur Context pour le AuthorizeView interne, voir ASP.NET
CoreBlazor authentification et autorisation.

Exiger que l’utilisateur soit dans le rôle Admin ou Developer avec l’attribut
[Authorize] :

razor

@attribute [Authorize(Roles = "Admin, Developer")]

Exiger que l’utilisateur soit à la fois dans les rôles Admin et Developer avec
l’attribut [Authorize] :

razor

@attribute [Authorize(Roles = "Admin")]


@attribute [Authorize(Roles = "Developer")]

Exiger que l’utilisateur soit dans le rôle Admin ou Developer avec du code
procédural :

razor

@code {
private async Task DoSomething()
{
var authState = await AuthenticationStateProvider
.GetAuthenticationStateAsync();
var user = authState.User;

if (user.IsInRole("Admin") || user.IsInRole("Developer"))
{
...
}
else
{
...
}
}
}

Exiger que l’utilisateur soit à la fois dans les rôles Admin et Developer avec du code
procédural en remplaçant le conditionnel OU (||) par un conditionnel ET (&&) dans
l’exemple précédent :

C#

if (user.IsInRole("Admin") && user.IsInRole("Developer"))

Tous les mécanismes d’autorisation dans les contrôleurs de l’application SERVEUR


peuvent utiliser le rôle Admin pour autoriser l’utilisateur :

Directive d’attribut[Authorize] (AuthorizeAttribute)

C#

[Authorize(Roles = "Admin")]

Logique procédurale

C#

if (User.IsInRole("Admin")) { ... }

Plusieurs tests de rôle sont pris en charge :

Exiger que l’utilisateur soit dans le rôle Admin ou Developer avec l’attribut
[Authorize] :

C#

[Authorize(Roles = "Admin, Developer")]

Exiger que l’utilisateur soit à la fois dans les rôles Admin et Developer avec
l’attribut [Authorize] :

C#

[Authorize(Roles = "Admin")]
[Authorize(Roles = "Developer")]

Exiger que l’utilisateur soit dans le rôle Admin ou Developer avec du code
procédural :

C#
static readonly string[] scopeRequiredByApi = new string[] {
"API.Access" };

...

[HttpGet]
public IEnumerable<ReturnType> Get()
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

if (User.IsInRole("Admin") || User.IsInRole("Developer"))
{
...
}
else
{
...
}

return ...
}

Exiger que l’utilisateur soit à la fois dans les rôles Admin et Developer avec du code
procédural en remplaçant le conditionnel OU (||) par un conditionnel ET (&&) dans
l’exemple précédent :

C#

if (User.IsInRole("Admin") && User.IsInRole("Developer"))

Étant donné que les comparaisons de chaînes .NET respectent la casse par défaut, les
noms de rôle correspondants respectent également la casse. Par exemple, Admin ( A
majuscule) n’est pas traité comme le même rôle que admin ( a minuscule).

La casse Pascal est généralement utilisée pour les noms de rôles (par exemple,
BillingAdministrator ), mais son utilisation n’est pas une exigence stricte. Différents

schémas de casse, tels que la case chameau, la casse kebab et la casse serpent, sont
autorisés. L’utilisation d’espaces dans les noms de rôle est également inhabituelle, mais
autorisée. Par exemple, billing administrator est un format de nom de rôle inhabituel
dans les applications .NET, mais valide.

Ressources supplémentaires
ID de modèle de rôle (documentation Entra)
Attribut groupMembershipClaims (documentation Entra)
Ajouter des rôles d’application dans votre application et les recevoir dans le jeton
(documentation Entra)
Rôles d’application (documentation Azure)
Autorisation basée sur les revendications dans ASP.NET Core
Autorisation basée sur les rôles dans ASP.NET Core
Authentification et autorisation avec ASP.NET Core Blazor

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Utiliser l’API Graph avec ASP.NET Core
Blazor WebAssembly
Article • 09/02/2024

Cet article explique comment utiliser Microsoft API Graph dans Blazor WebAssembly les
applications, qui est une RESTAPI web complète permettant aux applications d’accéder
aux ressources du service Microsoft Cloud.

Deux approches sont disponibles pour interagir directement avec Microsoft Graph dans
les Blazor applications :

SDK Graph : les kits de développement logiciel (SDK) Microsoft Graph sont conçus
pour simplifier la création d’applications de haute qualité, efficaces et résilientes
qui accèdent à Microsoft Graph. Sélectionnez le bouton SDK Graph en haut de cet
article pour adopter cette approche.

HttpClient nommé avec l’APIGraph : un nommé HttpClient peut émettre des


requêtes d’API web pour accéder directement à l’API Graph. Sélectionnez le
bouton HttpClient nommé avec l’API Graph en haut de cet article pour adopter
cette approche.

Les instructions de cet article ne sont pas destinées à remplacer la documentation


principale de Microsoft Graph et des conseils supplémentaires de sécurité Azure dans
d’autres ensembles de documentation Microsoft. Évaluez les conseils de sécurité dans la
section Ressources supplémentaires de cet article avant d’implémenter Microsoft Graph
dans un environnement de production. Suivez toutes les bonnes pratiques de Microsoft
pour limiter la surface d’attaque de vos applications.

) Important

Les scénarios décrits dans cet article s’appliquent à l’utilisation de Microsoft Entra
(ME-ID) comme fournisseur d’identité, et non AAD B2C. L’utilisation de Microsoft
Graph avec une application Blazor WebAssembly côté client et le fournisseur
d’identité AAD B2C n’est pas pris en charge pour l’instant.

Les exemples de cet article tirent parti des nouvelles fonctionnalités .NET/C#. Lors de
l’utilisation des exemples avec .NET 7 ou antérieur, des modifications mineures sont
requises. Toutefois, les exemples de texte et de code relatifs à l’interaction avec
Microsoft Graph sont identiques pour toutes les versions de ASP.NET Core.
Les instructions suivantes s’appliquent à Microsoft Graph v4. Si vous mettez à niveau une
application du SDK v4 vers v5, consultez le journal des modifications et le guide de mise
à niveau du Kit de développement logiciel (SDK) .NET Microsoft Graph v5.

Le Kit de développement logiciel (SDK) Microsoft Graph à utiliser dans les applications
Blazor est appelé bibliothèque cliente Microsoft Graph .NET.

Les exemples du Kit de développement logiciel (SDK) Graph nécessitent les références
de package suivantes dans l’application autonome Blazor WebAssembly :

Microsoft.AspNetCore.Components.WebAssembly.Authentication
Microsoft.Authentication.WebAssembly.Msal
Microsoft.Extensions.Http
Microsoft.Graph

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Après avoir ajouté les étendues de l’API Microsoft Graph dans la zone ME-ID du portail
Azure, ajoutez la configuration des paramètres d’application suivante au fichier
wwwroot/appsettings.json , qui inclut l’URL de base Graph avec la version et les étendues

graph. Dans l’exemple suivant, l’étendue User.Read est spécifiée pour les exemples dans
les sections ultérieures de cet article.

JSON

{
"MicrosoftGraph": {
"BaseUrl": "https://graph.microsoft.com/{VERSION}",
"Scopes": [
"user.read"
]
}
}

Dans l’exemple précédent, l’espace {VERSION} réservé est la version de l’API MS Graph
(par exemple : v1.0 ).

Ajoutez la classe suivante GraphClientExtensions à l’application autonome. Les


étendues sont fournies à la propriété Scopes de l' AccessTokenRequestOptions dans la
méthode AuthenticateRequestAsync . Le IHttpProvider.OverallTimeout est étendu depuis
la valeur par défaut de 100 secondes à 300 secondes pour donner plus de temps au
HttpClient pour recevoir une réponse de Microsoft Graph.

Lorsqu’un jeton d’accès n’est pas obtenu, le code suivant ne définit pas d’en-tête
d’autorisation du porteur pour les requêtes Graph.

GraphClientExtensions.cs :

C#

using System.Net.Http.Headers;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Authentication.WebAssembly.Msal.Models;
using Microsoft.Graph;

namespace BlazorSample;

internal static class GraphClientExtensions


{
public static IServiceCollection AddGraphClient(
this IServiceCollection services, string? baseUrl, List<string>?
scopes)
{
if (string.IsNullOrEmpty(baseUrl) || scopes?.Count == 0)
{
return services;
}

services.Configure<RemoteAuthenticationOptions<MsalProviderOptions>>
(
options =>
{
scopes?.ForEach((scope) =>
{

options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
});
});

services.AddScoped<IAuthenticationProvider,
GraphAuthenticationProvider>();

services.AddScoped<IHttpProvider, HttpClientHttpProvider>(sp =>


new HttpClientHttpProvider(new HttpClient()));

services.AddScoped(sp =>
{
return new GraphServiceClient(
baseUrl,
sp.GetRequiredService<IAuthenticationProvider>(),
sp.GetRequiredService<IHttpProvider>());
});

return services;
}

private class GraphAuthenticationProvider(IAccessTokenProvider


tokenProvider,
IConfiguration config) : IAuthenticationProvider
{
private readonly IConfiguration config = config;

public IAccessTokenProvider TokenProvider { get; } = tokenProvider;

public async Task AuthenticateRequestAsync(HttpRequestMessage


request)
{
var result = await TokenProvider.RequestAccessToken(
new AccessTokenRequestOptions()
{
Scopes =
config.GetSection("MicrosoftGraph:Scopes").Get<string[]>()
});

if (result.TryGetToken(out var token))


{
request.Headers.Authorization ??= new
AuthenticationHeaderValue(
"Bearer", token.Value);
}
}
}

private class HttpClientHttpProvider(HttpClient client) : IHttpProvider


{
private readonly HttpClient client = client;

public ISerializer Serializer { get; } = new Serializer();

public TimeSpan OverallTimeout { get; set; } =


TimeSpan.FromSeconds(300);

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage


request)
{
return client.SendAsync(request);
}

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage


request,
HttpCompletionOption completionOption,
CancellationToken cancellationToken)
{
return client.SendAsync(request, completionOption,
cancellationToken);
}
public void Dispose()
{
}
}
}

Dans le fichier Program , ajoutez les services clients et la configuration Graph avec la
méthode d’extension AddGraphClient :

C#

var baseUrl = builder.Configuration


.GetSection("MicrosoftGraph")["BaseUrl"];
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
.Get<List<string>>();

builder.Services.AddGraphClient(baseUrl, scopes);

Appeler l’API Graph à partir d’un composant à


l’aide du Kit de développement logiciel (SDK)
Graph
Le composant GraphExample suivant utilise un GraphServiceClient injecté pour obtenir
les données de profil ME-ID de l’utilisateur et afficher son numéro de téléphone mobile.
Pour tout utilisateur de test que vous créez dans ME-ID, veillez à donner au profil ME-ID
de l’utilisateur un numéro de téléphone mobile dans le portail Azure.

GraphExample.razor :

razor

@page "/graph-example"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Graph
@attribute [Authorize]
@inject GraphServiceClient Client

<h1>Microsoft Graph Component Example</h1>

@if (!string.IsNullOrEmpty(user?.MobilePhone))
{
<p>Mobile Phone: @user.MobilePhone</p>
}

@code {
private Microsoft.Graph.User? user;

protected override async Task OnInitializedAsync()


{
var request = Client.Me.Request();
user = await request.GetAsync();
}
}

Lorsque vous effectuez des tests avec le Kit de développement logiciel (SDK) Graph
localement, nous vous recommandons d’utiliser une nouvelle session de navigateur
InPrivate/Incognito pour chaque test afin d’éviter que les cookie en attente n’interfèrent
avec les tests. Pour plus d’informations, consultez Sécuriser une application autonome
ASP.NET Core Blazor WebAssembly avec Microsoft Entra ID.

Personnaliser les revendications utilisateur à


l’aide du Kit de développement logiciel (SDK)
Graph
Dans l’exemple suivant, l’application crée un numéro de téléphone mobile et des
revendications d’emplacement de bureau pour un utilisateur à partir des données de
son profil utilisateur ME-ID. L’application doit avoir l’étendue de l’API Graph User.Read
configurée dans ME-ID. Tous les utilisateurs de test pour ce scénario doivent avoir un
numéro de téléphone mobile et un emplacement de bureau dans leur profil ME-ID, qui
peut être ajouté via le portail Azure.

Dans la fabrique de compte d’utilisateur personnalisée suivante :

Un ILogger ( logger ) est inclus pour des raisons pratiques au cas où vous souhaitez
consigner des informations ou des erreurs dans la méthode CreateUserAsync .
En cas de levée de AccessTokenNotAvailableException, l’utilisateur est redirigé vers
le fournisseur d’identité pour se connecter à son compte. Des actions
supplémentaires ou différentes peuvent être effectuées lors de l’échec de la
requête d’un jeton d’accès. Par exemple, l’application peut enregistrer le
AccessTokenNotAvailableException et créer un ticket de support pour une
investigation plus approfondie.
Le framework RemoteUserAccount représente le compte de l’utilisateur. Si
l’application nécessite une classe de compte d’utilisateur personnalisée qui étend
RemoteUserAccount, changez votre classe de compte d’utilisateur personnalisée
pour RemoteUserAccount dans le code suivant.

CustomAccountFactory.cs :
C#

using System.Security.Claims;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using Microsoft.Graph;

namespace BlazorSample;

public class CustomAccountFactory(IAccessTokenProviderAccessor accessor,


IServiceProvider serviceProvider, ILogger<CustomAccountFactory>
logger)
: AccountClaimsPrincipalFactory<RemoteUserAccount>(accessor)
{
private readonly ILogger<CustomAccountFactory> logger = logger;
private readonly IServiceProvider serviceProvider = serviceProvider;

public override async ValueTask<ClaimsPrincipal> CreateUserAsync(


RemoteUserAccount account,
RemoteAuthenticationUserOptions options)
{
var initialUser = await base.CreateUserAsync(account, options);

if (initialUser.Identity is not null &&


initialUser.Identity.IsAuthenticated)
{
var userIdentity = initialUser.Identity as ClaimsIdentity;

if (userIdentity is not null)


{
try
{
var client = ActivatorUtilities
.CreateInstance<GraphServiceClient>
(serviceProvider);
var request = client.Me.Request();
var user = await request.GetAsync();

if (user is not null)


{
userIdentity.AddClaim(new Claim("mobilephone",
user.MobilePhone ?? "(000) 000-0000"));
userIdentity.AddClaim(new Claim("officelocation",
user.OfficeLocation ?? "Not set"));
}
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
}

return initialUser;
}
}

Configurez l’authentification MSAL pour utiliser la fabrique de compte d’utilisateur


personnalisée.

Vérifiez que le fichier Program utilise l’espace de noms


Microsoft.AspNetCore.Components.WebAssembly.Authentication :

C#

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

L’exemple de cette section s’appuie sur l’approche de lecture de l’URL de base avec la
version et les étendues de la configuration d’application via la section MicrosoftGraph
dans le fichier wwwroot/appsettings.json . Les lignes suivantes doivent déjà être
présentes dans le fichier Program après avoir suivi les instructions décrites plus haut
dans cet article :

C#

var baseUrl = string.Join("/",


builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"];
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
.Get<List<string>>();

builder.Services.AddGraphClient(baseUrl, scopes);

Dans le fichier Program , recherchez l’appel à la méthode d’extension


AddMsalAuthentication. Mettez à jour le code suivant, ce qui inclut un appel à
AddAccountClaimsPrincipalFactory qui ajoute une fabrique de principal de
revendications de compte avec le CustomAccountFactory .

Si l’application utilise une classe de compte d’utilisateur personnalisée qui s’étend


RemoteUserAccount, changez la classe de compte d’utilisateur personnalisée pour
RemoteUserAccount dans le code suivant.

C#

builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
RemoteUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState,
RemoteUserAccount,
CustomAccountFactory>();

Vous pouvez utiliser le composant UserClaims suivant pour étudier les revendications
de l’utilisateur après l’authentification de l’utilisateur avec ME-ID :

UserClaims.razor :

razor

@page "/user-claims"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
@inject AuthenticationStateProvider AuthenticationStateProvider

<h1>User Claims</h1>

@if (claims.Any())
{
<ul>
@foreach (var claim in claims)
{
<li>@claim.Type: @claim.Value</li>
}
</ul>
}
else
{
<p>No claims found.</p>
}

@code {
private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

protected override async Task OnInitializedAsync()


{
var authState = await AuthenticationStateProvider
.GetAuthenticationStateAsync();
var user = authState.User;

claims = user.Claims;
}
}

Lorsque vous effectuez des tests avec le Kit de développement logiciel (SDK) Graph
localement, nous vous recommandons d’utiliser une nouvelle session de navigateur
InPrivate/Incognito pour chaque test afin d’éviter que les cookie en attente n’interfèrent
avec les tests. Pour plus d’informations, consultez Sécuriser une application autonome
ASP.NET Core Blazor WebAssembly avec Microsoft Entra ID.

Ressources supplémentaires

Règle générale
Documentation Microsoft Graph
Exemple d’application Microsoft GraphBlazor WebAssembly : cet exemple
montre comment utiliser le Kit de développement logiciel (SDK) Microsoft Graph
.NET pour accéder aux données dans Office 365 à partir d’applications Blazor
WebAssembly .
Créer des applications .NET avec le didacticiel Microsoft Graph et l’exemple
d’application Microsoft Graph ASP.NET Core : bien que ces ressources ne
s’appliquent pas directement à l’appel de Graph à partir d’applications côté
clientBlazor WebAssembly, la configuration de l’application ME-ID et les pratiques
de codage Microsoft Graph dans les ressources liées sont pertinentes pour les
applications autonomes Blazor WebAssembly et doivent être consultées pour
connaître les meilleures pratiques générales.

Conseils de sécurité
Présentation de l'authentification de Microsoft Graph
Vue d’ensemble des autorisations de Microsoft Graph
Informations de référence sur les autorisations Microsoft Graph
Améliorer la sécurité avec le principe des privilèges minimum
Articles sur l’escalade de privilèges Azure sur Internet (résultat de la recherche
Google)

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Appliquer une stratégie de sécurité du
contenu pour ASP.NET Core Blazor
Article • 09/02/2024

Cet article explique comment utiliser une stratégie de sécurité de contenu (CSP) avec
des applications ASP.NET Core Blazor pour vous protéger contre les attaques de
scripting inter-site (XSS).

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.
Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples
d’applications BlazorSample_{PROJECT TYPE} à partir du Blazordépôt GitHub
d’exemples correspondant à la version .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Le scripting inter-site (XSS) est une vulnérabilité de sécurité dans laquelle un attaquant
place un ou plusieurs scripts côté client malveillants dans le contenu rendu d’une
application. Un stratégie de sécurité du contenu permet de se protéger contre les
attaques XSS en informant le navigateur de la validité des :

Sources du contenu chargé, y compris les scripts, les feuilles de style, les images et
les plug-ins.
Actions effectuées par une page, spécifiant les cibles d’URL autorisées des
formulaires.

Pour appliquer une stratégie de sécurité du contenu à une application, le développeur


spécifie plusieurs directives de sécurité du contenu CSP dans un ou plusieurs en-têtes
Content-Security-Policy ou balises <meta> . Pour obtenir des conseils sur l’application

d’un fournisseur de solutions Cloud à une application en code C# au démarrage,


consultez Démarrage de ASP.NET Core Blazor.

Les stratégies sont évaluées par le navigateur pendant le chargement d’une page. Le
navigateur inspecte les sources de la page et détermine si elles répondent aux exigences
des directives de sécurité du contenu. Lorsque les directives de stratégie ne sont pas
respectées pour une ressource, le navigateur ne charge pas cette ressource. Par
exemple, imaginez une stratégie qui n’autorise pas les scripts tiers. Lorsqu’une page
contient une balise <script> avec une origine tierce dans l’attribut src , le navigateur
empêche le chargement du script.

La stratégie de sécurité du contenu est prise en charge dans la plupart des navigateurs
mobiles et de bureau modernes, notamment Chrome, Edge, Firefox, Opera et Safari. La
stratégie de sécurité du contenu est recommandée pour les applications Blazor.

Directives de stratégie
Spécifiez au minimum les directives et les sources suivantes pour les applications Blazor.
Ajoutez des directives et des sources supplémentaires, si nécessaire. Les directives
suivantes sont utilisées dans la section Appliquer la stratégie de cet article, où sont
fournis des exemples de stratégies de sécurité pour les applications Blazor :
base-uri : limite les URL de la balise <base> d’une page. Spécifiez self pour
indiquer que l’origine de l’application, y compris le schéma et le numéro de port,
est une source valide.
default-src : indique une solution de repli pour les directives sources qui ne sont
pas explicitement spécifiées par la stratégie. Spécifiez self pour indiquer que
l’origine de l’application, y compris le schéma et le numéro de port, est une source
valide.
img-src : indique des sources valides des images.
Spécifiez data: pour autoriser le chargement d’images à partir des URL data: .
Spécifiez https: pour autoriser le chargement d’images à partir de points de
terminaison HTTPS.
object-src : indique des sources valides des balises <object> , <embed> et
<applet> . Spécifiez none pour empêcher toutes les sources d’URL.

script-src : indique des sources valides des scripts.


Spécifiez self pour indiquer que l’origine de l’application, y compris le schéma
et le numéro de port, est une source valide.
Dans une application Blazor côté client :
Spécifiez wasm-unsafe-eval pour autoriser le runtime Mono Blazor côté
client à fonctionner.
Spécifiez les hachages supplémentaires pour autoriser le chargement de vos
scripts hors infrastructure obligatoires.
Dans une application Blazor côté serveur, spécifiez les hachages pour autoriser
le chargement des scripts obligatoires.
style-src : indique des sources valides des feuilles de style.
Spécifiez self pour indiquer que l’origine de l’application, y compris le schéma
et le numéro de port, est une source valide.
Si l’application utilise des styles intralignes, spécifiez unsafe-inline pour
autoriser l’utilisation de vos styles intralignes.
upgrade-insecure-requests : indique que les URL de contenu provenant de
sources non sécurisées (HTTP) doivent être acquises de manière sécurisée via
HTTPS.

Les directives précédentes sont prises en charge par tous les navigateurs, à l’exception
de Microsoft Internet Explorer.

Pour obtenir des hachages SHA pour des scripts intralignes supplémentaires :

Appliquez la stratégie de sécurité du contenu indiquée dans la section Appliquer la


stratégie.
Accédez à la console des outils de développement du navigateur lors de
l’exécution locale de l’application. Le navigateur calcule et affiche des hachages
des scripts bloqués lorsqu’un en-tête de fournisseur de solutions Cloud ou une
balise meta est présent(e).
Copiez les hachages fournis par le navigateur dans les sources script-src . Utilisez
des guillemets simples autour de chaque hachage.

Pour obtenir une matrice de prise en charge du navigateur de niveau 2 de stratégie de


sécurité du contenu, consultez Puis-je utiliser : stratégie de sécurité du contenu niveau
2 .

Application de la stratégie
Utilisez une balise <meta> pour appliquer la stratégie :

Définissez la valeur de l’attribut http-equiv sur Content-Security-Policy .


Placez les directives dans la valeur de l’attribut content . Des directives distinctes
avec un point-virgule ( ; ).
Placez toujours la balise meta dans le<head> contenu.

Les sections suivantes présentent des exemples de stratégie. Ces exemples sont
versionnés avec cet article pour chaque édition de Blazor. Pour utiliser une version
appropriée pour votre édition, sélectionnez la version du document à l’aide du sélecteur
de liste déroulante Version sur cette page web.

Applications Blazor côté serveur


Dans le <head> contenu, appliquez les directives décrites dans la section Directives de
stratégie :

HTML

<meta http-equiv="Content-Security-Policy"
content="base-uri 'self';
default-src 'self';
img-src data: https:;
object-src 'none';
script-src 'self';
style-src 'self';
upgrade-insecure-requests;">

Ajoutez les hachages supplémentaires script-src et style-src selon les besoins de


l’application. Pendant le développement, utilisez un outil en ligne ou des outils de
développement de navigateur pour calculer les hachages pour vous. Par exemple,
l’erreur suivante dans la console des outils de navigateur signale le hachage d’un script
obligatoire non couvert par la stratégie :

Refus d’exécuter un script intraligne parce qu’il enfreint la directive de stratégie de


sécurité du contenu suivante : « ... ». Le mot clé « unsafe-inline », un hachage
(« sha256-v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA= ») ou un
nonce (« nonce-... ») est obligatoire pour activer l’exécution intraligne.

Le script particulier associé à l’erreur s’affiche dans la console après l’erreur.

Applications Blazor côté client


Dans le <head> contenu, appliquez les directives décrites dans la section Directives de
stratégie :

HTML

<meta http-equiv="Content-Security-Policy"
content="base-uri 'self';
default-src 'self';
img-src data: https:;
object-src 'none';
script-src 'self'
'wasm-unsafe-eval';
style-src 'self';
upgrade-insecure-requests;">

Ajoutez les hachages supplémentaires script-src et style-src selon les besoins de


l’application. Pendant le développement, utilisez un outil en ligne ou des outils de
développement de navigateur pour calculer les hachages pour vous. Par exemple,
l’erreur suivante dans la console des outils de navigateur signale le hachage d’un script
obligatoire non couvert par la stratégie :

Refus d’exécuter un script intraligne parce qu’il enfreint la directive de stratégie de


sécurité du contenu suivante : « ... ». Le mot clé « unsafe-inline », un hachage
(« sha256-v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA= ») ou un
nonce (« nonce-... ») est obligatoire pour activer l’exécution intraligne.

Le script particulier associé à l’erreur s’affiche dans la console après l’erreur.

Appliquer une stratégie de sécurité de contenu


(CSP, Content Security Policy) dans des
environnements hors Development
Lorsqu’une CSP est appliquée au contenu <head> d’une application Blazor, elle interfère
avec les tests locaux dans l’environnement de Development . Par exemple, le lien du
navigateur et le script d’actualisation du navigateur ne peuvent pas être chargés. Les
exemples suivants montrent comment appliquer la balise <meta> de la CSP dans des
environnements hors Development .

7 Notes

Les exemples de cette section n’affichent pas la balise <meta> complète pour les
CSP. Les balises <meta> complètes se trouvent dans les sous-sections de la section
Appliquer la stratégie plus haut dans cet article.

Trois approches générales sont disponibles :

Appliquez la CSP via le composant App , qui permet d’appliquer la CSP à toutes les
layouts de l’application.
Si vous devez appliquer des CSP à différentes zones de l’application, par exemple
une CSP personnalisée pour les pages d’administration uniquement, appliquez la
CSP par layout à l’aide de la balise <HeadContent>. Pour une efficacité totale,
chaque fichier de layout d’application doit adopter cette approche.
Le service d’hébergement ou le serveur peut fournir une CSP via un en-tête
Content-Security-Policy ajouté aux réponses sortantes d’une application. Étant
donné que cette approche varie en fonction du service d’hébergement ou du
serveur, elle n’est pas traitée dans les exemples suivants. Si vous souhaitez adopter
cette approche, consultez la documentation de votre fournisseur de services
d’hébergement ou votre serveur.

Approches d’application web Blazor


Dans le composant App ( Components/App.razor ), injectez IHostEnvironment :

razor

@inject IHostEnvironment Env

Dans le contenu <head> du composant App , appliquez la CSP quand elle n’est pas dans
l’environnement de Development :
razor

@if (!Env.IsDevelopment())
{
<meta ...>
}

Vous pouvez également appliquer des CSP par layout dans le dossier
Components/Layout , comme l’illustre l’exemple suivant. Assurez-vous que chaque layout

spécifie une CSP.

razor

@inject IHostEnvironment Env

@if (!Env.IsDevelopment())
{
<HeadContent>
<meta ...>
</HeadContent>
}

Approches d’application Blazor WebAssembly


Dans le composant App ( App.razor ), injectez IWebAssemblyHostEnvironment :

razor

@using Microsoft.AspNetCore.Components.WebAssembly.Hosting
@inject IWebAssemblyHostEnvironment Env

Dans le contenu <head> du composant App , appliquez la CSP quand elle n’est pas dans
l’environnement de Development :

razor

@if (!Env.IsDevelopment())
{
<HeadContent>
<meta ...>
</HeadContent>
}

Vous pouvez également utiliser le code précédent, mais appliquer des CSP par layout
dans le dossier Layout . Assurez-vous que chaque layout spécifie une CSP.
Limitations des balises meta
Une stratégie de balise <meta> ne prend pas en charge les directives suivantes :

frame-ancestors
report-to
report-uri
sandbox

Pour prendre en charge les directives précédentes, utilisez un en-tête nommé Content-
Security-Policy . La chaîne de directive est la valeur de l’en-tête.

Tester une stratégie et recevoir des rapports de


violation
Le test permet de vérifier que les scripts tiers ne sont pas bloqués par inadvertance lors
de la création d’une stratégie initiale.

Pour tester une stratégie sur une certaine période sans appliquer les directives de
stratégie, définissez l’attribut <meta> de la balise http-equiv ou le nom d’en-tête d’une
stratégie basée sur l’en-tête sur Content-Security-Policy-Report-Only . Les rapports
d’échec sont envoyés sous forme de documents JSON à une URL spécifiée. Pour plus
d’informations, consultez Documentation web sur notification de réception du
message : rapports de stratégie de sécurité du contenu uniquement .

Pour la création de rapports sur les violations pendant qu’une stratégie est active,
consultez les articles suivants :

report-to
report-uri

Bien que l’utilisation de report-uri ne soit plus recommandée, les deux directives
doivent être utilisées jusqu’à ce que report-to soit pris en charge par tous les
principaux navigateurs. N’utilisez pas report-uri exclusivement, car la prise en charge
de report-uri peut être supprimée des navigateurs à tout moment. Supprimez la prise
en charge de report-uri dans vos stratégies lorsque report-to est entièrement pris en
charge. Pour suivre l’adoption de report-to , consultez Puis-je utiliser : report-to .

Testez et mettez à jour la stratégie d’une application à chaque édition.

Résoudre des problèmes


Les erreurs s’affichent dans la console des outils de développement du navigateur.
Les navigateurs fournissent des informations sur :
Les éléments qui ne sont pas conformes à la stratégie.
Comment modifier la stratégie pour autoriser un élément bloqué.
Une stratégie n’est totalement efficace que lorsque le navigateur du client prend
en charge toutes les directives comprises. Pour obtenir une matrice de prise en
charge du navigateur actuel, consultez Puis-je utiliser : stratégie de sécurité du
contenu .

Ressources supplémentaires
Appliquer une stratégie de sécurité du contenu dans le code C# au démarrage
MDN web docs : stratégie de sécurité du contenu (CSP)
MDN web docs : en-tête de réponse Content-Security-Policy
Stratégie de sécurité du contenu de niveau 2
Évaluateur de stratégie de sécurité du contenu de Google

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Gestion de l’état d’ASP.NET Core Blazor
Article • 09/02/2024

Cet article décrit les approches courantes pour gérer les données (état) d’un utilisateur
pendant qu’il utilise une application et entre les sessions de navigateur.

7 Notes

Les exemples de code de cet article adoptent les types référence null (NRT) et
l'analyse statique de l'état null du compilateur .NET, qui sont pris en charge dans
ASP.NET Core 6.0 ou une version ultérieure. Lorsque vous ciblez ASP.NET Core 5.0
ou version antérieure, supprimez la désignation de type Null ( ? ) des types dans les
exemples de l’article.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ) :

Dans une application web Blazor, un mode d’affichage interactif doit être appliqué
au composant. Ce mode peut être spécifié dans le fichier de définition du
composant ou hérité d’un composant parent. Pour plus d’informations, consultez
Modes de rendu ASP.NET Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode d’affichage,
car ils s’exécutent toujours de manière interactive sur WebAssembly dans une
application Blazor WebAssembly.

Lorsque vous utilisez les modes d’affichage WebAssembly interactif ou Auto interactif, le
code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas de
code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.
Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir des exemples Blazor du référentiel
GitHub correspondant à la version .NET que vous ciblez. Pour le moment, tous les
exemples de la documentation ne figurent pas dans les exemples d’applications, mais
nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans les
exemples d’applications .NET 8. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Gérer l’état utilisateur


Blazor côté serveur est un framework d’application avec état. La plupart du temps,
l’application maintient une connexion au serveur. L’état de l’utilisateur est conservé dans
la mémoire du serveur dans un circuit.

Voici quelques exemples d’état utilisateur conservé dans un circuit :

Hiérarchie des instances de composant et leur sortie de rendu la plus récente dans
l’interface utilisateur rendue.
Valeurs des champs et des propriétés dans les instances de composant.
Données conservées dans des instances de service d’injection de dépendances (DI)
qui sont limitées au circuit.

L’état utilisateur peut également se trouver dans des variables JavaScript dans le jeu de
mémoire du navigateur via des appels d’interopérabilité JavaScript.

Si un utilisateur subit une perte temporaire de connexion réseau, Blazor tente de


reconnecter l’utilisateur à son circuit d’origine avec son état d’origine. Toutefois, il n’est
pas toujours possible de reconnecter un utilisateur à son circuit d’origine dans la
mémoire du serveur :

Le serveur ne peut pas conserver un circuit déconnecté indéfiniment. Le serveur


doit libérer un circuit déconnecté après un délai d’expiration ou lorsque le serveur
a besoin de mémoire.
Dans les environnements de déploiement multiserveurs et à charge équilibrée, des
serveurs individuels peuvent échouer ou être automatiquement supprimés
lorsqu’ils ne sont plus nécessaires pour gérer le volume global de requêtes. Le
serveur d’origine qui traite les requêtes d’un utilisateur peut devenir indisponible
lorsque l’utilisateur tente de se reconnecter.
L’utilisateur peut fermer et rouvrir son navigateur ou recharger la page, ce qui
supprime tout état conservé dans la mémoire du navigateur. Par exemple, les
valeurs des variables JavaScript définies par le biais d’appels d’interopérabilité
JavaScript sont perdues.

Lorsqu’un utilisateur ne peut pas être reconnecté à son circuit d’origine, il reçoit un
nouveau circuit avec un état vide. Cela revient à fermer et à rouvrir une application de
bureau.

Conserver l’état sur les circuits


En règle générale, conservez l’état sur les circuits où les utilisateurs créent activement
des données, et pas simplement en lisant des données qui existent déjà.

Pour conserver l’état sur les circuits, l’application doit conserver les données dans un
autre emplacement de stockage que la mémoire du serveur. La persistance d’état n’est
pas automatique. Vous devez prendre des mesures lors du développement de
l’application pour implémenter la persistance des données avec état.

La persistance des données n’est généralement requise que pour l’état à valeur élevée
que les utilisateurs ont consacré des efforts à créer. Dans les exemples suivants, l’état
persistant fait gagner du temps ou facilite les activités commerciales :

Formulaires web à plusieurs étapes : il est fastidieux pour un utilisateur de saisir à


nouveau des données pour plusieurs étapes terminées d’un formulaire web à
plusieurs étapes si leur état est perdu. Un utilisateur perd l’état dans ce scénario s’il
quitte le formulaire et revient ultérieurement.
Paniers d’achat : tout composant important sur le plan commercial d’une
application qui représente le chiffre d’affaires potentiel peut être conservé. Un
utilisateur qui perd son état, et donc son panier d’achat, est susceptible d’acheter
moins de produits ou de services lorsqu’il revient sur le site plus tard.

Une application peut uniquement conserver l’état de l’application. Les interfaces


utilisateur ne peuvent pas être conservées, comme les instances de composant et leurs
arborescences de rendu. Les composants et les arborescences de rendu ne sont
généralement pas sérialisables. Pour conserver l’état de l’interface utilisateur, comme les
nœuds développés d’un contrôle d’arborescence, l’application doit utiliser du code
personnalisé pour modéliser le comportement de l’état de l’interface utilisateur comme
état de l’application sérialisable.
Où conserver l’état
Il existe des emplacements courants pour conserver l’état :

Stockage côté serveur


URL
Stockage du navigateur
Service de conteneur d’état en mémoire

Stockage côté serveur


Pour une persistance permanente des données qui s’étend sur plusieurs utilisateurs et
appareils, l’application peut utiliser le stockage côté serveur. Voici les options :

Stockage Blob
Stockage de clé-valeur
Base de données relationnelle
Stockage de tables

Une fois les données enregistrées, l’état de l’utilisateur est conservé et disponible dans
tout nouveau circuit.

Pour plus d’informations sur les options de stockage de données Azure, consultez les
rubriques suivantes :

Bases de données Azure


Documentation d’Azure Storage

URL
Pour les données temporaires représentant l’état de navigation, modélisez les données
dans le cadre de l’URL. Voici quelques exemples d’état utilisateur modélisé dans l’URL :

ID d’une entité consultée.


Numéro de page actuel dans une grille paginée.

Le contenu de la barre d’adresse du navigateur est conservé :

Si l’utilisateur recharge manuellement la page.


Si le serveur web devient indisponible et que l’utilisateur est obligé de recharger la
page pour se connecter à un autre serveur.
Pour plus d’informations sur la définition de modèles d’URL avec la directive @page,
consultez Routage et navigation avec ASP.NET Core Blazor.

Stockage du navigateur
Pour les données temporaires que l’utilisateur crée activement, les collections
localStorage et sessionStorage sont un emplacement de stockage couramment
utilisé :

localStorage est limité à la fenêtre du navigateur. Si l’utilisateur recharge la page

ou ferme et rouvre le navigateur, l’état persiste. Si l’utilisateur ouvre plusieurs


onglets de navigateur, l’état est partagé entre les onglets. Les données sont
conservées dans localStorage jusqu’à leur effacement explicite.
sessionStorage est limité à l’onglet du navigateur. Si l’utilisateur recharge l’onglet,
l’état persiste. Si l’utilisateur ferme l’onglet ou le navigateur, l’état est perdu. Si
l’utilisateur ouvre plusieurs onglets de navigateur, chaque onglet a sa propre
version indépendante des données.

En général, sessionStorage est plus sûr à utiliser. sessionStorage évite le risque qu’un
utilisateur ouvre plusieurs onglets et rencontre les problèmes suivants :

Bogues dans le stockage d’état entre les onglets.


Comportement confus lorsqu’un onglet remplace l’état d’autres onglets.

localStorage est le meilleur choix si l’application doit conserver l’état pendant la

fermeture et la réouverture du navigateur.

Mises en garde pour l’utilisation du stockage du navigateur :

À l’instar de l’utilisation d’une base de données côté serveur, le chargement et


l’enregistrement des données sont asynchrones.
Contrairement à une base de données côté serveur, le stockage n’est pas
disponible pendant le prérendu, car la page demandée n’existe pas dans le
navigateur pendant la phase de prérendu.
Il est raisonnable de stocker quelques kilo-octets de données pour les applications
Blazor côté serveur. Au-delà de quelques kilo-octets, vous devez prendre en
compte les implications en matière de performances, car les données sont
chargées et enregistrées sur le réseau.
Les utilisateurs pourraient afficher ou falsifier les données. La protection des
données ASP.NET Core peut atténuer le risque. Par exemple, le stockage du
navigateur protégé ASP.NET Core utilise la protection des données ASP.NET Core.
Les packages NuGet tiers fournissent des API permettant d’utiliser localStorage et
sessionStorage . Il est utile de choisir un package qui utilise de manière transparente la

protection des données ASP.NET Core. La protection des données chiffre les données
stockées et réduit le risque potentiel de falsification des données stockées. Si les
données sérialisées JSON sont stockées en texte brut, les utilisateurs peuvent voir les
données à l’aide des outils de développement du navigateur et également modifier les
données stockées. La sécurisation des données n’est pas toujours un problème, car ces
dernières peuvent être de nature triviale. Par exemple, la lecture ou la modification de la
couleur stockée d’un élément d’interface utilisateur n’est pas un risque de sécurité
significatif pour l’utilisateur ou l’organisation. Évitez d’autoriser les utilisateurs à
inspecter ou à falsifier les données sensibles.

Stockage du navigateur protégé ASP.NET Core


Le stockage par navigateur protégé ASP.NET Core tire parti de la protection des
données ASP.NET Core pour localStorage et sessionStorage .

7 Notes

Le stockage du navigateur protégé s’appuie sur la protection des données ASP.NET


Core et est uniquement pris en charge pour les applications Blazor côté serveur.

Enregistrer et charger des données dans un composant


Dans tout composant nécessitant le chargement ou l’enregistrement de données dans
le stockage du navigateur, utilisez la directive @inject pour injecter une des instances
suivantes :

ProtectedLocalStorage

ProtectedSessionStorage

Le choix dépend de l’emplacement de stockage du navigateur que vous souhaitez


utiliser. sessionStorage est utilisé dans l’exemple suivant :

razor

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore
La directive @using peut être placée dans le fichier _Imports.razor de l’application au
lieu du composant. L’utilisation du fichier _Imports.razor rend l’espace de noms
disponible pour les segments plus volumineux de l’application ou l’ensemble de
l’application.

Pour conserver la valeur currentCount dans le composant Counter d’une application en


fonction du modèle de projet Blazor, modifiez la méthode IncrementCount pour utiliser
ProtectedSessionStore.SetAsync :

C#

private async Task IncrementCount()


{
currentCount++;
await ProtectedSessionStore.SetAsync("count", currentCount);
}

Dans les applications plus grandes et plus réalistes, le stockage de champs individuels
est un scénario peu probable. Les applications sont plus susceptibles de stocker des
objets de modèle entiers qui incluent un état complexe. ProtectedSessionStore sérialise
et désérialise automatiquement les données JSON pour stocker des objets d’état
complexes.

Dans l’exemple de code précédent, les données currentCount sont stockées en tant que
sessionStorage['count'] dans le navigateur de l’utilisateur. Les données ne sont pas

stockées en texte brut, mais sont plutôt protégées à l’aide de la protection des données
ASP.NET Core. Les données chiffrées peuvent être inspectées si
sessionStorage['count'] est évalué dans la console du développeur du navigateur.

Pour récupérer les données currentCount si l’utilisateur revient au composant Counter


ultérieurement, notamment si l’utilisateur se trouve sur un nouveau circuit, utilisez
ProtectedSessionStore.GetAsync :

C#

protected override async Task OnInitializedAsync()


{
var result = await ProtectedSessionStore.GetAsync<int>("count");
currentCount = result.Success ? result.Value : 0;
}

Si les paramètres du composant incluent l’état de navigation, appelez


ProtectedSessionStore.GetAsync et affectez un résultat non null dans

OnParametersSetAsync, et non OnInitializedAsync. OnInitializedAsync n’est appelé


qu’une seule fois lorsque le composant est instancié pour la première fois.
OnInitializedAsync n’est pas appelé ultérieurement si l’utilisateur accède à une autre URL
tout en restant sur la même page. Pour plus d’informations, consultez le cycle de vie des
composants Razor ASP.NET Core.

2 Avertissement

Les exemples de cette section ne fonctionnent que si le serveur n’a pas activé le
prérendu. Une fois le prérendu activé, une erreur est générée expliquant que les
appels d’interopérabilité JavaScript ne peuvent pas être émis, car le composant est
en cours de prérendu.

Désactivez le prérendu ou ajoutez du code supplémentaire pour utiliser


correctement le prérendu. Pour en savoir plus sur l’écriture de code qui fonctionne
avec le prérendu, consultez la section Gérer le prérendu.

Gérer l’état de chargement


Étant donné que le stockage du navigateur est accessible de manière asynchrone via
une connexion réseau, il existe toujours un délai avant que les données soient chargées
et disponibles pour un composant. Pour obtenir de meilleurs résultats, affichez un
message pendant le chargement en cours au lieu d’afficher des données vides ou par
défaut.

L’une des approches consiste à déterminer si les données sont null , ce qui signifie que
les données sont toujours en cours de chargement. Dans le composant Counter par
défaut, le nombre est conservé dans un int . Rendez currentCount nullable en ajoutant
un point d’interrogation ( ? ) au type ( int ) :

C#

private int? currentCount;

Au lieu d’afficher inconditionnellement le nombre et le bouton Increment , affichez ces


éléments uniquement si les données sont chargées en vérifiant HasValue :

razor

@if (currentCount.HasValue)
{
<p>Current count: <strong>@currentCount</strong></p>
<button @onclick="IncrementCount">Increment</button>
}
else
{
<p>Loading...</p>
}

Gérer le prérendu
Pendant le prérendu :

Il n’existe pas de connexion interactive au navigateur de l’utilisateur.


Le navigateur n’a pas encore de page dans laquelle il peut exécuter du code
JavaScript.

localStorage ou sessionStorage ne sont pas disponibles lors du prérendu. Si le

composant tente d’interagir avec le stockage, une erreur est générée expliquant que les
appels d’interopérabilité JavaScript ne peuvent pas être émis, car le composant est en
cours de prérendu.

L’un des moyens de résoudre l’erreur consiste à désactiver le prérendu. Il s’agit


généralement du meilleur choix si l’application utilise intensivement le stockage basé sur
un navigateur. Le prérendu ajoute de la complexité et n’est pas bénéfique pour
l’application, car l’application ne peut pas prérendre du contenu utile tant que
localStorage ou sessionStorage n’est pas disponible.

Pour désactiver le prérendu, indiquez le mode de rendu avec le paramètre prerender


défini sur false au niveau le plus élevé dans la hiérarchie des composants de
l’application qui n’est pas un composant racine.

7 Notes

Rendre un composant racine interactif, comme le composant App , n’est pas pris en
charge. Par conséquent, le prérendu ne peut pas être désactivé directement par le
composant App .

Pour les applications basées sur le modèle de projet d’application web Blazor, le
prérendu est habituellement désactivé où le composant Routes est utilisé dans le
composant App ( Components/App.razor ) :

razor

<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />


Désactivez également le pré-rendu pour le composant HeadOutlet :

razor

<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)"


/>

Pour plus d’informations, consultez Modes de rendu ASP.NET Core Blazor.

Lorsque le prérendu est désactivé, le prérendu du contenu <head> est désactivé.

Le prérendu peut être utile pour d’autres pages qui n’utilisent pas localStorage ou
sessionStorage . Pour conserver le prérendu, reportez l’opération de chargement jusqu’à

ce que le navigateur soit connecté au circuit. Voici un exemple de stockage d’une valeur
de compteur :

razor

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
<p>Current count: <strong>@currentCount</strong></p>
<button @onclick="IncrementCount">Increment</button>
}
else
{
<p>Loading...</p>
}

@code {
private int currentCount;
private bool isConnected;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
isConnected = true;
await LoadStateAsync();
StateHasChanged();
}
}

private async Task LoadStateAsync()


{
var result = await ProtectedLocalStore.GetAsync<int>("count");
currentCount = result.Success ? result.Value : 0;
}
private async Task IncrementCount()
{
currentCount++;
await ProtectedLocalStore.SetAsync("count", currentCount);
}
}

Factoriser la conservation de l’état à un emplacement


commun
Si de nombreux composants s’appuient sur un stockage basé sur le navigateur,
l’implémentation du code du fournisseur d’état crée souvent une duplication de code.
L’une des options permettant d’éviter la duplication du code consiste à créer un
composant parent du fournisseur d’état qui encapsule la logique du fournisseur d’état.
Les composants enfants peuvent fonctionner avec des données persistantes sans tenir
compte du mécanisme de persistance d’état.

Dans l’exemple suivant d’un composant CounterStateProvider , les données de


compteur sont conservées dans sessionStorage :

razor

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
<CascadingValue Value="this">
@ChildContent
</CascadingValue>
}
else
{
<p>Loading...</p>
}

@code {
private bool isLoaded;

[Parameter]
public RenderFragment? ChildContent { get; set; }

public int CurrentCount { get; set; }

protected override async Task OnInitializedAsync()


{
var result = await ProtectedSessionStore.GetAsync<int>("count");
CurrentCount = result.Success ? result.Value : 0;
isLoaded = true;
}

public async Task SaveChangesAsync()


{
await ProtectedSessionStore.SetAsync("count", CurrentCount);
}
}

7 Notes

Pour plus d’informations sur RenderFragment, consultez Composants ASP.NET


Core Razor.

Le composant CounterStateProvider gère la phase de chargement en ne rendant pas


son contenu enfant tant que le chargement de l’état n’est pas terminé.

Pour rendre l’état accessible à tous les composants d’une application, enveloppez le
composant CounterStateProvider autour du Router ( <Router>...</Router> ) dans le
composant Routes avec le rendu interactif côté serveur (SSR interactif) global.

Dans le composant App ( Components/App.razor ) :

razor

<Routes @rendermode="InteractiveServer" />

Dans le composant Routes ( Components/Routes.razor ) :

razor

<CounterStateProvider>
<Router ...>
...
</Router>
</CounterStateProvider>

Les composants encapsulés reçoivent et peuvent modifier l’état persistant du compteur.


Le composant Counter suivant implémente le modèle :

razor

@page "/counter"

<p>Current count: <strong>@CounterStateProvider?.CurrentCount</strong></p>


<button @onclick="IncrementCount">Increment</button>
@code {
[CascadingParameter]
private CounterStateProvider? CounterStateProvider { get; set; }

private async Task IncrementCount()


{
if (CounterStateProvider is not null)
{
CounterStateProvider.CurrentCount++;
await CounterStateProvider.SaveChangesAsync();
}
}
}

Le composant précédent n’est pas obligatoire pour interagir avec


ProtectedBrowserStorage , et il ne traite pas non plus de phase de « chargement ».

Pour traiter le prérendu comme décrit précédemment, CounterStateProvider peut être


modifié de sorte que tous les composants qui consomment les données du compteur
fonctionnent automatiquement avec le prérendu. Pour plus d’informations, consultez la
section Gérer le prérendu.

En règle générale, le modèle composant parent du fournisseur d’état est recommandé :

Pour consommer l’état sur de nombreux composants.


S’il n’y a qu’un seul objet d’état de niveau supérieur à conserver.

Pour conserver de nombreux objets d’état différents et consommer différents sous-


ensembles d’objets à différents emplacements, il est préférable d’éviter la persistance de
l’état à l’échelle globale.

Service de conteneur d’état en mémoire


Les composants imbriqués lient généralement des données à l’aide d’une liaison
chaînée, comme décrit dans Liaison de données ASP.NET Core Blazor. Les composants
imbriqués et non imbriqués peuvent partager l’accès aux données à l’aide d’un
conteneur d’état en mémoire inscrit. Une classe de conteneur d’état personnalisé peut
utiliser un Action assignable pour notifier les composants de différentes parties de
l’application des changements d’état. Dans l’exemple suivant :

Une paire de composants utilise un conteneur d’état pour suivre une propriété.
Un composant dans l’exemple suivant est imbriqué dans l’autre composant, mais
l’imbrication n’est pas nécessaire pour que cette approche fonctionne.
) Important

L’exemple de cette section montre comment créer un service de conteneur d’état


en mémoire, inscrire le service et utiliser le service dans les composants. L’exemple
ne conserve pas les données sans développement ultérieur. Pour le stockage
persistant des données, le conteneur d’état doit adopter un mécanisme de
stockage sous-jacent qui survit lorsque la mémoire du navigateur est effacée. Cela
peut être réalisé avec localStorage / sessionStorage ou une autre technologie.

StateContainer.cs :

C#

public class StateContainer


{
private string? savedString;

public string Property


{
get => savedString ?? string.Empty;
set
{
savedString = value;
NotifyStateChanged();
}
}

public event Action? OnChange;

private void NotifyStateChanged() => OnChange?.Invoke();


}

Applications côté client (fichier Program ) :

C#

builder.Services.AddSingleton<StateContainer>();

Applications côté serveur (fichier Program , ASP.NET Core 6.0 ou ultérieur) :

C#

builder.Services.AddScoped<StateContainer>();
Applications côté serveur ( Startup.ConfigureServices de Startup.cs , ASP.NET Core
antérieur à la version 6.0) :

C#

services.AddScoped<StateContainer>();

Shared/Nested.razor :

razor

@implements IDisposable
@inject StateContainer StateContainer

<h2>Nested component</h2>

<p>Nested component Property: <b>@StateContainer.Property</b></p>

<p>
<button @onclick="ChangePropertyValue">
Change the Property from the Nested component
</button>
</p>

@code {
protected override void OnInitialized()
{
StateContainer.OnChange += StateHasChanged;
}

private void ChangePropertyValue()


{
StateContainer.Property =
$"New value set in the Nested component: {DateTime.Now}";
}

public void Dispose()


{
StateContainer.OnChange -= StateHasChanged;
}
}

StateContainerExample.razor :

razor

@page "/state-container-example"
@implements IDisposable
@inject StateContainer StateContainer
<h1>State Container Example component</h1>

<p>State Container component Property: <b>@StateContainer.Property</b></p>

<p>
<button @onclick="ChangePropertyValue">
Change the Property from the State Container Example component
</button>
</p>

<Nested />

@code {
protected override void OnInitialized()
{
StateContainer.OnChange += StateHasChanged;
}

private void ChangePropertyValue()


{
StateContainer.Property = "New value set in the State " +
$"Container Example component: {DateTime.Now}";
}

public void Dispose()


{
StateContainer.OnChange -= StateHasChanged;
}
}

Les composants précédents implémentent IDisposable, et les délégués OnChange sont


désinscrits dans les méthodes Dispose , qui sont appelées par le framework lorsque les
composants sont supprimés. Pour plus d’informations, consultez le cycle de vie des
composants Razor ASP.NET Core.

Autres approches
Lors de l’implémentation d’un stockage d’état personnalisé, une approche utile consiste
à adopter des valeurs et des paramètres en cascade :

Pour consommer l’état sur de nombreux composants.


S’il n’y a qu’un seul objet d’état de niveau supérieur à conserver.

Résolution des problèmes


Dans un service de gestion d’état personnalisé, un rappel appelé en dehors du contexte
de synchronisation de Blazor doit envelopper la logique du rappel dans
ComponentBase.InvokeAsync pour la déplacer dans le contexte de synchronisation du
renderer.

Lorsque le service de gestion d’état n’appelle pas StateHasChanged dans le contexte de


synchronisation de Blazor, l’erreur suivante est levée :

System.InvalidOperationException : « Le thread actuel n’est pas associé au


répartiteur. Utilisez InvokeAsync() pour basculer l’exécution vers le répartiteur lors
du déclenchement de l’état du rendu ou du composant. »

Pour obtenir plus d’informations et un exemple de résolution de cette erreur, consultez


Rendu des composants Razor ASP.NET Core.

Ressources supplémentaires
Enregistrer l’état de l’application avant une opération d’authentification (Blazor
WebAssembly)
Gestion de l’état via une API de serveur externe
Appeler une API web à partir d’une application ASP.NET Core Blazor
Sécuriser ASP.NET Core Blazor WebAssembly

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Déboguer des applications ASP.NET
Core
Article • 09/02/2024

Cet article décrit comment déboguer Blazor des applications, y compris le débogage
d’applications Blazor WebAssembly avec des outils de navigateur et un environnement
de développement intégré (IDE).

Vous pouvez déboguer les applications web Blazor dans Visual Studio ou Visual Studio
Code.

Les applications Blazor WebAssembly peuvent être déboguées :

Dans Visual Studio ou Visual Studio Code.


À l’aide des outils de développement de navigateur dans les navigateurs basés sur
Chromium, notamment Microsoft Edge, Google Chrome et Firefox.

Les scénarios disponibles pour le débogage Blazor WebAssembly incluent :

Définissez et supprimez des points d’arrêt.


Exécutez l’application avec prise en charge du débogage dans les IDE.
Pas à pas dans le code.
Reprendre l’exécution du code avec un raccourci clavier dans les IDE.
Dans la fenêtre Locales, observez les valeurs des variables locales.
Consultez la pile d’appels, y compris les chaînes d’appels entre JavaScript et .NET.
Utilisez un serveur de symboles pour le débogage, configuré par les préférences
de Visual Studio.

Les scénarios non pris en charge incluent :

Déboguer dans des scénarios non locaux (par exemple, Sous-système Windows
pour Linux (WSL) ou Visual Studio Codespaces ).
Déboguer dans Firefox à partir de Visual Studio ou Visual Studio Code.

Prérequis
Cette section décrit les prérequis pour le débogage.

Prérequis relatifs au navigateur


Vous devez disposer de la dernière version des navigateurs suivants :
Google Chrome
Microsoft Edge
Firefox (outils de développement du navigateur uniquement)

Assurez-vous que les pare-feu ou les proxys ne bloquent pas la communication avec le
proxy de débogage ( NodeJS processus). Pour plus d’informations, consultez la section
Configuration du pare-feu.

7 Notes

Apple Safari sur macOS n’est actuellement pas pris en charge.

Prérequis relatifs à l’IDE


Vous devez disposer de la dernière version de Visual Studio ou de Visual Studio Code.

Conditions préalables pour Visual Studio Code


Visual Studio Code nécessite le kit de développement C# pour Visual Studio Code
(Prise en main de C# dans VS Code ). Dans la Place de marché des extensions de Visual
Studio Code, filtrez la liste des extensions avec « c# dev kit » pour localiser l’extension :

L’installation du kit de développement C# installe automatiquement les extensions


supplémentaires suivantes :

.NET Install Tool (Outil d’installation .NET)


C#
IntelliCode for C# Dev Kit (Kit de développement IntelliCode pour C#)

Si vous rencontrez des avertissements ou des erreurs, vous pouvez créer un problème
(microsoft/vscode-dotnettoolsréférentiel GitHub) et les décrire.
Conditions préalables à la configuration des applications
L’aide contenue dans cette sous-section s’applique au débogage côté client.

Ouvrez le fichier Properties/launchSettings.json du projet de démarrage. Vérifiez la


présence de la propriété inspectUri suivante dans chaque profil de lancement du
nœud profiles du fichier. Si la propriété suivante n’est pas présente, ajoutez-la à chaque
profil :

JSON

"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-
proxy?browser={browserInspectUri}"

La propriété inspectUri :

Permet à l’IDE de détecter que l’application est une application Blazor.


Indique à l’infrastructure de débogage de script de se connecter au navigateur via
le proxy de débogage de Blazor.

Les valeurs d’espace réservé pour le protocole WebSocket ( wsProtocol ), l’hôte


( url.hostname ), le port ( url.port ) et l’URI de l’inspecteur sur le navigateur lancé
( browserInspectUri ) sont fournies par l’infrastructure.

Packages
Blazor Web Apps : Microsoft.AspNetCore.Components.WebAssembly.Server : référence
un package interne (Microsoft.NETCore.BrowserDebugHost.Transport ) pour les
assemblys qui partagent l’hôte de débogage du navigateur.

Blazor WebAssembly autonome :


Microsoft.AspNetCore.Components.WebAssembly.DevServer : serveur de
développement à utiliser lors de la création d’applications Blazor. Appelle
WebAssemblyNetDebugProxyAppBuilderExtensions.UseWebAssemblyDebugging en
interne pour ajouter un intergiciel (middleware) pour le débogage d’applications Blazor
WebAssembly dans Chromium outils de développement.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .

Déboguer une Blazor Web App dans un IDE


Visual Studio

L’exemple de cette section suppose que vous avez créé une application web Blazor
avec le mode de rendu interactif Auto (Serveur et WebAssembly) et un
emplacement d’interactivité par composant.

1. Ouvrez l’application.
2. Définissez un point d’arrêt sur la ligne currentCount++; dans le Counter
composant ( Pages/Counter.razor ) du projet client ( .Client ).
3. Appuyez sur la touche F5 pour exécuter l’application dans le débogueur.
4. Dans le navigateur, accédez à la page Counter à /counter . Attendez quelques
secondes pour que le proxy de débogage se charge et s’exécute. Sélectionnez
la touche Cliquer sur moi pour atteindre le point d’arrêt.
5. Dans Visual Studio, inspectez la valeur du champ currentCount dans la fenêtre
Locales .
6. Appuyez sur F5 pour poursuivre l’exécution.

Il est également possible que des points d’arrêt soient atteints dans le projet
serveur dans les composants côté serveur rendus de manière statique et interactive.

1. Arrêtez le débogueur.

2. Ajoutez le composant suivant à l’application de serveur. Le composant


applique le mode de rendu Serveur interactif ( InteractiveServer ).

Components/Pages/Counter2.razor :

razor

@page "/counter-2"
@rendermode InteractiveServer

<PageTitle>Counter 2</PageTitle>

<h1>Counter 2</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click


me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

3. Définissez un point d'arrêt sur la ligne currentCount++; dans le composant


Counter2 .

4. Appuyez sur la touche F5 pour exécuter l’application dans le débogueur.

5. Dans le navigateur, accédez à la page Counter2 à /counter-2 . Attendez


quelques secondes pour que le proxy de débogage se charge et s’exécute.
Sélectionnez la touche Cliquer sur moi pour atteindre le point d’arrêt.

6. Appuyez sur F5 pour poursuivre l’exécution.

Les points d’arrêt ne sont pas atteints au démarrage de l’application avant


l’exécution du proxy de débogage. Cela inclut les points d’arrêt dans le fichier
Program et les points d’arrêt dans les OnInitialized{Async} méthodes de cycle de vie

des composants chargés par la première page demandée à partir de l’application.

Déboguer une application Blazor WebAssembly


dans un IDE
Visual Studio

1. Ouvrez l’application.
2. Définissez un point d'arrêt sur la ligne currentCount++; dans le Counter
composant ( Pages/Counter.razor ).
3. Appuyez sur la touche F5 pour exécuter l’application dans le débogueur.
4. Dans le navigateur, accédez à la page Counter à /counter . Attendez quelques
secondes pour que le proxy de débogage se charge et s’exécute. Sélectionnez
la touche Cliquer sur moi pour atteindre le point d’arrêt.
5. Dans Visual Studio, inspectez la valeur du champ currentCount dans la fenêtre
Locales .
6. Appuyez sur F5 pour poursuivre l’exécution.
Les points d’arrêt ne sont pas atteints au démarrage de l’application avant
l’exécution du proxy de débogage. Cela inclut les points d’arrêt dans le fichier
Program et les points d’arrêt dans les OnInitialized{Async} méthodes de cycle de vie

des composants chargés par la première page demandée à partir de l’application.

Joindre à une session de débogage Visual


Studio Code existante
Pour joindre une application Blazor en cours d’exécution, ouvrez le fichier
.vscode/launch.json et remplacez l’espace réservé {URL} par l’URL où l’application est

en cours d’exécution :

JSON

{
"name": "Attach and Debug",
"type": "blazorwasm",
"request": "attach",
"url": "{URL}"
}

Options de lancement de Visual Studio Code


Les options de configuration de lancement dans le tableau suivant sont prises en charge
pour le type de débogage blazorwasm ( .vscode/launch.json ).

ノ Agrandir le tableau

Option Description

browser Navigateur à lancer pour la session de débogage. Définissez edge sur chrome . La valeur
par défaut est edge .

cwd Répertoire de travail sous lequel lancer l’application.

request Utilisez launch pour lancer et attacher une session de débogage à une application
Blazor WebAssembly ou attach pour attacher une session de débogage à une
application déjà en cours d’exécution.

timeout Nombre de millisecondes à attendre pour que la session de débogage soit attachée. La
valeur par défaut est de 30 000 millisecondes (30 secondes).
Option Description

trace Utilisé pour générer des journaux à partir du débogueur JS. Définissez sur true pour
générer des journaux.

url URL à ouvrir dans le navigateur lors du débogage.

webRoot Spécifie le chemin absolu du serveur web. Doit être défini si une application est servie à
partir d’un sous-itinéraire.

Déboguer Blazor WebAssembly avec Google


Chrome ou Microsoft Edge
Les conseils de cette section s’appliquent aux applications de débogage Blazor
WebAssembly dans :

Google Chrome s’exécutant sur Windows ou macOS.


Microsoft Edge s’exécutant sur Windows.

1. Utilisez l’application dans un interpréteur de commandes avec dotnet run .

2. Lancez un navigateur et accédez à l’URL de l’application.

3. Démarrez le débogage à distance en appuyant sur:

Maj + Alt + d sur Windows.


Shift + ⌘ + d sur macOS.

Le navigateur doit être en cours d’exécution avec le débogage à distance activé, ce


qui n’est pas la valeur par défaut. Si le débogage à distance est désactivé, une
page d’erreur Impossible de trouver l’onglet de navigateur pouvant être
déboguée est affichée avec des instructions pour lancer le navigateur avec le port
de débogage ouvert. Suivez les instructions de votre navigateur.

Une fois que vous avez suivi les instructions pour activer le débogage à distance,
l’application s’ouvre dans une nouvelle fenêtre de navigateur. Démarrez le
débogage à distance en appuyant sur la combinaison de touches d’accès rapide
dans la nouvelle fenêtre du navigateur :

Maj + Alt + d sur Windows.


Shift + ⌘ + d sur macOS.

L’onglet du navigateur des outils de développement s’ouvre dans une nouvelle


fenêtre et affiche une image fantôme de l’application.
7 Notes

Si vous avez suivi les instructions pour ouvrir un nouvel onglet de navigateur
avec le débogage à distance activé, vous pouvez fermer la fenêtre du
navigateur d’origine pour ne garder que la deuxième fenêtre ouverte, celle-ci
exécutant l’application sous le premier onglet et le débogueur sous le
deuxième.

4. Après un instant, l’onglet Sources affiche une liste des assemblys .NET et des
pages de l’application.

5. Ouvrez le nœud file:// . Dans le code du composant (fichiers .razor ) et les


fichiers de code C# ( .cs ), les points d’arrêt que vous définissez sont atteints
lorsque le code s’exécute sous l’onglet du navigateur de l’application (l’onglet
initial ouvert après le démarrage du débogage à distance). Une fois qu’un point
d’arrêt est atteint, exécutez pas à pas le code ( F10 ) ou reprenez normalement
l’exécution du code sous l’onglet de débogage ( F8 ).

Pour le débogage de navigateurs basés sur Chromium, Blazor fournit un proxy de


débogage qui implémente le protocole Chrome DevTools et augmente le protocole
avec .NET Informations spécifiques. Lorsque vous appuyez sur le raccourci clavier du
débogage, Blazor pointe Chrome DevTools vers le proxy. Le proxy se connecte à la
fenêtre de navigateur que vous souhaitez déboguer (d’où la nécessité d’activer le
débogage à distance).

Déboguer une application Blazor WebAssembly


avec Firefox
Les conseils de cette section s’appliquent aux applications de débogage Blazor
WebAssembly dans Firefox sur Windows.

Le débogage d’une application Blazor WebAssembly avec Firefox nécessite la


configuration du navigateur pour le débogage à distance et la connexion au navigateur
à l’aide des outils de développement de celui-ci via le proxy de débogage WebAssembly
.NET.

7 Notes
Le débogage dans Firefox à partir de Visual Studio n’est pas pris en charge pour le
moment.

Voici comment déboguer une application Blazor WebAssembly dans Firefox pendant le
développement :

1. Configurez Firefox :

Ouvrir about:config dans un nouvel onglet de navigateur. Lisez et ignorez


l’avertissement qui s’affiche.
Activez devtools.debugger.remote-enabled en définissant sa valeur sur True .
Activez devtools.chrome.enabled en définissant sa valeur sur True .
Désactivez devtools.debugger.prompt-connection en définissant sa valeur sur
False .

2. Fermez toutes les instances Firefox.


3. Utilisez l’application dans un interpréteur de commandes avec dotnet run .
4. Relancez le navigateur Firefox et accédez à l’application.
5. Ouvrir about:debugging dans un nouvel onglet de navigateur. Laissez cet onglet
ouvert.
6. Revenez à l’onglet où l’application est en cours d’exécution. Démarrez le débogage
à distance en appuyant sur Maj + Alt + d .
7. Dans l’onglet Debugger , ouvrez le fichier source de l’application que vous souhaitez
déboguer sous le nœud file:// et définissez un point d’arrêt. Par exemple,
définissez un point d’arrêt sur la ligne currentCount++; dans la méthode
IncrementCount du composant Counter ( Pages/Counter.razor ).

8. Accédez à la page du composant Counter ( /counter ) sous l’onglet du navigateur


de l’application et sélectionnez le bouton compteur pour atteindre le point d’arrêt.
9. Appuyez sur F5 pour poursuivre l’exécution sous l’onglet de débogage.

Arrêter sur les exceptions non gérées


Par défaut, le débogueur ne s’arrête pas sur les exceptions non gérées, car Blazor
intercepte les exceptions qui ne sont pas gérées par le code du développeur.

Pour arrêter sur les exceptions non gérées :

Ouvrez les paramètres d’exception du débogueur


(Déboguer>Fenêtres>Paramètres d’exception) dans Visual Studio.
Définissez les paramètres Exceptions JavaScript suivants :
Toutes les exceptions
Exceptions non interceptées

Cartes sources de navigateur


Les cartes sources du navigateur permettent au navigateur de mapper les fichiers
compilés à leurs fichiers sources d’origine et sont couramment utilisées pour le
débogage côté client. Toutefois, Blazor ne mappe pas C# directement à
JavaScript/WASM. Au lieu de cela, Blazor effectue l’interprétation de l’IL dans le
navigateur, de sorte que les mappages sources ne sont pas pertinents.

Configuration du pare-feu
Si un pare-feu bloque la communication avec le proxy de débogage, créez une règle
d’exception de pare-feu qui autorise la communication entre le navigateur et le
processus NodeJS .

2 Avertissement

La modification d’une configuration de pare-feu doit être effectuée avec précaution


pour éviter de créer des failles de sécurité. Appliquez soigneusement les conseils de
sécurité, suivez les meilleures pratiques de sécurité et respectez les avertissements
émis par le fabricant du pare-feu.

Autorisation d’une communication ouverte avec le processus NodeJS :

Ouvre le serveur Node à n’importe quelle connexion, en fonction des


fonctionnalités et de la configuration du pare-feu.
Peut être risqué en fonction de votre réseau.
Est recommandé uniquement sur les ordinateurs de développeur.

Si possible, autorisez uniquement la communication ouverte avec le processus


NodeJS sur des réseaux approuvés ou privés.

Pour obtenir des conseils sur la configuration du Pare-feu Windows, consultez Créer un
programme de trafic entrant ou une règle de service. Pour plus d’informations, consultez
Windows Defender Firewall with Advanced Security et les articles connexes dans
l’ensemble de documentation pare-feu Windows.

Résoudre les problèmes


Si vous rencontrez des erreurs, les conseils suivants peuvent vous aider :

Supprimer les points d'arrêt:


Google Chrome: sous l’onglet Débogueur, ouvrez les outils de développement
dans votre navigateur. Dans la console, exécutez localStorage.clear() pour
supprimer les points d’arrêt.
Microsoft Edge : sous l’onglet Application, ouvrez Stockage local. Cliquez avec
le bouton droit sur le site et sélectionnez Effacer.
Vérifiez que vous avez installé et approuvé le certificat de développement HTTPS
ASP.NET Core. Pour plus d’informations, consultez Appliquer HTTPS dans
ASP.NET Core.
Visual Studio nécessite l’option Activer le débogage JavaScript pour ASP.NET
(Chrome et Edge) dans Outils>Options>Débogage>général. Il s’agit du
paramètre par défaut pour Visual Studio. Si le débogage ne fonctionne pas, vérifiez
que l’option est sélectionnée.
Si votre environnement utilise un proxy HTTP, assurez-vous que localhost est
inclus dans les paramètres de contournement du proxy. Pour ce faire, définissez la
variable d’environnement NO_PROXY dans :
Fichier launchSettings.json du projet.
Au niveau des variables d’environnement utilisateur ou système pour qu’il
s’applique à toutes les applications. Lorsque vous utilisez une variable
d’environnement, redémarrez Visual Studio pour que la modification prenne
effet.
Assurez-vous que les pare-feu ou les proxys ne bloquent pas la communication
avec le proxy de débogage ( NodeJS processus). Pour plus d’informations, consultez
la section Configuration du pare-feu.

Points d’arrêt dans OnInitialized{Async} non atteints


Le Blazor lancement du proxy de débogage de l’infrastructure n’a pas lieu dès le
démarrage de l’application, de sorte que les points d’arrêt dans les méthodes de
OnInitialized{Async} cycle de vie peuvent ne pas être atteints. Nous vous recommandons
d’ajouter un délai au début du corps de la méthode pour donner au proxy de débogage
un certain temps de lancement avant que le point d’arrêt ne soit atteint. Vous pouvez
inclure le délai basé sur une if directive du compilateur pour vous assurer que le délai
n’est pas présent pour une version de mise en production de l’application.

OnInitialized:

C#
protected override void OnInitialized()
{
#if DEBUG
Thread.Sleep(10000);
#endif

...
}

OnInitializedAsync :

C#

protected override async Task OnInitializedAsync()


{
#if DEBUG
await Task.Delay(10000);
#endif

...
}

Délai d’expiration de Visual Studio (Windows)


Si Visual Studio lève une exception que l’adaptateur de débogage n’a pas pu lancer,
indiquant que le délai d’expiration a été atteint, vous pouvez ajuster le délai d’expiration
avec un paramètre de Registre :

Console

VsRegEdit.exe set "<VSInstallFolder>" HKCU JSDebugger\Options\Debugging


"BlazorTimeoutInMilliseconds" dword {TIMEOUT}

L’espace réservé {TIMEOUT} dans la commande précédente est en millisecondes. Par


exemple, une minute est affectée en tant que 60000 .

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus
d’informations, consultez notre  Ouvrir un problème de
guide du contributeur. documentation

 Indiquer des commentaires sur


le produit
Charger des assemblys en mode différé
dans Blazor WebAssembly ASP.NET Core
Article • 09/02/2024

Les performances de démarrage de l’application Blazor WebAssembly peuvent être


améliorées en chargeant les assemblys d’application créés par le développeur
uniquement s’ils sont requis. Cela s’appelle le chargement différé.

Les premières sections de cet article portent sur la configuration des applications. Pour
une démonstration pratique, consultez la section Exemple complet à la fin de cet article.

Cet article s’applique uniquement aux applications Blazor WebAssembly. Le chargement


différé d’assembly ne bénéficie pas aux applications côté serveur, car les applications
rendues par le serveur ne téléchargent pas d’assemblys sur le client.

Le chargement différé ne doit pas être utilisé pour les principaux assemblys de CLR, qui
peuvent être supprimés lors de la publication et indisponibles sur le client lorsque
l’application se charge.

Espace réservé d’extension de fichier ( {FILE


EXTENSION} ) pour les fichiers d’assembly
Les fichiers d’assembly utilisent le format d’empaquetage Webcil pour les assemblies
.NET avec une extension de fichier .wasm .

Tout au long de l’article, l’espace réservé {FILE EXTENSION} représente « wasm ».

Configuration du fichier projet


Marquez les assemblys pour le chargement différé dans le fichier projet ( .csproj ) de
l’application en utilisant l’élément BlazorWebAssemblyLazyLoad . Utilisez le nom de
l’assembly avec l’extension de fichier. Le framework Blazor empêche le chargement de
l’assembly au lancement de l’application.

XML

<ItemGroup>
<BlazorWebAssemblyLazyLoad Include="{ASSEMBLY NAME}.{FILE EXTENSION}" />
</ItemGroup>
L’espace réservé {ASSEMBLY NAME} est le nom de l’assembly et l’espace réservé {FILE
EXTENSION} est l’extension de fichier. L’extension de fichier est obligatoire.

Incluez un élément BlazorWebAssemblyLazyLoad pour chaque assembly. Si un assembly


possède des dépendances, incluez une entrée BlazorWebAssemblyLazyLoad pour chaque
dépendance.

Configuration du composant Router


L’infrastructure Blazor enregistre automatiquement un service singleton pour le
chargement différé des assemblys dans les applications Blazor WebAssembly coté client,
LazyAssemblyLoader. La méthode LazyAssemblyLoader.LoadAssembliesAsync :

Uses l’interopérabilité JS pour récupérer (fetch) les assemblys via un appel réseau.
Charge les assemblys dans le runtime qui s’exécutent sur WebAssembly dans le
navigateur.

Le composant Router de Blazor désigne les assemblys dans lesquels Blazor recherche
des composants routables. Il est également responsable du rendu du composant pour la
route empruntée par l’utilisateur. La méthode OnNavigateAsync du composant Router
est utilisée conjointement avec le chargement différé pour charger les assemblys
appropriés pour les points de terminaison demandés par un utilisateur.

La logique est implémentée dans OnNavigateAsync pour déterminer quels assemblys


charger avec LazyAssemblyLoader. Les options de structuration de la logique sont les
suivantes :

Vérifications conditionnelles dans la méthode OnNavigateAsync.


Table de recherche qui mappe les routes aux noms d’assemblys, qui est soit
injectée dans le composant soit implémentée dans le bloc @code.

Dans l’exemple suivant :

L’espace de noms pour Microsoft.AspNetCore.Components.WebAssembly.Services


est spécifié.
Le service LazyAssemblyLoader est injecté ( AssemblyLoader ).
L’espace réservé {PATH} est le chemin où la liste d’assembly doit se charger.
L’exemple utilise une vérification conditionnelle qui contrôle qu’un seul chemin
charge un ensemble unique d’assemblys.
L’espace réservé {LIST OF ASSEMBLIES} est la liste séparée par des virgules des
chaînes de nom de fichier d’assembly, avec leurs extensions de fichier (par
exemple, "Assembly1.{FILE EXTENSION}", "Assembly2.{FILE EXTENSION}" ).
App.razor :

razor

@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject LazyAssemblyLoader AssemblyLoader
@inject ILogger<App> Logger

<Router AppAssembly="typeof(App).Assembly"
OnNavigateAsync="OnNavigateAsync">
...
</Router>

@code {
private async Task OnNavigateAsync(NavigationContext args)
{
try
{
if (args.Path == "{PATH}")
{
var assemblies = await
AssemblyLoader.LoadAssembliesAsync(
new[] { {LIST OF ASSEMBLIES} });
}
}
catch (Exception ex)
{
Logger.LogError("Error: {Message}", ex.Message);
}
}
}

7 Notes

L’exemple précédent ne présente pas le contenu de la balise Razor ( ... ) du


composant Router. Pour une démonstration avec le code complet, consultez la
section Exemple complet de cet article.

Assemblys qui incluent des composants


routables
Quand la liste d’assemblys inclut des composants routables, la liste d’assemblys d’un
chemin donné est transmise à la collection AdditionalAssemblies du composant Router.

Dans l’exemple suivant :


List<Assembly> dans lazyLoadedAssemblies transmet la liste d’assemblys à
AdditionalAssemblies. Le framework recherche des routes dans les assemblys et
met à jour la collection de routes s’il en trouve de nouvelles. Pour accéder au type
Assembly, l’espace de noms pour System.Reflection est inclus en haut du fichier
App.razor .

L’espace réservé {PATH} est le chemin où la liste d’assembly doit se charger.


L’exemple utilise une vérification conditionnelle qui contrôle qu’un seul chemin
charge un ensemble unique d’assemblys.
L’espace réservé {LIST OF ASSEMBLIES} est la liste séparée par des virgules des
chaînes de nom de fichier d’assembly, avec leurs extensions de fichier (par
exemple, "Assembly1.{FILE EXTENSION}", "Assembly2.{FILE EXTENSION}" ).

App.razor :

razor

@using System.Reflection
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject ILogger<App> Logger
@inject LazyAssemblyLoader AssemblyLoader

<Router AppAssembly="typeof(App).Assembly"
AdditionalAssemblies="lazyLoadedAssemblies"
OnNavigateAsync="OnNavigateAsync">
...
</Router>

@code {
private List<Assembly> lazyLoadedAssemblies = new();

private async Task OnNavigateAsync(NavigationContext args)


{
try
{
if (args.Path == "{PATH}")
{
var assemblies = await
AssemblyLoader.LoadAssembliesAsync(
new[] { {LIST OF ASSEMBLIES} });
lazyLoadedAssemblies.AddRange(assemblies);
}
}
catch (Exception ex)
{
Logger.LogError("Error: {Message}", ex.Message);
}
}
}

7 Notes

L’exemple précédent ne présente pas le contenu de la balise Razor ( ... ) du


composant Router. Pour une démonstration avec le code complet, consultez la
section Exemple complet de cet article.

Pour plus d’informations, consultez Routage et navigation ASP.NET Core Blazor.

Interaction utilisateur avec du contenu


<Navigating>
Pendant le chargement d’assemblys, ce qui peut prendre plusieurs secondes, le
composant Router peut indiquer à l’utilisateur qu’une transition de page se produit avec
la propriété Navigating du routeur.

Pour plus d’informations, consultez Routage et navigation ASP.NET Core Blazor.

Gérer les annulations dans OnNavigateAsync


L’objet NavigationContext passé au rappel de OnNavigateAsync contient un
CancellationToken qui est défini quand un nouvel événement de navigation se produit.
Le rappel OnNavigateAsync doit se lever une exception quand le jeton d’annulation est
défini pour éviter de continuer à exécuter le rappel OnNavigateAsync dans une
navigation obsolète.

Pour plus d’informations, consultez Routage et navigation ASP.NET Core Blazor.

Événements OnNavigateAsync et fichiers


d’assembly renommés
Le chargeur de ressources se fie aux noms d’assembly définis dans le fichier
blazor.boot.json . Si des assemblys sont renommés, les noms d’assembly utilisés dans

un rappel OnNavigateAsync et les noms d’assembly présents dans le fichier


blazor.boot.json ne sont pas synchronisés.

Pour rectifier cela :


Vérifiez si l’application s’exécute dans l’environnement Production au moment de
déterminer les noms d’assembly à utiliser.
Stockez les noms d’assembly renommés dans un fichier distinct et lisez dans ce
fichier pour déterminer le nom d’assembly à utiliser avec le service
LazyAssemblyLoader et le rappel OnNavigateAsync.

Exemple complet
La démonstration de cette section effectue les opérations suivantes :

Elle crée un assembly de contrôles de robot ( GrantImaharaRobotControls.{FILE


EXTENSION} ) en tant que bibliothèque de classes Razor (RCL) qui inclut un

composant Robot ( Robot.razor avec un modèle de route /robot ).


Elle charge l’assembly de la bibliothèque RCL en mode différé pour assurer le
rendu de son composant Robot quand l’URL /robot est demandée par l’utilisateur.

Créer une application autonome Blazor WebAssembly pour illustrer le chargement


différé de l’assembly d’une bibliothèque de classes Razor. Nommez le projet
LazyLoadTest .

Ajouter un projet de bibliothèque de classes ASP.NET Core à la solution :

Visual Studio : faites un clic droit sur le fichier solution dans l’Explorateur de
solutions et sélectionnez Ajouter>Nouveau projet. Dans la boîte de dialogue des
nouveaux types de projet, sélectionnez Razor Bibliothèque de classes. Nommez le
projet GrantImaharaRobotControls . Ne cochez pas la case Prendre en charge les
pages et les vues.
Visual Studio Code/Interface CLI .NET : Exécutez dotnet new razorclasslib -o
GrantImaharaRobotControls depuis une invite de commandes. L’option -o|--output

crée un dossier et nomme le projet GrantImaharaRobotControls .

L’exemple de composant présenté plus loin dans cette section utilise un formulaire
Blazor. Dans le projet RCL, ajoutez le package Microsoft.AspNetCore.Components.Forms
au projet.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez
les articles figurant sous Installer et gérer des packages dans Flux de travail de la
consommation des packages (documentation NuGet). Vérifiez les versions du
package sur NuGet.org .
Créez une classe HandGesture dans la RCL avec une méthode ThumbUp qui est censée
faire lever le pouce à un robot. La méthode accepte un argument pour l’axe, Left ou
Right , en tant que enum. La méthode retourne true en cas de réussite.

HandGesture.cs :

C#

using Microsoft.Extensions.Logging;

namespace GrantImaharaRobotControls;

public static class HandGesture


{
public static bool ThumbUp(Axis axis, ILogger logger)
{
logger.LogInformation("Thumb up gesture. Axis: {Axis}", axis);

// Code to make robot perform gesture

return true;
}
}

public enum Axis { Left, Right }

Ajoutez le composant suivant à la racine du projet RCL. Le composant permet à


l’utilisateur d’envoyer une demande de lever de pouce gauche ou droit.

Robot.razor :

razor

@page "/robot"
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.Extensions.Logging
@inject ILogger<Robot> Logger

<h1>Robot</h1>

<EditForm Model="robotModel" OnValidSubmit="HandleValidSubmit">


<InputRadioGroup @bind-Value="robotModel.AxisSelection">
@foreach (var entry in (Axis[])Enum
.GetValues(typeof(Axis)))
{
<InputRadio Value="entry" />
<text>&nbsp;</text>@entry<br>
}
</InputRadioGroup>
<button type="submit">Submit</button>
</EditForm>

<p>
@message
</p>

@code {
private RobotModel robotModel = new() { AxisSelection = Axis.Left };
private string? message;

private void HandleValidSubmit()


{
Logger.LogInformation("HandleValidSubmit called");

var result = HandGesture.ThumbUp(robotModel.AxisSelection, Logger);

message = $"ThumbUp returned {result} at {DateTime.Now}.";


}

public class RobotModel


{
public Axis AxisSelection { get; set; }
}
}

Dans le projet LazyLoadTest , créez une référence de projet pour la RCL


GrantImaharaRobotControls :

Visual Studio : faites un clic droit sur le projet LazyLoadTest , puis sélectionnez
Ajouter>Référence de projet pour ajouter une référence de projet pour la RCL
GrantImaharaRobotControls .

Visual Studio Code/Interface CLI .NET : Exécutez dotnet add reference {PATH} dans
un interpréteur de commandes depuis le dossier du projet. L’espace réservé
{PATH} est le chemin du projet RCL.

Spécifiez l’assembly de la RCL pour le chargement différé dans le fichier projet ( .csproj )
de l’application LazyLoadTest :

XML

<ItemGroup>
<BlazorWebAssemblyLazyLoad Include="GrantImaharaRobotControls.{FILE
EXTENSION}" />
</ItemGroup>

Le composant suivant Router illustre le chargement de l’assembly


GrantImaharaRobotControls.{FILE EXTENSION} quand l’utilisateur accède à /robot .
Remplacez le composant par défaut App de l’application par le composant App suivant.

Pendant les transitions de page, un message stylise est présenté à l’utilisateur avec
l’élément <Navigating> . Pour plus d’informations, consultez la section Interaction
utilisateur avec le contenu <Navigating>.

L’assembly est affecté à AdditionalAssemblies, ce qui amène le routeur à rechercher des


composants routables dans l’assembly, où il trouve le composant Robot . La route du
composant Robot est ajoutée à la collection de routes de l’application. Pour plus
d’informations, consultez l’article Routage et navigation Blazor ASP.NET Core et la
section Assemblys qui incluent des composants routables de cet article.

App.razor :

razor

@using System.Reflection
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject ILogger<App> Logger
@inject LazyAssemblyLoader AssemblyLoader

<Router AppAssembly="typeof(App).Assembly"
AdditionalAssemblies="lazyLoadedAssemblies"
OnNavigateAsync="OnNavigateAsync">
<Navigating>
<div style="padding:20px;background-color:blue;color:white">
<p>Loading the requested page&hellip;</p>
</div>
</Navigating>
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)"
/>
</Found>
<NotFound>
<LayoutView Layout="typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

@code {
private List<Assembly> lazyLoadedAssemblies = new();

private async Task OnNavigateAsync(NavigationContext args)


{
try
{
if (args.Path == "robot")
{
var assemblies = await AssemblyLoader.LoadAssembliesAsync(
new[] { "GrantImaharaRobotControls.{FILE EXTENSION}" });
lazyLoadedAssemblies.AddRange(assemblies);
}
}
catch (Exception ex)
{
Logger.LogError("Error: {Message}", ex.Message);
}
}
}

Générez et exécutez l'application.

Si le composant Robot de la RCL est demandé au niveau /robot , l’assembly


GrantImaharaRobotControls.{FILE EXTENSION} est chargé et le composant Robot est

rendu. Vous pouvez inspecter le chargement de l’assembly dans l’onglet Réseau des
outils pour développeurs du navigateur.

Résoudre les problèmes


Si un rendu inattendu se produit, tel que le rendu d’un composant d’une
navigation précédente, vérifiez que le code lève une exception si le jeton
d’annulation est défini.
Si les assemblys configurés pour le chargement différé se chargent de manière
inattendue au démarrage de l’application, vérifiez que l’assembly est marqué pour
le chargement différé dans le fichier projet.

Ressources supplémentaires
Gérer les événements de navigation asynchrones avec OnNavigateAsync
Meilleures pratiques d’ASP.NET Core Blazor en matière de performances

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
dépendances natives ASP.NET Core
Blazor WebAssembly
Article • 16/02/2024

Les applications Blazor WebAssembly peuvent utiliser des dépendances natives créées
pour s’exécuter sur WebAssembly. Vous pouvez lier statiquement des dépendances
natives au runtime WebAssembly .NET à l’aide des outils de génération WebAssembly
.NET, les mêmes outils que ceux utilisés pour compiler une application Blazor à
WebAssembly et pour lier à nouveau le runtime pour supprimer les fonctionnalités
inutilisées.

Cet article s’applique uniquement à Blazor WebAssembly.

.NET WebAssembly Build Tools


Les outils de génération .NET WebAssembly sont basés sur Emscripten , une chaîne
d’outils de compilateur pour la plateforme web. Pour plus d’informations sur les outils
de génération, y compris l’installation, consultez Outils pour ASP.NET Core Blazor.

Ajoutez des dépendances natives à une application Blazor WebAssembly en ajoutant


NativeFileReference éléments dans le fichier projet de l’application. Lorsque le projet
est généré, chaque NativeFileReference est passée à Emscripten par les outils de
génération .NET WebAssembly afin qu’ils soient compilés et liés au runtime. Ensuite,
p/invoke dans le code natif à partir du code .NET de l’application.

En règle générale, tout code natif portable peut être utilisé comme dépendance native
avec Blazor WebAssembly. Vous pouvez ajouter des dépendances natives au code
C/C++ ou au code précédemment compilé à l’aide d’Emscripten :

Fichiers objet ( .o )
Archiver des fichiers ( .a )
Bitcode ( .bc )
Modules WebAssembly autonomes ( .wasm )

Les dépendances prédéfinies doivent généralement être générées à l’aide de la même


version d’Emscripten que celle utilisée pour générer le runtime WebAssembly .NET.

7 Notes
Pour plus d’informations sur les propriétés et les cibles MSBuild
Mono /WebAssembly, consultez WasmApp.targets (dépôt GitHub
dotnet/runtime) . La documentation officielle pour les propriétés MSBuild
courantes est planifiée selon la page Document blazor msbuild configuration
options dotnet/docs#27395 (Documenter les options de configuration msbuild
blazor).

Utiliser du code natif


Ajoutez une fonction C native simple à une application Blazor WebAssembly :

1. Créez un projet Blazor WebAssembly.

2. Ajoutez un fichier Test.c au projet.

3. Ajoutez une fonction C pour l’informatique des factorials.

Test.c :

int fact(int n)
{
if (n == 0) return 1;
return n * fact(n - 1);
}

4. Ajoutez un NativeFileReference pour Test.c dans le fichier projet de l’application


:

XML

<ItemGroup>
<NativeFileReference Include="Test.c" />
</ItemGroup>

5. Dans un composant Razor, ajoutez un DllImportAttribute pour la fonction fact


dans la bibliothèque de Test générée et appelez la méthode fact à partir du code
.NET dans le composant.

Pages/NativeCTest.razor :

razor
@page "/native-c-test"
@using System.Runtime.InteropServices

<PageTitle>Native C</PageTitle>

<h1>Native C Test</h1>

<p>
@@fact(3) result: @fact(3)
</p>

@code {
[DllImport("Test")]
static extern int fact(int n);
}

Lorsque vous générez l’application avec les outils de génération WebAssembly .NET
installés, le code C natif est compilé et lié au runtime WebAssembly .NET ( dotnet.wasm ).
Une fois l’application générée, exécutez l’application pour voir la valeur factorielle
rendue.

Rappels de méthode managée C++


Étiquetez les méthodes managées qui sont passées à C++ avec l’attribut
[UnmanagedCallersOnly] .

La méthode marquée avec l’attribut [UnmanagedCallersOnly] doit être static . Pour


appeler une méthode d’instance dans un composant Razor, passez un GCHandle pour
l’instance en C++, puis passez-la en mode natif. Vous pouvez également utiliser une
autre méthode pour identifier le instance du composant.

La méthode marquée avec [DllImport] doit utiliser un pointeur de fonction C# 9.0


plutôt qu’un type délégué pour l’argument de rappel.

7 Notes

Pour les types de pointeur de fonction C# dans les méthodes [DllImport] , utilisez
IntPtr dans la signature de méthode côté managé au lieu de delegate

*unmanaged<int, void> . Pour plus d’informations, consultez Rappel [WASM] du

code natif vers .NET : l’analyse des types de pointeurs de fonction dans les
signatures n’est pas prise en charge (dotnet/runtime #56145) .
Packager des dépendances natives dans un
package NuGet
Les packages NuGet peuvent contenir des dépendances natives à utiliser sur
WebAssembly. Ces bibliothèques et leurs fonctionnalités natives sont ensuite
disponibles pour n’importe quelle application Blazor WebAssembly. Les fichiers des
dépendances natives doivent être générés pour WebAssembly et empaquetés dans le
browser-wasm dossier spécifique à l’architecture. Les dépendances spécifiques à

WebAssembly ne sont pas référencées automatiquement et doivent être référencées


manuellement en tant que NativeFileReference s. Les auteurs de package peuvent
choisir d’ajouter les références natives en incluant un fichier .props dans le package
avec les références.

Utilisation de l’exemple de bibliothèque


SkiaSharp
SkiaSharp est une bibliothèque graphique 2D multiplateforme pour .NET basée sur la
bibliothèque graphique Skia native avec prise en charge de Blazor WebAssembly.

Pour utiliser SkiaSharp dans une application Blazor WebAssembly :

1. Ajoutez une référence de package au package SkiaSharp.Views.Blazor dans un


projet Blazor WebAssembly. Utilisez le processus de Visual Studio pour ajouter des
packages à une application (Gérer les packages NuGet avec inclure la préversion
sélectionnée) ou exécutez la commande dotnet add package dans un interpréteur
de commandes :

CLI .NET

dotnet add package –-prerelease SkiaSharp.Views.Blazor

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET,
consultez les articles figurant sous Installer et gérer des packages dans Flux de
travail de la consommation des packages (documentation NuGet). Vérifiez
les versions du package sur NuGet.org .

2. Ajoutez un composant SKCanvasView à l’application avec les éléments suivants :


espaces de noms SkiaSharp et SkiaSharp.Views.Blazor .
Logique à dessiner dans le composant Vue de canevas SkiaSharp
( SKCanvasView ).

Pages/NativeDependencyExample.razor :

razor

@page "/native-dependency-example"
@using SkiaSharp
@using SkiaSharp.Views.Blazor

<PageTitle>Native dependency</PageTitle>

<h1>Native dependency example with SkiaSharp</h1>

<SKCanvasView OnPaintSurface="OnPaintSurface" />

@code {
private void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;

canvas.Clear(SKColors.White);

using var paint = new SKPaint


{
Color = SKColors.Black,
IsAntialias = true,
TextSize = 24
};

canvas.DrawText("SkiaSharp", 0, 24, paint);


}
}

3. Générez l’application, ce qui peut prendre plusieurs minutes. Exécutez l’application


et accédez au composant NativeDependencyExample à /native-dependency-example .

Ressources supplémentaires
.NET WebAssembly Build Tools

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Bonnes pratiques sur le plan des
performances de Blazor ASP.NET Core
Article • 06/12/2023

Blazor est optimisé pour la haute performance dans la plupart des scénarios d’interface
utilisateur d’application. Cependant, pour atteindre le meilleur niveau de performance,
les développeurs doivent adopter les modèles et les fonctionnalités.

7 Notes

Les exemples de code de cet article adoptent les types référence null (NRT) et
l'analyse statique de l'état null du compilateur .NET, qui sont pris en charge dans
ASP.NET Core 6.0 ou une version ultérieure.

Optimiser la vitesse de rendu


Optimisez la vitesse de rendu de façon à réduire la charge de travail de rendu et à
améliorer la réactivité de l’interface utilisateur, ce qui peut se traduire par une vitesse de
rendu de l’interface utilisateur multipliée par dix ou plus.

Éviter le rendu inutile de sous-arborescences de


composants
Vous pouvez peut-être éliminer une grande partie du coût de rendu d’un composant
parent en ignorant le renouvellement du rendu des sous-arborescences de composants
enfants qui intervient lorsqu’un événement se produit. Vous devez uniquement vous
soucier des sous-arborescences dont le renouvellement du rendu est particulièrement
onéreux et provoque le ralentissement de l’interface utilisateur.

Au moment de l’exécution, les composants sont hiérarchisés. Un composant racine (le


premier composant chargé) possède des composants enfants. De même, les enfants de
la racine présentent leurs propres composants enfants, et ainsi de suite. Quand un
événement se produit, par exemple un utilisateur qui sélectionne un bouton, le
processus suivant détermine les composants qui doivent faire l’objet d’un nouveau
rendu :

1. L’événement est distribué au composant qui a rendu le gestionnaire de


l’événement. Après l’exécution du gestionnaire d’événements, le composant fait
l’objet d’un nouveau rendu.
2. À cette occasion, le composant adresse une nouvelle copie des valeurs de
paramètres à chacun de ses composants enfants.
3. Après avoir reçu un nouvel ensemble de valeurs de paramètres, chaque composant
décide ou pas de renouveler le rendu. Par défaut, les composants le décident si les
valeurs de paramètres ont changé, par exemple dans le cas d’objets mutables.

Les deux dernières étapes de la séquence précédente se poursuivent de manière


récursive en descendant dans la hiérarchie des composants. Dans de nombreux cas, la
sous-arborescence entière fait l’objet d’un nouveau rendu. Les événements ciblant les
composants de haut niveau peuvent être à l’origine d’un nouveau rendu coûteux, car
chaque composant situé en dessous du composant de haut niveau doit faire l’objet d’un
nouveau rendu.

Pour empêcher la récursivité du rendu dans une sous-arborescence déterminée, utilisez


l’une des approches suivantes :

Vérifiez que les paramètres des composants enfants sont de types primitifs
immuables, tels que string , int , bool , DateTime et d’autres types similaires. La
logique intégrée de détection des changements ignore automatiquement les
nouvelles opérations de rendu si les valeurs de paramètres immuables primitifs
n’ont pas changé. Si vous effectuez le rendu d’un composant enfant avec
<Customer CustomerId="@item.CustomerId" /> , sachant que CustomerId est de type

int , le composant Customer ne fait pas l’objet d’un nouveau rendu, sauf si
item.CustomerId change.

Substituer ShouldRender :
Pour accepter des valeurs de paramètres non primitifs, telles que des types de
modèles personnalisés complexes, des rappels d’événements ou des valeurs de
RenderFragment.
Si vous créez un composant d’interface utilisateur uniquement qui ne change
pas après le rendu initial, qu’il y ait ou pas des changements de valeurs de
paramètres.

Dans l’exemple d’outil de recherche de vols aériens suivant, des champs privés sont
utilisés pour suivre les informations nécessaires à la détection des changements.
L’identificateur de vol entrant précédent ( prevInboundFlightId ) et l’identificateur de vol
sortant précédent ( prevOutboundFlightId ) assurent le suivi d’informations pour la
prochaine mise à jour potentielle du composant. Si l’un des identificateurs de vol
change quand les paramètres du composant sont définis dans OnParametersSet, le
composant fait l’objet d’un nouveau rendu, car shouldRender est défini sur true . Si
shouldRender est évalué à false après la vérification des identificateurs de vol, un

nouveau rendu coûteux est évité :

razor

@code {
private int prevInboundFlightId = 0;
private int prevOutboundFlightId = 0;
private bool shouldRender;

[Parameter]
public FlightInfo? InboundFlight { get; set; }

[Parameter]
public FlightInfo? OutboundFlight { get; set; }

protected override void OnParametersSet()


{
shouldRender = InboundFlight?.FlightId != prevInboundFlightId
|| OutboundFlight?.FlightId != prevOutboundFlightId;

prevInboundFlightId = InboundFlight?.FlightId ?? 0;
prevOutboundFlightId = OutboundFlight?.FlightId ?? 0;
}

protected override bool ShouldRender() => shouldRender;


}

Un gestionnaire d’événements peut aussi définir shouldRender sur true . Pour la plupart
des composants, il n’est généralement pas nécessaire de déterminer s’il convient
d’effectuer un nouveau rendu au niveau des gestionnaires d’événements individuels.

Pour plus d’informations, consultez les ressources suivantes :

Cycle de vie des composants Razor ASP.NET Core


ShouldRender
Rendu des composants Razor ASP.NET Core

Virtualisation
Quand le rendu porte sur de grandes quantités d’éléments d’interface utilisateur dans
une boucle, par exemple une liste ou une grille constituée de milliers d’entrées, la
quantité d’opérations de rendu peut entraîner un décalage dans le rendu de l’interface
utilisateur. Sachant que l’utilisateur ne peut voir qu’un petit nombre d’éléments à la fois
sans faire défiler l’écran, il est souvent déraisonnable de passer du temps à effectuer le
rendu d’éléments qui ne sont actuellement pas visibles.
Blazor propose le composant Virtualize<TItem> pour créer l’apparence et les
comportements de défilement d’une liste arbitrairement volumineuse tout en assurant
le rendu des seuls éléments de liste qui se trouvent dans la fenêtre d’affichage active du
défilement. Par exemple, un composant peut afficher une liste de 100 000 entrées, mais
payer uniquement le coût de rendu de 20 éléments visibles.

Pour plus d’informations, consultez Virtualisation des composants ASP.NET Core Razor.

Créer des composants légers et optimisés


La plupart des composants Razor ne demandent pas un effort d’optimisation immense,
car la plupart d’entre eux ne se répètent pas dans l’interface utilisateur et ne font pas
l’objet d’un nouveau rendu à fréquence élevée. Par exemple, il est très probable que les
composants routables avec une directive @page et les composants servant à afficher les
éléments importants de l’interface utilisateur, tels que les boîtes de dialogue ou les
formulaires, apparaissent seulement l’un après l’autre et que leur rendu se renouvelle en
réponse à un geste de l’utilisateur. Ces composants ne créent généralement pas de
charge de travail de rendu intense. Vous pouvez donc utiliser librement n’importe quelle
combinaison de fonctionnalités du framework sans trop vous soucier des performances
de rendu.

Cependant, il peut arriver que les composants soient répétés à grande échelle, ce qui
nuit souvent aux performances de l’interface utilisateur, notamment dans les scénarios
courants suivants :

Formulaires volumineux imbriqués avec des centaines d’éléments individuels, tels


que des entrées ou des étiquettes.
Grilles comportant des centaines de lignes ou des milliers de cellules.
Nuages de points comportant des millions de points de données.

Si vous modélisez chaque élément, cellule ou point de données sous forme d’instance
de composant distincte, il en existe souvent tellement que leurs performances de rendu
deviennent critiques. Cette section fournit des conseils pour alléger ces composants et
permettre ainsi à l’interface utilisateur de rester rapide et réactive.

Éviter la présence de milliers d’instances de composant


Chaque composant est une île distincte qui peut effectuer un rendu indépendamment
de ses parents et enfants. En choisissant la façon dont l’interface utilisateur est divisée
en une hiérarchie de composants, vous contrôlez la granularité du rendu de l’interface
utilisateur. Le niveau de performance qui en résulte peut être bon ou mauvais.
En divisant l’interface utilisateur en différents composants, vous pouvez faire en sorte
que le nouveau rendu porte sur des parties plus petites de l’interface utilisateur lorsque
des événements se produisent. Si vous disposez d’une table constituée d’un grand
nombre de lignes dotées chacune d’un bouton, vous pouvez faire en sorte que nouveau
rendu porte sur une seule une ligne, et non sur la page ou la table entière, en utilisant
un composant enfant. Chaque, chaque composant a besoin d’une surcharge de
mémoire et de processeur supplémentaire pour gérer son état et son cycle de vie de
rendu indépendants.

À l’occasion d’un test effectué par les ingénieurs de l’unité produit ASP.NET Core, une
surcharge de rendu d’environ 0,06 ms par instance de composant a été observée dans
une application Blazor WebAssembly. L’application de test a effectué le rendu d’un
composant simple qui accepte trois paramètres. En interne, la surcharge est en grande
partie due à la récupération de l’état par composant auprès des dictionnaires ainsi qu’à
la transmission et à la réception des paramètres. Par multiplication, vous pouvez
constater que l’ajout de 2 000 instances de composant supplémentaires ajouterait
0,12 seconde au temps de rendu et que les utilisateurs commenceraient à trouver que
l’interface utilisateur est lente.

Il est possible d’alléger les composants pour vous permettre d’en avoir davantage.
Cependant, une technique plus puissante est souvent d’éviter d’avoir autant de
composants à afficher. Les sections suivantes décrivent deux approches que vous
pouvez adopter.

Si vous souhaitez en savoir plus sur la gestion de la mémoire, veuillez consulter la


rubrique Héberger et déployer des applications Blazor ASP.NET Core côté serveur.

Intégrer des composants enfants dans leurs parents

Examinez la partie suivante d’un composant parent qui assure le rendu des composants
enfants dans une boucle :

razor

<div class="chat">
@foreach (var message in messages)
{
<ChatMessageDisplay Message="@message" />
}
</div>

ChatMessageDisplay.razor :
razor

<div class="chat-message">
<span class="author">@Message.Author</span>
<span class="text">@Message.Text</span>
</div>

@code {
[Parameter]
public ChatMessage? Message { get; set; }
}

L’exemple précédent fonctionne bien à condition qu’il n’y ait pas plusieurs milliers de
messages à afficher simultanément. Pour afficher des milliers de messages
simultanément, évitez de factoriser le composant ChatMessageDisplay seul. Au lieu de
cela, intégrez le composant enfant dans le parent. L’approche suivante évite la surcharge
par composant liée au rendu d’un si grand nombre de composants enfants mais fait
perdre la possibilité d’un nouveau rendu du balisage de chaque composant enfant de
manière indépendante :

razor

<div class="chat">
@foreach (var message in messages)
{
<div class="chat-message">
<span class="author">@message.Author</span>
<span class="text">@message.Text</span>
</div>
}
</div>

Définir des éléments RenderFragments réutilisables dans le code

Vous pouvez factoriser des composants enfants dans le seul but de réutiliser la logique
de rendu. Si c’est le cas, vous pouvez créer une logique de rendu réutilisable sans
implémenter de composants supplémentaires. Dans le bloc @code d’un composant,
définissez un RenderFragment. Effectuez le rendu du fragment de n’importe où autant
de fois que nécessaire :

razor

@RenderWelcomeInfo

<p>Render the welcome content a second time:</p>


@RenderWelcomeInfo

@code {
private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!
</p>;
}

Pour rendre le code RenderTreeBuilder réutilisable dans plusieurs composants, déclarez


le RenderFragmentpublic et static :

razor

public static RenderFragment SayHello = @<h1>Hello!</h1>;

SayHello dans l’exemple précédent peut être appelé à partir d’un composant non lié.

Cette technique est utile pour créer des bibliothèques d’extraits de balisage réutilisables
qui s’affichent sans surcharge par composant.

Les délégués RenderFragment peuvent accepter des paramètres. Le composant suivant


transmet le message ( message ) au délégué RenderFragment :

razor

<div class="chat">
@foreach (var message in messages)
{
@ChatMessageDisplay(message)
}
</div>

@code {
private RenderFragment<ChatMessage> ChatMessageDisplay = message =>
@<div class="chat-message">
<span class="author">@message.Author</span>
<span class="text">@message.Text</span>
</div>;
}

L’approche précédente réutilise la logique de rendu sans surcharge par composant.


Cependant, l’approche ne permet pas d’actualiser la sous-arborescence de l’interface
utilisateur de manière indépendante, ni d’ignorer le rendu de la sous-arborescence de
l’interface utilisateur lorsque son parent est rendu, car il n’y a pas de limite de
composants. L’affectation à un délégué RenderFragment est uniquement prise en
charge dans les fichiers de composant Razor ( .razor ), et les rappels d’événements ne
sont pas pris en charge.
Pour un champ, une méthode ou une propriété non statique qui ne peut pas être
référencé par un initialiseur de champ, comme TitleTemplate dans l’exemple suivant,
utilisez une propriété plutôt qu’un champ pour le RenderFragment :

C#

protected RenderFragment DisplayTitle =>


@<div>
@TitleTemplate
</div>;

Ne pas recevoir trop de paramètres


Si un composant se répète extrêmement souvent, par exemple des centaines voire des
milliers de fois, la surcharge liée à la transmission et à la réception de chaque paramètre
augmente.

Il est rare qu’un trop grand nombre de paramètres limite sévèrement le niveau de
performance, mais il peut s’agir d’un facteur. Pour un composant TableCell dont le
rendu se produit 1 000 fois dans une grille, chaque paramètre supplémentaire transmis
au composant peut ajouter environ 15 ms au coût de rendu total. Si chaque cellule
acceptait 10 paramètres, le passage des paramètres prendrait environ 150 ms par
composant pour un coût total de rendu de 150 000 ms (150 secondes) et entraînerait un
décalage de rendu de l’interface utilisateur.

Pour réduire la charge de paramètres, regroupez plusieurs paramètres dans une classe
personnalisée. Par exemple, un composant de cellules de table peut accepter un objet
commun. Dans l’exemple suivant, Data est différent pour chaque cellule, mais Options
est commun à toutes les instances de cellule :

razor

@typeparam TItem

...

@code {
[Parameter]
public TItem? Data { get; set; }

[Parameter]
public GridOptions? Options { get; set; }
}
Cependant, il peut être préférable de ne pas avoir de composant de cellules de table,
comme le montre l’exemple précédent, et de plutôt intégrer sa logique dans le
composant parent.

7 Notes

Quand il existe plusieurs approches pour améliorer le niveau de performance, il est


généralement nécessaire d’effectuer une analyse comparative de ces approches
pour déterminer celle qui offre les meilleurs résultats.

Pour plus d’informations sur les paramètres de type générique ( @typeparam ), consultez
les ressources suivantes :

Informations de référence sur la syntaxe Razor pour ASP.NET Core


Composants Razor ASP.NET Core
Composants basés sur un modèle Blazor ASP.NET Core

Vérifier que les paramètres en cascade sont corrigés

Le composant CascadingValue possède un paramètre IsFixed facultatif :

Si IsFixed a la valeur false (par défaut), chaque destinataire de la valeur en


cascade configure un abonnement pour recevoir des notifications de modification.
Chaque [CascadingParameter] est nettement plus cher qu’un [Parameter]
standard en raison du suivi d’abonnement.
Si IsFixed a la valeur true (par exemple, <CascadingValue Value="@someValue"
IsFixed="true"> ), les destinataires reçoivent la valeur initiale, mais ne configurent

pas un abonnement pour recevoir des mises à jour. Chaque [CascadingParameter]


est léger et pas plus cher qu’un [Parameter] normal.

Le fait de définir IsFixed sur true a pour effet d’améliorer le niveau de performance si
un grand nombre d’autres composants reçoivent la valeur en cascade. Dans la mesure
du possible, définissez IsFixed sur true pour les valeurs en cascade. Vous pouvez
définir IsFixed sur true quand la valeur fournie ne change pas dans le temps.

Quand un composant transmet this en tant que valeur en cascade, IsFixed peut aussi
être défini sur true :

razor

<CascadingValue Value="this" IsFixed="true">


<SomeOtherComponents>
</CascadingValue>

Pour plus d’informations, consultez Valeurs et paramètres en cascade de Blazor ASP.NET


Core.

Éviter la projection d’attributs avec CaptureUnmatchedValues

Les composants peuvent choisir de recevoir des valeurs de paramètres « sans


correspondance » en utilisant l’indicateur CaptureUnmatchedValues :

razor

<div @attributes="OtherAttributes">...</div>

@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? OtherAttributes { get; set; }
}

Cette approche permet de transmettre d’autres attributs arbitraires à l’élément. Or, cette
approche est coûteuse, car le convertisseur doit :

Faire correspondre tous les paramètres fournis au jeu de paramètres connus pour
créer un dictionnaire.
Enregistrer la façon dont plusieurs copies d’un même attribut se remplacent
mutuellement.

Utilisez CaptureUnmatchedValues là où les performances de rendu des composants ne


sont pas critiques, notamment dans le cas des composants qui ne sont pas répétés
fréquemment. Pour les composants dont le rendu s’effectue à grande échelle, par
exemple pour chaque élément d’une liste volumineuse ou des cellules d’une grille,
essayez d’éviter la projection d’attributs.

Pour plus d’informations, consultez ASP.NET Core Blazor paramètres de platissement


d’attributs et arbitraires.

Implémenter SetParametersAsync manuellement

Une source importante de surcharge de rendu par composant est l’écriture des valeurs
de paramètres entrantes dans les propriétés [Parameter] . Le renderer utilise la réflexion
pour écrire les valeurs de paramètres, ce qui peut nuire au niveau de performance à
grande échelle.
Dans certains cas extrêmes, vous pouvez souhaiter éviter la réflexion et implémenter
manuellement votre propre logique de définition de paramètres. Cela peut être
pertinent dans les cas suivants :

Un composant fait l’objet d’un rendu extrêmement fréquent, par exemple quand il
existe des centaines ou des milliers de copies du composant dans l’interface
utilisateur.
Un composant accepte de nombreux paramètres.
Vous constatez que la surcharge liée à la réception de paramètres a un impact
observable sur la réactivité de l’interface utilisateur.

Dans les cas extrêmes, vous pouvez remplacer la méthode SetParametersAsync virtuelle
du composant et implémenter votre propre logique propre au composant. L’exemple
suivant évite délibérément les recherches dans un dictionnaire :

razor

@code {
[Parameter]
public int MessageId { get; set; }

[Parameter]
public string? Text { get; set; }

[Parameter]
public EventCallback<string> TextChanged { get; set; }

[Parameter]
public Theme CurrentTheme { get; set; }

public override Task SetParametersAsync(ParameterView parameters)


{
foreach (var parameter in parameters)
{
switch (parameter.Name)
{
case nameof(MessageId):
MessageId = (int)parameter.Value;
break;
case nameof(Text):
Text = (string)parameter.Value;
break;
case nameof(TextChanged):
TextChanged = (EventCallback<string>)parameter.Value;
break;
case nameof(CurrentTheme):
CurrentTheme = (Theme)parameter.Value;
break;
default:
throw new ArgumentException($"Unknown parameter:
{parameter.Name}");
}
}

return base.SetParametersAsync(ParameterView.Empty);
}
}

Dans le code précédent, le retour de la classe de base SetParametersAsync exécute les


méthodes de cycle de vie normales sans attribuer une nouvelle fois des paramètres.

Comme vous pouvez le constater dans le code précédent, remplacer


SetParametersAsync et fournir une logique personnalisée sont des tâches complexes et
laborieuses. C’est pour cette raison que nous déconseillons généralement d’adopter
cette approche. Dans les cas extrêmes, elle peut améliorer les performances de rendu de
20 à 25 %, mais réservez plutôt cette approche aux scénarios extrêmes cités plus haut
dans cette section.

Ne pas déclencher les événements trop rapidement


Certains événements de navigateur se déclenchent très fréquemment. Par exemple,
onmousemove et onscroll peuvent se déclencher des dizaines voire des centaines de fois

à la seconde. Dans la plupart des cas, vous n’avez pas besoin d’effectuer de mises à jour
fréquentes de l’interface utilisateur. Si les événements sont déclenchés trop rapidement,
vous risquez de nuire à la réactivité de l’interface utilisateur ou de consommer un temps
processeur excessif.

Plutôt que d’utiliser des événements natifs qui se déclenchent rapidement, envisagez
d’utiliser l’interopérabilité JS pour inscrire un rappel qui se déclenche moins
fréquemment. Par exemple, le composant suivant affiche la position de la souris, mais ne
se met à jour qu’une fois toutes les 500 ms au maximum :

razor

@implements IDisposable
@inject IJSRuntime JS

<h1>@message</h1>

<div @ref="mouseMoveElement" style="border:1px dashed red;height:200px;">


Move mouse here
</div>

@code {
private ElementReference mouseMoveElement;
private DotNetObjectReference<MyComponent>? selfReference;
private string message = "Move the mouse in the box";

[JSInvokable]
public void HandleMouseMove(int x, int y)
{
message = $"Mouse move at {x}, {y}";
StateHasChanged();
}

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
selfReference = DotNetObjectReference.Create(this);
var minInterval = 500;

await JS.InvokeVoidAsync("onThrottledMouseMove",
mouseMoveElement, selfReference, minInterval);
}
}

public void Dispose() => selfReference?.Dispose();


}

Le code JavaScript correspondant inscrit l’écouteur d’événements DOM pour le


déplacement de la souris. Dans cet exemple, l’écouteur d’événements utilise la fonction
de throttle Lodash pour limiter le taux d’appels :

HTML

<script
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"
></script>
<script>
function onThrottledMouseMove(elem, component, interval) {
elem.addEventListener('mousemove', _.throttle(e => {
component.invokeMethodAsync('HandleMouseMove', e.offsetX, e.offsetY);
}, interval));
}
</script>

Éviter le renouvellement du rendu après avoir géré des


événements sans changement d’état
Par défaut, les composants héritent de ComponentBase, qui appelle automatiquement
StateHasChanged après l’appel des gestionnaires d’événements du composant. Dans
certains cas, il peut être inutile voire inopportun de déclencher un nouveau rendu après
l’appel d’un gestionnaire d’événements. Par exemple, il peut arriver qu’un gestionnaire
d’événements ne modifie pas l’état du composant. Dans ces scénarios, l’application peut
tirer parti de l’interface IHandleEvent pour contrôler le comportement de la gestion
d’événements de Blazor.

Pour empêcher les nouveaux rendus pour tous les gestionnaires d’événements d’un
composant, implémentez IHandleEvent et fournissez une tâche
IHandleEvent.HandleEventAsync qui appelle le gestionnaire d’événements sans appeler
StateHasChanged.

Dans l’exemple suivant, aucun gestionnaire d’événements ajouté au composant ne


déclenche de nouveau rendu. Ainsi, HandleSelect n’occasionne pas de nouveau rendu
quand il est appelé.

HandleSelect1.razor :

razor

@page "/handle-select-1"
@rendermode InteractiveServer
@using Microsoft.Extensions.Logging
@implements IHandleEvent
@inject ILogger<HandleSelect1> Logger

<p>
Last render DateTime: @dt
</p>

<button @onclick="HandleSelect">
Select me (Avoids Rerender)
</button>

@code {
private DateTime dt = DateTime.Now;

private void HandleSelect()


{
dt = DateTime.Now;

Logger.LogInformation("This event handler doesn't trigger a


rerender.");
}

Task IHandleEvent.HandleEventAsync(
EventCallbackWorkItem callback, object? arg) =>
callback.InvokeAsync(arg);
}

En plus d’empêcher les nouveaux rendus après le déclenchement de gestionnaires


d’événements dans un composant de manière globale, il est possible d’empêcher les
nouveaux rendus après le déclenchement d’un seul gestionnaire d’événements en
utilisant la méthode utilitaire suivante.

Ajoutez la classe EventUtil ci-dessous à une application Blazor. Les actions et fonctions
statiques situées en haut de la classe EventUtil fournissent des gestionnaires qui
couvrent plusieurs combinaisons d’arguments et de types de retour dont se sert Blazor
pendant la gestion des événements.

EventUtil.cs :

C#

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;

public static class EventUtil


{
public static Action AsNonRenderingEventHandler(Action callback)
=> new SyncReceiver(callback).Invoke;
public static Action<TValue> AsNonRenderingEventHandler<TValue>(
Action<TValue> callback)
=> new SyncReceiver<TValue>(callback).Invoke;
public static Func<Task> AsNonRenderingEventHandler(Func<Task> callback)
=> new AsyncReceiver(callback).Invoke;
public static Func<TValue, Task> AsNonRenderingEventHandler<TValue>(
Func<TValue, Task> callback)
=> new AsyncReceiver<TValue>(callback).Invoke;

private record SyncReceiver(Action callback)


: ReceiverBase { public void Invoke() => callback(); }
private record SyncReceiver<T>(Action<T> callback)
: ReceiverBase { public void Invoke(T arg) => callback(arg); }
private record AsyncReceiver(Func<Task> callback)
: ReceiverBase { public Task Invoke() => callback(); }
private record AsyncReceiver<T>(Func<T, Task> callback)
: ReceiverBase { public Task Invoke(T arg) => callback(arg); }

private record ReceiverBase : IHandleEvent


{
public Task HandleEventAsync(EventCallbackWorkItem item, object arg)
=>
item.InvokeAsync(arg);
}
}

Appelez EventUtil.AsNonRenderingEventHandler pour appeler un gestionnaire


d’événements qui ne déclenche pas de rendu quand il est appelé.

Dans l’exemple suivant :


La sélection du premier bouton, qui appelle HandleClick1 , déclenche un nouveau
rendu.
La sélection du deuxième bouton, qui appelle HandleClick2 , ne déclenche pas de
nouveau rendu.
La sélection du troisième bouton, qui appelle HandleClick3 , ne déclenche pas de
nouveau rendu et utiliser elle activation et utilise des arguments d’événement
(MouseEventArgs).

HandleSelect2.razor :

razor

@page "/handle-select-2"
@rendermode InteractiveServer
@using Microsoft.Extensions.Logging
@inject ILogger<HandleSelect2> Logger

<p>
Last render DateTime: @dt
</p>

<button @onclick="HandleClick1">
Select me (Rerenders)
</button>

<button @onclick="EventUtil.AsNonRenderingEventHandler(HandleClick2)">
Select me (Avoids Rerender)
</button>

<button @onclick="EventUtil.AsNonRenderingEventHandler<MouseEventArgs>
(HandleClick3)">
Select me (Avoids Rerender and uses <code>MouseEventArgs</code>)
</button>

@code {
private DateTime dt = DateTime.Now;

private void HandleClick1()


{
dt = DateTime.Now;

Logger.LogInformation("This event handler triggers a rerender.");


}

private void HandleClick2()


{
dt = DateTime.Now;

Logger.LogInformation("This event handler doesn't trigger a


rerender.");
}
private void HandleClick3(MouseEventArgs args)
{
dt = DateTime.Now;

Logger.LogInformation(
"This event handler doesn't trigger a rerender. " +
"Mouse coordinates: {ScreenX}:{ScreenY}",
args.ScreenX, args.ScreenY);
}
}

En plus d’implémenter l’interface IHandleEvent, la mise à profit des autres bonnes


pratiques décrites dans cet article peut aussi contribuer à réduire les rendus indésirables
après la gestion d’événements. Par exemple, le remplacement de ShouldRender dans les
composants enfants du composant cible permet de contrôler le renouvellement du
rendu.

Éviter de recréer des délégués pour de nombreux


éléments ou composants répétés
La recréation par Blazor de délégués d’expression lambda pour les éléments ou les
composants d’une boucle peut avoir un impact négatif sur les performances.

Le composant suivant présenté dans l’article sur la gestion des événements assure le
rendu d’un ensemble de boutons. Chaque bouton attribue un délégué à son événement
@onclick , ce qui est acceptable s’il n’y a pas beaucoup de boutons à afficher.

EventHandlerExample5.razor :

razor

@page "/event-handler-example-5"
@rendermode InteractiveServer

<h1>@heading</h1>

@for (var i = 1; i < 4; i++)


{
var buttonNumber = i;

<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
</p>
}
@code {
private string heading = "Select a button to learn its position";

private void UpdateHeading(MouseEventArgs e, int buttonNumber)


{
heading = $"Selected #{buttonNumber} at {e.ClientX}:{e.ClientY}";
}
}

Si un grand nombre de boutons sont rendus à l’aide de l’approche précédente, la


vitesse de rendu est impactée négativement, ce qui entraîne une expérience utilisateur
médiocre. Pour afficher un grand nombre de boutons avec un rappel pour les
événements de clic, l’exemple suivant utilise une collection d’objets de bouton qui
affectent le délégué @onclick de chaque bouton à un Action. L’approche suivante n’a
pas besoin de Blazor pour reconstruire tous les délégués de bouton à chaque rendu des
boutons :

LambdaEventPerformance.razor :

razor

@page "/lambda-event-performance"
@rendermode InteractiveServer

<h1>@heading</h1>

@foreach (var button in Buttons)


{
<p>
<button @key="button.Id" @onclick="button.Action">
Button #@button.Id
</button>
</p>
}

@code {
private string heading = "Select a button to learn its position";

private List<Button> Buttons { get; set; } = new();

protected override void OnInitialized()


{
for (var i = 0; i < 100; i++)
{
var button = new Button();

button.Id = Guid.NewGuid().ToString();

button.Action = (e) =>


{
UpdateHeading(button, e);
};

Buttons.Add(button);
}
}

private void UpdateHeading(Button button, MouseEventArgs e)


{
heading = $"Selected #{button.Id} at {e.ClientX}:{e.ClientY}";
}

private class Button


{
public string? Id { get; set; }
public Action<MouseEventArgs> Action { get; set; } = e => { };
}
}

Optimiser la vitesse d’interopérabilité


JavaScript
Les appels entre .NET et JavaScript nécessitent une surcharge supplémentaire, car :

Par défaut, les appels sont asynchrones.


Par défaut, les paramètres et les valeurs de retour sont sérialisés par JSON pour
offrir un mécanisme de conversion facile à comprendre entre les types .NET et
JavaScript.

En outre, pour les applications Blazor côté serveur, ces appels sont passés sur le réseau.

Éviter les appels à granularité excessivement fine


Sachant que chaque appel implique une certaine surcharge, il peut être utile de réduire
le nombre d’appels. Examinez le code suivant, qui stocke une collection d’éléments dans
le localStorage du navigateur :

C#

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)


{
foreach (var item in items)
{
await JS.InvokeVoidAsync("localStorage.setItem", item.Id,
JsonSerializer.Serialize(item));
}
}
L’exemple précédent effectue un appel d’interopérabilité JS distinct pour chaque
élément. Au lieu de cela, l’approche suivante réduit l’interopérabilité JS à un seul appel :

C#

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)


{
await JS.InvokeVoidAsync("storeAllInLocalStorage", items);
}

La fonction JavaScript correspondante stocke toute la collection d’éléments sur le client :

JavaScript

function storeAllInLocalStorage(items) {
items.forEach(item => {
localStorage.setItem(item.id, JSON.stringify(item));
});
}

Pour les applications Blazor WebAssembly, le regroupement de différents appels


d’interopérabilité JS dans un même appel a généralement pour effet d’améliorer
nettement les performances à condition que le composant effectue un grand nombre
d’appels d’interopérabilité JS.

Envisager l’utilisation d’appels synchrones

Appeler JavaScript à partir de .NET


Cette section s'applique uniquement aux composants côté client.

Les appels d’interopérabilité JS sont asynchrones par défaut, que le code appelé soit
synchrone ou asynchrone. Les appels sont asynchrones par défaut pour garantir que les
composants sont compatibles entre les modes de rendu côté serveur et côté client. Sur
le serveur, tous les appels interop JS doivent être asynchrones car ils sont envoyés via
une connexion réseau.

Si vous savez avec certitude que votre composant s'exécute uniquement sur
WebAssembly, vous pouvez choisir d'effectuer des appels interop synchrones JS. Cela
représente un peu moins de surcharge que d’effectuer des appels asynchrones, et peut
entraîner moins de cycles de rendu, car il n’y a pas d’état intermédiaire lors de l’attente
des résultats.
Pour effectuer un appel synchrone de .NET vers JavaScript dans un composant côté
client, convertissez IJSRuntime en IJSInProcessRuntime pour effectuer l'appel
d'interopérabilité JS :

razor

@inject IJSRuntime JS

...

@code {
protected override void HandleSomeEvent()
{
var jsInProcess = (IJSInProcessRuntime)JS;
var value = jsInProcess.Invoke<string>
("javascriptFunctionIdentifier");
}
}

Lorsque vous travaillez avec IJSObjectReference des composants côté client dans
ASP.NET Core 5.0 ou version ultérieure, vous pouvez utiliser
IJSInProcessObjectReference de manière synchrone. IJSInProcessObjectReference
implémente IAsyncDisposable/IDisposable et doit être supprimé à des fins de nettoyage
de la mémoire pour empêcher une fuite de mémoire, comme l’illustre l’exemple suivant :

razor

@inject IJSRuntime JS
@implements IAsyncDisposable

...

@code {
...
private IJSInProcessObjectReference? module;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
module = await JS.InvokeAsync<IJSInProcessObjectReference>
("import",
"./scripts.js");
}
}

...

async ValueTask IAsyncDisposable.DisposeAsync()


{
if (module is not null)
{
await module.DisposeAsync();
}
}
}

Appeler .NET à partir de JavaScript


Cette section s'applique uniquement aux composants côté client.

Les appels d’interopérabilité JS sont asynchrones par défaut, que le code appelé soit
synchrone ou asynchrone. Les appels sont asynchrones par défaut pour garantir que les
composants sont compatibles entre les modes de rendu côté serveur et côté client. Sur
le serveur, tous les appels interop JS doivent être asynchrones car ils sont envoyés via
une connexion réseau.

Si vous savez avec certitude que votre composant s'exécute uniquement sur
WebAssembly, vous pouvez choisir d'effectuer des appels interop synchrones JS. Cela
représente un peu moins de surcharge que d’effectuer des appels asynchrones, et peut
entraîner moins de cycles de rendu, car il n’y a pas d’état intermédiaire lors de l’attente
des résultats.

Pour effectuer un appel synchrone de JavaScript vers .NET dans un composant côté
client, utilisez DotNet.invokeMethod à la place de DotNet.invokeMethodAsync .

Les appels synchrones fonctionnent si :

Le composant est uniquement rendu pour exécution sur WebAssembly.


La fonction appelée retourne une valeur de manière synchrone. La fonction n’est
pas une méthode async et ne retourne pas de Task .NET ou Promise JavaScript.

Utiliser l’interopérabilité [JSImport] / [JSExport]


JavaScript
L’interopérabilité [JSImport] / [JSExport] JavaScript pour les applications Blazor
WebAssembly offre un niveau de performance et une stabilité améliorés par rapport à
l’API d’interopérabilité JS des versions du framework antérieures à ASP.NET Core 7.0.

Pour plus d’informations, consultez interopérabilité JSImport/JSExport JavaScript avec


Blazor ASP.NET Core.
Compilation anticipée (AOT)
La compilation anticipée (AOT, « Ahead-of-time ») compile le code .NET d’une
application Blazor directement en WebAssembly natif pour une exécution directe par le
navigateur. Si la compilation AOT génère des applications plus volumineuses qui
prennent plus de temps à télécharger, elles offrent généralement de meilleures
performances d’exécution, en particulier dans le cas des applications qui exécutent des
tâches gourmandes en ressources processeur. Pour plus d’informations, consultez
Héberger et déployer ASP.NET Core Blazor WebAssembly.

Réduire la taille de téléchargement de


l’application

Nouvelle liaison du runtime


Pour plus d’informations sur la façon dont la liaison dynamique à l’exécution (ou
« runtime relinking ») réduit la taille de téléchargement d’une application, consultez
Héberger et déployer Blazor WebAssembly ASP.NET Core .

Utiliser System.Text.Json
L’implémentation de l’interopérabilité JS de Blazor s’appuie sur System.Text.Json, qui est
une bibliothèque de sérialisation JSON hautes performances offrant une faible allocation
de mémoire. En utilisant System.Text.Json, la taille de charge d’utile d’application ne doit
être supérieure à celle résultant de l’ajout d’une ou plusieurs autres bibliothèques JSON.

Pour obtenir une aide pour la migration, consultez Guide pratique pour migrer de
Newtonsoft.Json vers System.Text.Json.

Suppression de code en langage intermédiaire (IL)


Cette section s’applique uniquement aux applications Blazor WebAssembly.

La suppression des assemblys non utilisés dans une application Blazor WebAssembly a
pour effet de réduire la taille de l’application en supprimant le code inutilisé dans les
fichiers binaires de l’application. Pour plus d’informations, consultez Configurer l’outil de
suppression pour Blazor ASP.NET Core.

Charger des assemblys en mode différé


Cette section s’applique uniquement aux applications Blazor WebAssembly.

Chargez des assemblys au moment de l’exécution quand leur utilisation est requise par
une route. Pour plus d’informations, consultez Charger des assemblys en mode différé
dans ASP.NET Core Blazor WebAssembly.

Compression
Cette section s’applique uniquement aux applications Blazor WebAssembly.

Quand une application Blazor WebAssembly est publiée, la sortie est compressée
statiquement pendant la publication pour réduire la taille de l’application et ôter la
surcharge liée à la compression du runtime. Blazor s’appuie sur le serveur pour effectuer
la négociation de contenu et fournir des fichiers compressés de manière statique.

Une fois qu’une application est déployée, vérifiez qu’elle fournit des fichiers compressés.
Inspectez l’onglet Réseau dans les outils de développement d’un navigateur et vérifiez
que les fichiers sont fournis avec Content-Encoding: br (compression Brotli) ou Content-
Encoding: gz (compression Gzip). Si l’hôte ne fournit pas de fichiers compressés, suivez

les instructions fournies dans Héberger et déployer Blazor WebAssembly ASP.NET Core.

Désactiver les fonctionnalités non utilisées


Cette section s’applique uniquement aux applications Blazor WebAssembly.

Le runtime de Blazor WebAssembly inclut les fonctionnalités .NET suivantes qui peuvent
être désactivées pour réduire la taille de la charge utile :

Un fichier de données est inclus pour que les informations de fuseau horaire soient
correctes. Si l’application n’a pas besoin de cette fonctionnalité, désactivez-la
éventuellement en attribuant à la propriété MSBuild BlazorEnableTimeZoneSupport
au niveau du fichier projet de l’application la valeur false :

XML

<PropertyGroup>
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
</PropertyGroup>

Par défaut, Blazor WebAssembly contient les ressources de globalisation


nécessaires pour afficher les valeurs, telles que les dates et la devise, dans la
culture de l’utilisateur. Si l’application n’a pas besoin d’être localisée, vous pouvez
configurer l’application pour prendre en charge la culture invariante, qui est basée
sur la culture en-US .

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Tester les composants Razor dans
ASP.NET Core Blazor
Article • 30/11/2023

Par : Egil Hansen

Le test des composants Razor est un aspect important de la publication d’applications


Blazor stables et gérables.

Pour tester un composant Razor, le composant en cours de test (CUT, Component Under
Test) est :

Rendu avec une entrée appropriée correspondant au test.


susceptible de faire l’objet d’interaction ou de modification, selon le type de test
effectué. Par exemple, des gestionnaires d’événements peuvent être déclenchés,
comme un événement onclick pour un bouton.
Inspecté pour rechercher des valeurs attendues. Un test réussit lorsqu’une ou
plusieurs valeurs inspectées correspondent aux valeurs attendues pour le test.

Approches de test
Deux approches courantes pour le test de composants Razor sont les tests de bout en
bout (E2E) et les tests unitaires :

Tests unitaires : les tests unitaires sont écrits avec une bibliothèque de tests
unitaires qui fournit les éléments suivants :
Rendu des composants.
Inspection de la sortie et de l’état des composants.
Déclenchement de gestionnaires d’événements et de méthodes de cycle de vie.
Assertions indiquant que le comportement du composant est correct.

bUnit est un exemple de bibliothèque qui active les tests unitaires de


composants Razor.

Test E2E : un exécuteur de test exécute une application Blazor contenant le


composant en cours de test (CUT) et automatise une instance de navigateur. L’outil
de test inspecte le composant en cours de test et interagit avec lui par le biais du
navigateur. Playwright pour .NET est un exemple d’infrastructure de test E2E qui
peut être utilisée avec des applications Blazor.
Dans les tests unitaires, seul le composant Razor (Razor/C#) est impliqué. Les
dépendances externes, telles que les services et l’interopérabilité JS, doivent être
simulées. Dans le test E2E, le composant Razor et toute son infrastructure auxiliaire font
partie du test, ce qui inclut CSS, JS, ainsi que les API DOM et de navigateur.

L’étendue de test décrit le niveau d’exécution des tests. L’étendue de test a généralement
une influence sur la vitesse des tests. Les tests unitaires s’exécutent sur un sous-
ensemble des sous-systèmes de l’application et s’exécutent généralement en quelques
millisecondes. L’exécution des tests E2E, qui testent un large groupe de sous-systèmes
de l’application, peut prendre plusieurs secondes.

Les tests unitaires permettent également d’accéder à l’instance du composant en cours


de test (CUT), ce qui permet d’inspecter et de vérifier l’état interne du composant. Cela
n’est normalement pas possible dans les tests E2E.

En ce qui concerne l’environnement du composant, les tests E2E doivent s’assurer que
l’état environnemental attendu a été atteint avant le début de la vérification. Sinon, le
résultat est imprévisible. Dans le test unitaire, le rendu du composant en cours de test
(CUT) et le cycle de vie du test sont plus intégrés, ce qui améliore la stabilité du test.

Les tests E2E impliquent le lancement de plusieurs processus, des E/S réseau et disque,
ainsi que d’autres activités de sous-système qui entraînent souvent une fiabilité
médiocre des tests. Les tests unitaires sont généralement isolés de ce genre de
problèmes.

Le tableau suivant récapitule les différences entre les deux approches de test.

Fonctionnalité Tests unitaires Test E2E

Étendue de test Composant Razor (Razor/C#) Component Razor (Razor/C#)


uniquement avec CSS/JS

Délai d’exécution des tests Millisecondes Secondes

Accès à l’instance du Oui Non


composant

Sensible à l’environnement Non Oui

Fiabilité Plus fiable Moins fiable

Choisir l’approche de test la plus appropriée


Prenez en compte le scénario lors du choix du type de test à effectuer. Certaines
considérations sont décrites dans le tableau suivant.
Scénario Approche Notes
suggérée

Composant sans Tests Lorsqu’il n’existe aucune dépendance à l’interopérabilité


logique unitaires JS dans un composant Razor, le composant peut être
d’interopérabilité JS testé sans accès à JS ou à l’API DOM. Dans ce scénario,
choisir le test unitaire ne présente aucun inconvénient.

Composant avec une Tests Il est courant que les composants interrogent le DOM
logique unitaires ou déclenchent des animations par le biais de
d’interopérabilité JS l’interopérabilité JS. Le test unitaire est généralement
simple préféré dans ce scénario, car il est simple de simuler
l’interaction JS par le biais de l’interface IJSRuntime.

Composant qui dépend Tests Si un composant utilise l’interopérabilité JS pour appeler


de code JS complexe unitaires et une bibliothèque JS volumineuse ou complexe, mais
tests JS que l’interaction entre le composant Razor et la
distincts bibliothèque JS est simple, la meilleure approche est
susceptible de traiter le composant et la bibliothèque
ou le code JS comme deux parties distinctes, et de les
tester individuellement. Testez le composant Razor avec
une bibliothèque de tests unitaires, et testez le code JS
avec une bibliothèque de tests JS.

Composant avec une Test E2E Lorsque les fonctionnalités d’un composant dépendent
logique qui dépend de de JS et de sa manipulation du DOM, vérifiez le code JS
la manipulation JS du et le code Blazor ensemble dans un test E2E. C’est
DOM de navigateur l’approche que les développeurs de l’infrastructure
Blazor ont adoptée avec la logique de rendu de
navigateur de Blazor, qui a du code C# et du code JS
fortement couplés. Le code C# et le code JS doivent
fonctionner ensemble pour restituer correctement les
composants Razor dans un navigateur.

Composant qui dépend Test E2E Lorsque les fonctionnalités d’un composant dépendent
d’une bibliothèque de d’une bibliothèque de classes tierce qui a des
classes tierce avec des dépendances difficiles à simuler, telles que
dépendances difficiles à l’interopérabilité JS, le test E2E peut être la seule option
simuler pour tester le composant.

Tester des composants avec bUnit


Il n’existe aucune infrastructure de test Microsoft officielle pour Blazor, mais le projet
bUnit porté par la communauté offre un moyen pratique d’effectuer un test unitaire
des composants Razor.

7 Notes
bUnit est une bibliothèque de tests tierce. Elle n’est pas prise en charge ou gérée
par Microsoft.

bUnit fonctionne avec des infrastructures de test à usage général, telles que MSTest,
NUnit et xUnit . Ces infrastructures de test donnent aux tests bUnit l’apparence de
tests unitaires standard. Les tests bUnit intégrés à une infrastructure de test à usage
général sont généralement exécutés avec les éléments suivants :

Explorateur de tests de Visual Studio.


Commande CLI dotnet test dans un interpréteur de commandes.
Un pipeline de test DevOps automatisé.

7 Notes

Les concepts de test et les implémentations de test entre différents frameworks de


test sont similaires, mais pas identiques. Pour obtenir des conseils, reportez-vous à
la documentation de l’infrastructure de test.

L’exemple suivant illustre la structure d’un test bUnit sur le composant Counter dans
une application basée sur un modèle de projet Blazor. Le composant Counter affiche et
incrémente un compteur basé sur la sélection, par l’utilisateur, d’un bouton dans la
page :

razor

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()


{
currentCount++;
}
}

Le test bUnit suivant vérifie que le compteur du composant en cours de test (CUT) est
incrémenté correctement lorsque le bouton est sélectionné :
razor

@code {
[Fact]
public void CounterShouldIncrementWhenClicked()
{
// Arrange
using var ctx = new TestContext();
var cut = ctx.Render(@<Counter />);
var paraElm = cut.Find("p");

// Act
cut.Find("button").Click();

// Assert
var paraElmText = paraElm.TextContent;
paraElm.MarkupMatches("Current count: 1");
}
}

Les tests peuvent également être rédigés dans un fichier de classe C# :

C#

public class CounterTests


{
[Fact]
public void CounterShouldIncrementWhenClicked()
{
// Arrange
using var ctx = new TestContext();
var cut = ctx.RenderComponent<Counter>();
var paraElm = cut.Find("p");

// Act
cut.Find("button").Click();

// Assert
var paraElmText = paraElm.TextContent;
paraElmText.MarkupMatches("Current count: 1");
}
}

Les actions suivantes ont lieu à chaque étape du test :

Organiser : le composant Counter est rendu à l’aide de l’objet TestContext de


bUnit. L’élément de paragraphe du composant en cours de test ( <p> ) est trouvé et
affecté à paraElm . Dans la syntaxe Razor, un composant peut être transmis en tant
que RenderFragment à bUnit.
Agir : l’élément du bouton ( <button> ) est localisé, puis sélectionné en appelant
Click , ce qui doit incrémenter le compteur et mettre à jour le contenu de la balise

du paragraphe ( <p> ). Le contenu de texte de l’élément de paragraphe est obtenu


en appelant TextContent .

Déclarer : MarkupMatches est appelé sur le contenu de texte pour vérifier qu’il
correspond à la chaîne attendue, qui est Current count: 1 .

7 Notes

La méthode assert MarkupMatches diffère d’une assertion régulière de comparaison


de chaînes (par exemple Assert.Equal("Current count: 1", paraElmText); ).
MarkupMatches effectue une comparaison sémantique de l’entrée et de la balise

HTML attendue. Une comparaison sémantique est consciente de la sémantique


HTML, ce qui signifie que des éléments tels que l’espace blanc non significatif est
ignoré. Il en résulte des tests plus stables. Pour plus d’informations, consultez
Personnalisation de la comparaison HTML sémantique .

Ressources supplémentaires
Bien démarrer avec bUnit : les instructions bUnit incluent des conseils sur la création
d’un projet de test, le référencement de packages d’infrastructure de test, ainsi que la
génération et l’exécution de tests.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Application web progressive (PWA)
Blazor sur ASP.NET Core
Article • 30/11/2023

Une application web progressive (PWA) Blazor est une application monopage (SPA) qui
utilise des API et des fonctionnalités de navigateurs modernes pour se comporter
comme une application de bureau.

Blazor WebAssembly est une plateforme standardisée d’applications web côté client, qui
peut utiliser toutes les API de navigateur existantes, y compris les API PWA requises
pour les fonctionnalités suivantes :

Exécution hors connexion et chargement instantané, indépendamment de la


vitesse du réseau.
Exécution dans la propre fenêtre de l’application, et pas seulement dans une
fenêtre de navigateur.
Lancement à partir du menu Démarrer, du dock ou de l’écran d’accueil du système
d’exploitation de l’hôte.
Réception des notifications Push d’un serveur back-end, même quand l’utilisateur
n’interagit pas avec l’application.
Mises à jour automatiques en arrière-plan.

Le terme progressive employé pour décrire ce type d’applications s’explique ainsi :

Un utilisateur découvre et utilise d’abord l’application dans son navigateur web


comme n’importe quelle autre application SPA.
Ensuite, progressivement, l’utilisateur installe l’application dans son système
d’exploitation et active les notifications Push.

Créer un projet à partir du modèle PWA


Visual Studio

Quand vous créez une application Blazor WebAssembly, cochez la case


Application web progressive.

Convertir une application Blazor WebAssembly


existante en PWA
Convertissez une application Blazor WebAssembly existante en application PWA en
suivant les instructions de cette section.

Dans le fichier projet de l’application :

Ajoutez la propriété ServiceWorkerAssetsManifest suivante à un PropertyGroup :

XML

...
<ServiceWorkerAssetsManifest>service-worker-
assets.js</ServiceWorkerAssetsManifest>
</PropertyGroup>

Ajoutez l’élément ServiceWorker suivant à un ItemGroup :

XML

<ItemGroup>
<ServiceWorker Include="wwwroot\service-worker.js"
PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>

Pour obtenir des ressources statiques, utilisez l’une des approches suivantes :

Créez un projet PWA distinct en exécutant la commande dotnet new dans un


interpréteur de commandes :

CLI .NET

dotnet new blazorwasm -o MyBlazorPwa --pwa

Dans la commande précédente, l’option -o|--output crée un dossier pour


l’application nommée MyBlazorPwa .

Si vous ne convertissez pas l’application vers la dernière version, ignorez l’option


-f|--framework . L’exemple suivant crée l’application pour ASP.NET Core

version 5.0 :

CLI .NET

dotnet new blazorwasm -o MyBlazorPwa --pwa -f net5.0

Accédez au dépôt GitHub ASP.NET Core à l’URL suivante, qui fournit des liens vers
la source et les ressources de référence de la branche main . Sélectionnez la version
que vous utilisez dans la liste déroulante Switch branches or tags (Changer de
branches ou d’étiquettes) qui s’applique à votre application.

Dossier wwwroot du modèle de projet Blazor WebAssembly (branche main du


dépôt GitHub dotnet/aspnetcore)

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner
une balise pour une version spécifique, utilisez la liste déroulante Échanger
les branches ou les balises. Pour plus d’informations, consultez Comment
sélectionner une balise de version du code source ASP.NET Core
(dotnet/AspNetCore.Docs #26205) .

À partir du dossier wwwroot source soit de l’application que vous avez créée, soit des
ressources de référence dans le dépôt GitHub dotnet/aspnetcore , copiez les fichiers
suivants vers le dossier wwwroot de l’application :

icon-512.png
manifest.json

service-worker.js
service-worker.published.js

Dans le fichier wwwroot/index.html de l’application :

Ajoutez les éléments <link> pour le manifeste et l’icône d’application :

HTML

<link href="manifest.json" rel="manifest" />


<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />

Ajoutez la balise <script> suivante dans la balise </body> fermante


immédiatement après la balise de script blazor.webassembly.js :

HTML

...
<script>navigator.serviceWorker.register('service-worker.js');
</script>
</body>

Installation et manifeste de l’application


Quand ils découvrent une application basée sur le modèle PWA, les utilisateurs ont
l’option d’installer l’application dans le menu Démarrer, le dock ou l’écran d’accueil de
leur système d’exploitation. Cette option leur est présentée différemment selon le
navigateur qu’ils utilisent. Dans les navigateurs de bureau basés sur Chromium, comme
Edge ou Chrome, il y a un bouton Ajouter dans la barre d’URL. Quand les utilisateurs
sélectionnent le bouton Ajouter, une boîte de dialogue de confirmation s’affiche :

Sur iOS, les utilisateurs peuvent installer l’application PWA en utilisant le bouton
Partager de Safari et son option Ajouter à l’écran Home. Sur Chrome pour Android, les
utilisateurs doivent sélectionner le bouton Menu dans le coin supérieur droit, puis
sélectionner Ajouter à l’écran Home.

Une fois installée, l’application s’ouvre dans sa propre fenêtre sans barre d’adresse :
Pour personnaliser le titre, le jeu de couleurs, l’icône ou d’autres détails de la fenêtre,
consultez le fichier manifest.json dans le répertoire wwwroot du projet. Le schéma de ce
fichier est défini par les standards du Web. Pour plus d’informations, consultez
Documentation web MDN : Manifeste des applications web .

Prise en charge hors connexion


Par défaut, les applications créées avec l’option de modèle PWA peuvent s’exécuter hors
connexion. Un utilisateur doit d’abord découvrir l’application quand il est connecté. Le
navigateur télécharge et met en cache automatiquement toutes les ressources
nécessaires à l’exécution hors connexion de l’application.

) Important

La prise en charge du développement interférerait avec le cycle de développement


habituel consistant à apporter des modifications et à les tester. C’est pourquoi la
prise en charge du mode hors connexion est possible uniquement pour les
applications publiées.

2 Avertissement
Si vous envisagez de distribuer une application PWA exécutable hors connexion,
plusieurs avertissements et mises en garde importants sont à prendre en compte.
Ces scénarios sont inhérents à toutes les applications PWA exécutables hors
connexion ; ils ne sont pas propres à Blazor. Veillez à lire et à bien comprendre ces
mises en garde avant de faire des hypothèses sur le fonctionnement de votre
application exécutable hors connexion.

Pour voir comment fonctionne la prise en charge du mode hors connexion :

1. Publiez l’application. Pour plus d’informations, consultez Héberger et déployer


ASP.NET Core Blazor.

2. Déployez l’application sur un serveur prenant en charge HTTPS et accédez à


l’application dans un navigateur à son adresse HTTPS sécurisée.

3. Ouvrez les outils de développement du navigateur et vérifiez qu’un service worker


est inscrit pour l’hôte sous l’onglet Application :

4. Rechargez la page et examinez l’onglet Réseau. Service Worker ou le cache


mémoire est répertorié comme sources pour toutes les ressources de la page :
5. Pour vérifier que le navigateur ne dépend pas de l’accès réseau pour charger
l’application, utilisez l’une de ces deux méthodes :

Arrêtez le serveur web et regardez si l’application continue de fonctionner


normalement, y compris les rechargements de page. De même, regardez si
l’application continue de fonctionner normalement en cas de connexion
réseau lente.
Demandez au navigateur de simuler le mode hors connexion sous l’onglet
Réseau :

La prise en charge du mode hors connexion avec un service worker est un standard du
Web ; elle n’est pas propre à Blazor. Pour plus d’informations sur les service workers,
consultez Documentation web MDN : API de service worker . Pour en savoir plus sur
les schémas d’utilisation courants des service workers, consultez Google Web : Le cycle
de vie d’un service worker .

Le modèle PWA de Blazor produit deux fichiers de service worker :

wwwroot/service-worker.js , qui est utilisé pendant le développement.

wwwroot/service-worker.published.js , qui est utilisé après la publication de


l’application.

Pour partager la logique entre les deux fichiers de service worker, envisagez l’approche
suivante :

Ajoutez un troisième fichier JavaScript, qui contient la logique commune.


Utilisez self.importScripts pour charger la logique commune dans les deux
fichiers de service worker.

Stratégie de récupération cache-first


Le service worker service-worker.published.js intégré résout les requêtes en suivant
une stratégie cache-first (cache en priorité). Cela signifie que le service worker retourne
en priorité le contenu mis en cache, sans prendre en compte si l’utilisateur dispose d’un
accès réseau ou si du contenu plus récent est disponible sur le serveur.

La stratégie cache-first offre plusieurs avantages :

Elle garantit la fiabilité. L’accès réseau n’est pas un état booléen. Un utilisateur
n’est pas simplement en ligne (connecté) ou hors connexion :
L’appareil de l’utilisateur peut supposer qu’il est en ligne, mais le réseau peut
être si lent qu’il est impossible d’attendre.
Le réseau peut retourner des résultats non valides pour certaines URL, par
exemple quand un portail WIFI captif bloque ou redirige des requêtes.

C’est pourquoi l’API navigator.onLine du navigateur n’est pas fiable et ne doit pas
avoir de dépendances.

Elle garantit l’exactitude. Quand il crée un cache de ressources hors connexion, le


service worker utilise le hachage de contenu pour être sûr de récupérer (fetch) un
instantané complet et intrinsèquement cohérent des ressources à un instant t. Ce
cache est ensuite utilisé comme unité atomique. Il est inutile de demander au
réseau des ressources plus récentes, car les seules versions requises sont celles qui
se trouvent déjà dans le cache. Toute autre ressource risquerait d’introduire des
problèmes d’incohérence et d’incompatibilité (par exemple, en essayant d’utiliser
des versions d’assemblys .NET qui n’ont pas été compilés ensemble).

Si vous devez empêcher le navigateur de récupérer service-worker-assets.js à partir


de son cache HTTP, par exemple pour résoudre les échecs temporaires de vérification de
l’intégrité au déploiement d’une nouvelle version du service worker, mettez à jour
l’inscription du service worker dans wwwroot/index.html en définissant updateViaCache
sur « none » :

HTML

<script>
navigator.serviceWorker.register('/service-worker.js', {updateViaCache:
'none'});
</script>
Mises à jour en arrière-plan
Vous pouvez vous représenter mentalement une application PWA exécutable d’abord
hors connexion comme une application qui se comporte comme une application mobile
qui peut être installée. L’application démarre immédiatement, quelle que soit la
connectivité réseau, mais la logique de l’application installée provient d’un instantané
d’un point dans le temps qui ne correspond pas forcément à la dernière version.

Le modèle PWA de Blazor produit des applications qui tentent automatiquement de se


mettre à jour en arrière-plan chaque fois que l’utilisateur découvre une application et
dispose d’une connexion réseau opérationnelle. Voici le processus :

Au moment de la compilation, le projet génère un manifeste de ressources pour le


service worker. Par défaut, le manifeste s’appelle service-worker-assets.js . Le
manifeste liste toutes les ressources statiques dont l’application a besoin pour
s’exécuter hors connexion, telles que les assemblys .NET, les fichiers JavaScript et
les fichiers CSS, y compris leurs hachages de contenu. La liste des ressources est
chargée par le service worker afin qu’il sache quelles ressources mettre en cache.
Chaque fois que l’utilisateur découvre l’application, le navigateur redemande
service-worker.js et service-worker-assets.js en arrière-plan. Les fichiers sont

comparés octet par octet avec le service worker installé existant. Si le serveur
retourne le contenu modifié pour l’un de ces fichiers, le service worker tente
d’installer une nouvelle version de lui-même.
Lors de l’installation de sa nouvelle version, le service worker crée un autre cache
distinct pour les ressources hors connexion, puis commence à remplir ce cache
avec les ressources listées dans service-worker-assets.js . Cette logique est
implémentée dans la fonction onInstall à l’intérieur de service-
worker.published.js .

Le processus aboutit quand toutes les ressources sont chargées sans erreur et que
tous les hachages de contenu correspondent. En cas de réussite, le nouveau
service worker entre dans un état d’attente d’activation. Dès que l’utilisateur ferme
l’application (tous les onglets et fenêtres d’application doivent être fermés), le
nouveau service worker devient actif et est utilisé pour les découvertes suivantes
de l’application. L’ancien service worker et son cache sont supprimés.
Si le processus n’aboutit pas, l’instance du nouveau service worker est supprimée.
Le processus de mise à jour sera retenté à la prochaine visite de l’utilisateur, quand
le client aura, espérons-le, une meilleure connexion réseau pour traiter
correctement les requêtes.

Personnalisez ce processus en modifiant la logique du service worker. Aucun des


comportements précédents n’est propre à Blazor. Il s’agit simplement de l’expérience
par défaut fournie par l’option de modèle PWA. Pour plus d’informations, consultez
Documents web MDN : API de service worker .

Résolution des requêtes


Comme nous l’avons expliqué dans la section Stratégie de récupération cache-first, le
service worker par défaut utilise une stratégie cache-first, c’est-à-dire qu’il essaie de
retourner en priorité le contenu mis en cache quand il est disponible. S’il n’y a pas de
contenu mis en cache pour une URL donnée, par exemple lors de la récupération de
données depuis une API back-end, le service worker utilise une requête réseau standard.
La requête réseau réussit si le serveur est accessible. Cette logique est implémentée
dans la fonction onFetch à l’intérieur de service-worker.published.js .

Si les composants Razor de l’application dépendent de la récupération de données à


partir d’API back-end et que vous souhaitez fournir une expérience utilisateur conviviale
pour les requêtes ayant échoué en raison de l’indisponibilité du réseau, implémentez la
logique dans les composants de l’application. Par exemple, utilisez try/catch autour des
requêtes HttpClient.

Prise en charge des pages rendues par le serveur


Réfléchissez à ce qui se passe lorsque l’utilisateur accède pour la première fois à une
URL comme /counter ou à tout autre lien profond dans l’application. Dans de tels cas,
au lieu de retourner le contenu mis en cache en tant que /counter , vous voulez que le
navigateur charge le contenu mis en cache en tant que /index.html pour démarrer
votre application Blazor WebAssembly. Ces requêtes initiales sont appelées requêtes de
navigation, par opposition aux :

requêtes subresource pour obtenir des images, des feuilles de style ou d’autres
fichiers ;
requêtes fetch/XHR pour obtenir des données d’API.

Le service worker par défaut contient une logique de cas particuliers pour les requêtes
de navigation. Le service worker résout les requêtes en retournant le contenu mis en
cache pour /index.html , quelle que soit l’URL demandée. Cette logique est
implémentée dans la fonction onFetch à l’intérieur de service-worker.published.js .

Si votre application a certaines URL qui doivent retourner du contenu HTML rendu par le
serveur, au lieu du contenu /index.html issu du cache, vous devez modifier la logique
dans votre service worker. Si toutes les URL contenant /Identity/ doivent être traitées
comme des requêtes uniquement en ligne standard adressées au serveur, modifiez la
logique service-worker.published.js onFetch . Recherchez le code suivant :

JavaScript

const shouldServeIndexHtml = event.request.mode === 'navigate';

Modifiez le code comme suit :

JavaScript

const shouldServeIndexHtml = event.request.mode === 'navigate'


&& !event.request.url.includes('/Identity/');

Si vous ne modifiez pas le code, quelle que soit la connectivité réseau, le service worker
intercepte les requêtes pour ces URL et les résout avec /index.html .

Ajoutez à la vérification des points de terminaison supplémentaires pour les fournisseurs


d’authentification externes. Dans l’exemple suivant, le point de terminaison /signin-
google pour l’authentification Google est ajouté à la vérification :

JavaScript

const shouldServeIndexHtml = event.request.mode === 'navigate'


&& !event.request.url.includes('/Identity/')
&& !event.request.url.includes('/signin-google');

Aucune action n’est requise pour l’environnement de développement, où le contenu est


toujours récupéré à partir du réseau.

Contrôle de la mise en cache des ressources


Si votre projet définit la propriété MSBuild ServiceWorkerAssetsManifest , les outils de
build de Blazor créent un manifeste de ressources pour le service worker, avec le nom
spécifié. Le modèle PWA par défaut produit un fichier projet contenant la propriété
suivante :

XML

<ServiceWorkerAssetsManifest>service-worker-
assets.js</ServiceWorkerAssetsManifest>
Le fichier étant placé dans le répertoire de sortie wwwroot , le navigateur peut récupérer
ce fichier en demandant /service-worker-assets.js . Pour afficher le contenu de ce
fichier, ouvrez /bin/Debug/{TARGET FRAMEWORK}/wwwroot/service-worker-assets.js dans
un éditeur de texte. Ne modifiez pas le fichier, car il est regénéré à chaque build.

Par défaut, ce manifeste liste les éléments suivants :

Toutes les ressources managées par Blazor, telles que les assemblys .NET et les
fichiers de runtime .NET WebAssembly requis pour l’exécution hors connexion.
Toutes les ressources nécessaires à la publication dans le répertoire wwwroot de
l’application, comme les images, les feuilles de style et les fichiers JavaScript, y
compris les ressources web statiques fournies par des projets externes et des
packages NuGet.

Vous pouvez contrôler lesquelles de ces ressources sont récupérées (fetch) et mises en
cache par le service worker, en modifiant la logique dans onInstall dans service-
worker.published.js . Par défaut, le service worker récupère et met en cache les fichiers

ayant des extensions de nom de fichier web classiques, par exemple .html , .css , .js et
.wasm , ainsi que les types de fichiers spécifiques à Blazor WebAssembly, comme les

fichiers .pdb (Toutes les versions) et .dll (ASP.NET Core 7.0 ou une version antérieure).

Pour inclure des ressources supplémentaires qui ne sont pas présentes dans le
répertoire wwwroot de l’application, définissez d’autres entrées MSBuild ItemGroup ,
comme illustré dans l’exemple suivant :

XML

<ItemGroup>
<ServiceWorkerAssetsManifestItem Include="MyDirectory\AnotherFile.json"
RelativePath="MyDirectory\AnotherFile.json"
AssetUrl="files/AnotherFile.json" />
</ItemGroup>

Les métadonnées AssetUrl spécifient l’URL relative de base que le navigateur doit
utiliser lors de la récupération de la ressource à mettre en cache. Cela n’est pas
forcément lié à son nom de fichier source d’origine sur le disque.

) Important

L’ajout d’un ServiceWorkerAssetsManifestItem n’entraîne pas la publication du


fichier dans le répertoire wwwroot de l’application. La sortie de publication doit être
contrôlée séparément. Seul le ServiceWorkerAssetsManifestItem provoque
l’affichage d’une entrée supplémentaire dans le manifeste des ressources du service
worker.

Notifications Push
Comme toute autre application PWA, une application PWA Blazor WebAssembly peut
recevoir des notifications Push de la part d’un serveur back-end. Le serveur peut
envoyer des notifications Push à tout moment, même lorsque l’utilisateur n’interagit pas
avec l’application. Par exemple, des notifications Push peuvent être envoyées quand un
autre utilisateur effectue une action pertinente.

Le mécanisme d’envoi d’une notification Push est totalement indépendant de Blazor


WebAssembly, car il est implémenté par le serveur back-end qui peut utiliser n’importe
quelle technologie. Si vous souhaitez envoyer des notifications Push à partir d’un
serveur ASP.NET Core, envisagez d’utiliser une technique similaire à l’approche adoptée
dans l’atelier Blazing Pizza .

Le mécanisme de réception et d’affichage d’une notification Push sur le client est


également indépendant de Blazor WebAssembly, car il est implémenté dans le fichier
JavaScript du service worker. Pour obtenir un exemple, consultez l’approche utilisée
dans l’atelier Blazing Pizza .

Mises en garde relatives aux applications PWA


exécutables hors connexion
Les applications n’ont pas vocation à être toutes exécutables hors connexion. La prise en
charge du mode hors connexion accroît la complexité de manière significative, alors
qu’elle n’est pas toujours pertinente pour les cas d’usage requis.

La prise en charge du mode hors connexion est généralement pertinente uniquement :

Si le magasin de données principal est local dans le navigateur. Par exemple,


l’approche est pertinente dans une application avec une interface utilisateur pour
un appareil IoT qui stocke des données dans localStorage ou IndexedDB .
Si l’application a une grosse charge de travail pour récupérer (fetch) et mettre en
cache les données d’API back-end relatives à chacun des utilisateurs afin que ceux-
ci puissent naviguer dans les données en mode hors connexion. Si l’application
doit prendre en charge la modification, auquel cas un système de suivi des
changements et de synchronisation des données avec le serveur back-end doit
être créé.
Si l’objectif est de garantir le chargement immédiat de l’application, quelles que
soient les conditions réseau. Implémentez une expérience utilisateur appropriée
autour des requêtes d’API back-end pour afficher la progression des requêtes et
présenter un comportement correct en cas d’échec des requêtes en raison d’une
indisponibilité du réseau.

En outre, les applications PWA exécutables hors connexion doivent faire face à diverses
autres complications. Il est donc essentiel que les développeurs prennent connaissance
des mises en garde données dans les sections suivantes.

Prise en charge du mode hors connexion uniquement


après publication
Durant le développement, vous souhaitez généralement voir chaque modification
immédiatement répercutée dans le navigateur sans avoir à passer par un processus de
mise à jour en arrière-plan. C’est pourquoi le modèle PWA de Blazor permet la prise en
charge du mode hors connexion uniquement après publication.

Quand vous créez une application exécutable hors connexion, tester l’application dans
l’environnement de développement n’est pas suffisant. Vous devez tester l’application
dans son état publié pour comprendre comment elle répond aux différentes conditions
réseau.

Fin du processus de mise à jour quand l’utilisateur quitte


l’application
Les mises à jour ne sont pas complètes tant que l’utilisateur n’a pas quitté tous les
onglets de l’application. Comme cela a été expliqué dans la section Mises à jour en
arrière-plan, une fois que vous avez déployé une mise à jour dans l’application, le
navigateur récupère les fichiers du service worker qui ont été mis à jour pour
commencer le processus de mise à jour.

Ce qui surprend de nombreux développeurs, c’est que, même lorsque cette mise à jour
est terminée, elle n’est pas appliquée tant que l’utilisateur n’a pas quitté tous les
onglets. Il ne suffit pas d’actualiser l’onglet affichant l’application, même si c’est le seul
onglet encore ouvert. Tant que votre application n’est pas complètement fermée, le
nouveau service worker reste dans l’état en attente d’activation. Ce comportement n’est
pas propre à Blazor ; c’est un comportement standard des plateformes web.

Cela dérange souvent les développeurs qui essaient de tester les mises à jour de leurs
ressources de service worker ou de celles mises en cache hors connexion. Dans les outils
de développement du navigateur, vous pouvez voir quelque chose de similaire à ceci :

Tant que la liste des « clients », qui sont des onglets ou fenêtres affichant votre
application, n’est pas vide, le service worker reste en attente. Les service workers ont ce
comportement afin de garantir la cohérence. La cohérence implique que toutes les
ressources sont récupérées du même cache atomique.

Durant vos tests des modifications, il peut s’avérer pratique de sélectionner le lien
« skipWaiting » (Ignorer l’attente), comme dans la capture d’écran précédente, puis de
recharger la page. Vous pouvez automatiser ce comportement pour tous les utilisateurs
en codant votre service worker pour ignorer la phase d’attente et activer
immédiatement les mises à jour . Si vous ignorez la phase d’attente, vous renoncez à la
garantie que les ressources soient toujours récupérées de manière cohérente à partir de
la même instance de cache.

Les utilisateurs peuvent exécuter n’importe quelle version


historique de l’application
Les développeurs web s’attendent généralement à ce que les utilisateurs exécutent
uniquement la dernière version déployée de leur application web. C’est en effet le
comportement standard dans le modèle de distribution web classique. Toutefois, une
application PWA exécutable d’abord hors connexion s’apparente davantage à une
application mobile native, dont les utilisateurs n’exécutent pas toujours la dernière
version.
Comme nous l’avons expliqué dans la section Mises à jour en arrière-plan, après le
déploiement d’une mise à jour dans votre application, chaque utilisateur existant
continuera d’utiliser une version antérieure au moins une fois lors d’une découverte
ultérieure, car la mise à jour effectuée en arrière-plan sera activée seulement quand
l’utilisateur aura totalement quitté l’application. De plus, la version antérieure utilisée
n’est pas obligatoirement la précédente version que vous aviez déployée. La version
antérieure peut être n’importe laquelle des versions historiques, en fonction de la date
de la dernière mise à jour faite par l’utilisateur.

Cela peut poser un problème si les composants front-end et back-end de votre


application exigent un accord sur le schéma pour les requêtes d’API. Vous ne devez pas
déployer de modifications de schéma d’API présentant une incompatibilité descendante
avant d’être sûr que tous les utilisateurs ont effectué la mise à niveau. Vous pouvez
sinon empêcher les utilisateurs d’utiliser des versions antérieures de l’application qui ne
sont pas compatibles. Cette exigence de scénario est identique pour les applications
mobiles natives. Si vous déployez un changement cassant dans des API serveur,
l’application cliente cesse de fonctionner pour les utilisateurs qui n’ont pas encore fait la
mise à jour.

Faites votre possible pour ne pas déployer de changements cassants dans vos API back-
end. Si vous ne pouvez pas faire autrement, essayez d’utiliser des API de service worker
standard telles que ServiceWorkerRegistration pour déterminer si l’application est à
jour et, si ce n’est pas le cas, pour empêcher son utilisation.

Interférence avec les pages rendues par le serveur


Comme décrit dans la section Prise en charge des pages rendues par le serveur, si vous
souhaitez contourner le comportement du service worker qui consiste à retourner le
contenu /index.html pour toutes les requêtes de navigation, modifiez la logique dans
votre service worker.

Tout le contenu du manifeste des ressources du service


worker est mis en cache par défaut
Comme vous l’avez vu dans la section Contrôle de la mise en cache des ressources, le
fichier service-worker-assets.js est généré au moment de la build et il liste toutes les
ressources que le service worker doit récupérer (fetch) et mettre en cache.

Étant donné que cette liste inclut par défaut tout ce qui est envoyé vers wwwroot , y
compris le contenu fourni par des packages et projets externes, vous devez être prudent
dans le choix du contenu mis dans cette liste. Si le répertoire wwwroot contient des
millions d’images, le service worker tentera de récupérer et mettre en cache la totalité
de ces images, ce qui entraînera une consommation de bande passante excessive et
probablement l’échec de la tentative.

Implémentez une logique arbitraire pour contrôler quel sous-ensemble du contenu du


manifeste doit être récupéré et mis en cache, en modifiant la fonction onInstall dans
service-worker.published.js .

Interaction avec l’authentification


Le modèle PWA peut être utilisé conjointement avec l’authentification. Une application
PWA exécutable hors connexion peut également prendre en charge l’authentification
lorsque l’utilisateur dispose d’une connectivité réseau initiale.

Quand un utilisateur n’a pas de connectivité réseau, il ne peut pas s’authentifier ni


obtenir de jetons d’accès. Par défaut, toute tentative d’accès à la page de connexion
sans accès réseau génère un message « erreur réseau ». Vous devez concevoir un flux
d’interface utilisateur qui permet à l’utilisateur d’effectuer des tâches utiles en mode
hors connexion sans qu’il ait besoin de s’authentifier ou d’obtenir des jetons d’accès.
Une autre solution est de concevoir l’application pour qu’elle échoue correctement
lorsque le réseau n’est pas disponible. Si l’application ne peut pas être conçue de façon
à prendre en charge ces scénarios, il est préférable de ne pas fournir de mode hors
connexion.

Quand une application conçue pour une utilisation en ligne et hors connexion est de
nouveau en ligne :

L’application peut avoir besoin de provisionner un nouveau jeton d’accès.


L’application doit détecter si un autre utilisateur est connecté au service afin qu’elle
puisse appliquer les opérations sur le compte de l’utilisateur qui avaient été faites
en mode hors connexion.

Pour créer une application PWA exécutable hors connexion qui interagit avec
l’authentification :

Remplacez AccountClaimsPrincipalFactory<TAccount> par une fabrique qui


enregistre le dernier utilisateur connecté et qui utilise l’utilisateur enregistré
lorsque l’application est hors connexion.
Mettez les opérations en file d’attente pendant que l’application est hors
connexion et appliquez-les une fois que l’application est de nouveau en ligne.
À la déconnexion, supprimez l’utilisateur enregistré.
L’exemple d’application CarChecker illustre les approches vues plus haut. Consultez
les informations sur les composants suivants de l’application :

OfflineAccountClaimsPrincipalFactory

( Client/Data/OfflineAccountClaimsPrincipalFactory.cs )
LocalVehiclesStore ( Client/Data/LocalVehiclesStore.cs )

Composant LoginStatus ( Client/Shared/LoginStatus.razor )

Ressources supplémentaires
Résoudre les problèmes d’intégrité avec le script PowerShell
Négociation entre origines SignalR côté client pour l’authentification

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Héberger et déployer ASP.NET Core
Blazor
Article • 09/02/2024

Cet article explique comment héberger et déployer des applications Blazor.

Publier l’application
Les applications sont publiées pour le déploiement dans la configuration Release.

Visual Studio

1. Sélectionnez la commande Publier {APPLICATION} dans le menu Générer, où


l’espace réservé {APPLICATION} est le nom de l’application.
2. Sélectionnez l’onglet Cible de publication. Pour publier localement,
sélectionnez Dossier.
3. Acceptez l’emplacement par défaut dans le champ Choisir un dossier ou
spécifiez un autre emplacement. Sélectionnez le bouton Publish .

La publication de l’application déclenche une restauration des dépendances du projet et


crée le projet avant de créer les ressources pour le déploiement. Dans le cadre du
processus de génération, les assemblys et méthodes inutilisés sont supprimés pour
réduire la durée du chargement et la taille du téléchargement de l’application.

Emplacements de publication :

Application web Blazor : par défaut, l’application est publiée dans le dossier
/bin/Release/{TARGET FRAMEWORK}/publish . Déployez le contenu du dossier

publish sur l’hôte.


Blazor WebAssembly : par défaut, l’application est publiée dans le dossier
bin\Release\net8.0\browser-wasm\publish\ . Pour déployer l’application en tant

que site statique, copiez le contenu du dossier wwwroot sur l’hôte de site statique.

Dans les chemins d’accès précédents, {TARGET FRAMEWORK} désigne la version cible de
.Net Framework (par exemple, net8.0 ).

IIS
Pour héberger une application Blazor dans IIS, consultez les ressources suivantes :

Hébergement IIS
Publier une application ASP.NET Core sur IIS
Héberger ASP.NET Core sur Windows avec IIS
Héberger et déployer des applications Blazor ASP.NET Core côté serveur :
applications serveur s’exécutant sur IIS, y compris IIS avec des machines virtuelles
Azure exécutant le système d’exploitation Windows et Azure App Service.
Héberger et déployer ASP.NET Core Blazor WebAssembly : Inclut des conseils
supplémentaires pour Blazor WebAssembly applications hébergées sur IIS,
notamment l’hébergement de sites statiques, les fichiers personnalisés web.config ,
la réécriture d’URL, les sous-applications, la compression et l’hébergement de
fichiers statiques dans le stockage Azure.
Hébergement de sous-applications IIS
Suivez les conseils de la section Chemin d’accès de base de l’application pour
l’application Blazor avant de publier l’application. Les exemples utilisent
/CoolApp comme chemin de base de l’application et montrent comment obtenir

le chemin de base à partir des paramètres d’application ou d’autres fournisseurs


de configuration.
Suivez les instructions de configuration de sous-application dans la
Configuration avancée. Le chemin d’accès du dossier de la sous-application
sous le site racine devient le chemin d’accès virtuel de la sous-application. Pour
un chemin d’accès de base d’application de /CoolApp , l’application Blazor est
placée dans un dossier nommé CoolApp sous le site racine et la sous-application
prend un chemin d’accès virtuel de /CoolApp .

Le partage d’un pool d’applications entre des applications ASP.NET Core n’est pas pris
en charge, y compris pour les applications Blazor. Utilisez un pool d’applications par
application lors de l’hébergement avec IIS et évitez d’utiliser les répertoires virtuels d’IIS
pour héberger plusieurs applications.

Chemin de base de l’application


Le chemin de base de l’application est le chemin URL racine de l’application. Le routage
réussi dans les applications Blazor nécessite la configuration de l’infrastructure pour tout
chemin d’URL racine qui n’est pas au chemin / de base de l’application par défaut.

Prenez en compte l’application ASP.NET Core et la sous-application Blazor suivantes :

L’application ASP.NET Core est nommée MyApp :


L’application réside physiquement dans d:/MyApp .
Les demandes sont reçues à l’adresse https://www.contoso.com/{MYAPP
RESOURCE} .

Une application Blazor nommée CoolApp est une sous-application de MyApp :


La sous-application réside physiquement dans d:/MyApp/CoolApp .
Les demandes sont reçues à l’adresse
https://www.contoso.com/CoolApp/{COOLAPP RESOURCE} .

Sans configuration supplémentaire pour CoolApp , la sous-application de ce scénario n’a


aucune connaissance de l’emplacement où elle réside sur le serveur. Par exemple,
l’application ne peut pas construire des URL relatives correctes pour ses ressources sans
savoir qu’elle réside à l’emplacement du chemin URL relatif /CoolApp/ . Ce scénario
s’applique également dans divers scénarios de proxy inverse et d’hébergement quand
une application n’est pas hébergée à l’emplacement d’un chemin URL racine.

Arrière-plan
La destination d’une balise d’ancrage (href ) peut être composée d’un des deux points
de terminaison suivants :

Emplacements absolus qui incluent un schéma (par défaut, le schéma de la page


s’il est omis), l’hôte, le port et le chemin d’accès ou simplement une barre oblique
( / ) suivie du chemin d’accès.

Exemples : https://example.com/a/b/c or /a/b/c

Emplacements relatifs qui contiennent uniquement un chemin d’accès et ne


commencent pas par une barre oblique ( / ). Celles-ci sont résolues par rapport à
l’URL du document actuel ou à la valeur de la balise <base> , le cas échéant.

Exemple : a/b/c

La présence d’une barre oblique de fin ( / ) dans un chemin d’accès de base d’une
application configuré est importante pour calculer le chemin de base des URL de
l’application. Par exemple, https://example.com/a a un chemin d’accès de base de
https://example.com/ , tandis qu’avec https://example.com/a/ une barre oblique de fin

a un chemin d’accès de base de https://example.com/a .

Il existe trois sources de liens qui se rapportent à Blazor dans les applications ASP.NET
Core :

Les URL des composants Razor ( .razor ) sont généralement relatives.


Les URL dans les scripts, telles que les scripts Blazor ( blazor.*.js ) sont relatifs au
document.

Si vous affichez une application Blazor à partir de différents documents (par exemple,
/Admin/B/C/ et /Admin/D/E/ ), vous devez prendre en compte le chemin d’accès de base

de l’application, sinon le chemin d’accès de base sera différent lors du rendu de


l’application dans chaque document et les ressources seront extraites des URL
incorrectes.

Il existe deux approches pour résoudre correctement le problème des liens relatifs :

Mappez dynamiquement les ressources en utilisant le document sur lequel elles


ont été rendues en tant que racine.
Définissez un chemin d’accès de base cohérent pour le document et mappez les
ressources sous ce chemin d’accès de base.

La première option est plus compliquée et n’est pas l’approche la plus classique, car elle
rend la navigation différente pour chaque document. Prenons l’exemple suivant pour le
rendu d’une page /Something/Else :

Rendue sous /Admin/B/C/ , la page est rendue avec un chemin d’accès


/Admin/B/C/Something/Else .

Rendu sous /Admin/D/E/ , la page est rendue au même chemin d’accès de


/Admin/B/C/Something/Else .

Dans la première approche, le routage propose IDynamicEndpointMetadata et


MatcherPolicy, qui, combinés, peuvent être la base de l’implémentation d’une solution
complètement dynamique qui détermine au moment de l’exécution la façon dont les
requêtes sont routées.

Pour la deuxième option, qui est l’approche habituelle adoptée, l’application définit le
chemin d’accès de base dans le document et mappe les points de terminaison du
serveur aux chemins d’accès sous la base. Les conseils suivants adoptent cette approche.

Blazor côté serveur


Mappez le hub SignalR d’une application Blazor côté serveur en transmettant le chemin
d’accès MapBlazorHub au fichier Program :

C#

app.MapBlazorHub("base/path");
L’avantage de l’utilisation de MapBlazorHub est que vous pouvez mapper des modèles,
tels que "{tenant}" et pas seulement des chemins concrets.

Vous pouvez également mapper le hub SignalR lorsque l’application se trouve dans un
dossier virtuel avec un pipeline de middlewares ramifiés. Dans l’exemple suivant, les
requêtes vers /base/path/ sont traitées par le hub SignalR de Blazor :

C#

app.Map("/base/path/", subapp => {


subapp.UsePathBase("/base/path/");
subapp.UseRouting();
subapp.UseEndpoints(endpoints => endpoints.MapBlazorHub());
});

Configurez la balise <base> , conformément aux instructions de la section Configurer le


chemin d’accès de base de l’application.

Blazor WebAssembly autonome


Dans une application Blazor WebAssembly autonome, seule la balise <base> est
configurée, conformément aux instructions de la section Configurer le chemin d’accès
de base de l’application.

Configurer le chemin d’accès de base de l’application


Pour fournir la configuration du Blazorchemin d’accès de base de
https://www.contoso.com/CoolApp/ , définissez le chemin d’accès de base de

l’application, également appelé chemin racine relatif.

En configurant le chemin d’accès de base de l’application, un composant qui ne figure


pas dans le répertoire racine peut construire des URL relatives au chemin racine de
l’application. Des composants situés à différents niveaux de la structure de répertoires
peuvent générer des liens vers d’autres ressources à des emplacements quelconques
dans l’application. Le chemin de base de l’application sert également à intercepter les
liens hypertextes sélectionnés où la cible href du lien figure dans l’espace d’URI du
chemin de base de l’application. Le composant Router gère la navigation interne.

Dans de nombreux scénarios d’hébergement, le chemin URL relatif vers l’application est
la racine de l’application. Dans ces cas par défaut, le chemin de base de l’URL relative de
l’application est / configuré comme <base href="/" /> dans le contenu <head>.
7 Notes

Dans certains scénarios d’hébergement, tels que GitHub Pages et les sous-
applications IIS, le chemin d’accès de base de l’application doit être défini sur le
chemin URL relatif du serveur de l’application.

Dans une application Blazor côté serveur, utilisez l’une des approches suivantes :

Option 1 : Utilisez la balise <base> pour définir le chemin d’accès de base de


l’application (emplacement du <head> contenu) :

HTML

<base href="/CoolApp/">

La barre oblique de fin est requise.

Option 2 : Appelez UsePathBasepremier dans le pipeline de traitement des


requêtes de l’application ( Program.cs ) immédiatement après que le
WebApplicationBuilder est généré ( builder.Build() ) pour configurer le chemin
d’accès de base pour tout intergiciel suivant qui interagit avec le chemin d’accès
de requête :

C#

app.UsePathBase("/CoolApp");

L’appel de UsePathBase est recommandé quand vous souhaitez également


exécuter l’application Blazor Server localement. Par exemple, fournissez l’URL de
lancement dans Properties/launchSettings.json :

XML

"launchUrl": "https://localhost:{PORT}/CoolApp",

L’espace réservé {PORT} dans l’exemple précédent est le port qui correspond au
port sécurisé dans le chemin de configuration applicationUrl . L’exemple
suivant montre le profil de lancement complet d’une application sur le
port 7279 :

XML
"BlazorSample": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7279;http://localhost:5279",
"launchUrl": "https://localhost:7279/CoolApp",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

Pour plus d’informations sur le fichier launchSettings.json , consultez Utiliser


plusieurs environnements dans ASP.NET Core. Pour plus d’informations sur les
chemins d’accès et l’hébergement de base de l’applicationBlazor, consultez
<base href="/" /> ou l’alternative de balise de base pour l’intégration MVC
Blazor (dotnet/aspnetcore #43191) .

Blazor WebAssembly ( wwwroot/index.html ) autonome :

HTML

<base href="/CoolApp/">

La barre oblique de fin est requise.

7 Notes

Lors de l’utilisation de WebApplication (consultez Migrer de ASP.NET Core 5.0 vers


6.0), app.UseRouting doit être appelé après UsePathBase afin que l’Intergiciel de
routage puisse observer le chemin d’accès modifié avant la mise en
correspondance des itinéraires. Dans le cas contraire, les routes sont mises en
correspondance avant que le chemin ne soit réécrit par UsePathBase comme décrit
dans les articles Ordre des intergiciels et Routage.

Ne préfixez pas les liens dans l’ensemble de l’application avec une barre oblique. Évitez
d’utiliser un séparateur de segment de chemin ou utilisez la notation de chemin relatif
point-barre oblique ( ./ ) :

❌ Incorrect : <a href="/account">


✔️Correct : <a href="account">
✔️Correct : <a href="./account">

Dans les demandes d’API web Blazor WebAssembly avec le service HttpClient, confirmez
que les JSassistances ON (HttpClientJsonExtensions) ne préfixent pas les URL avec une
barre oblique ( / ) :

❌ Incorrect : var rsp = await client.GetFromJsonAsync("/api/Account");


✔️Correct : var rsp = await client.GetFromJsonAsync("api/Account");

Ne préfixez pas les liens relatifs du Gestionnaire de navigation avec une barre oblique.
Évitez d’utiliser un séparateur de segment de chemin d’accès ou utilisez la notation de
chemin d’accès relatif point-slash ( ./ ) ( Navigation est un NavigationManagerinjecté) :

❌ Incorrect : Navigation.NavigateTo("/other");
✔️Correct : Navigation.NavigateTo("other");
✔️Correct : Navigation.NavigateTo("./other");

Dans les configurations standard d’hébergement Azure/IIS, une configuration


supplémentaire n’est généralement pas nécessaire. Dans certains scénarios
d’hébergement non IIS et d’hébergement de proxy inverse, une configuration
d’intergiciel de fichiers statiques supplémentaire peut être nécessaire :

Pour délivrer correctement les fichiers statiques (par exemple,


app.UseStaticFiles("/CoolApp"); ).

Pour délivrer le script Blazor ( _framework/blazor.*.js ). Pour plus d’informations,


consultez Fichiers statiques ASP.NET CoreBlazor.

Pour une application Blazor WebAssembly avec un chemin URL relatif non racine (par
exemple, <base href="/CoolApp/"> ), l’application ne parvient pas à trouver ses
ressources quand elle est exécutée localement. Pour surmonter ce problème pendant le
développement local et les tests, vous pouvez fournir un argument de base de chemin
qui correspond à la valeur href de la balise <base> au moment de l’exécution. N’incluez
pas de barre oblique de fin. Pour passer l’argument de base de chemin quand vous
exécutez l’application localement, exécutez la commande dotnet run à partir du
répertoire de l’application avec l’option --pathbase :

CLI .NET

dotnet run --pathbase=/{RELATIVE URL PATH (no trailing slash)}

Pour une application Blazor WebAssembly avec le chemin URL relatif /CoolApp/ ( <base
href="/CoolApp/"> ), la commande est :

CLI .NET

dotnet run --pathbase=/CoolApp


Si vous préférez configurer le profil de lancement de l’application pour spécifier
pathbase automatiquement au lieu de le faire manuellement avec dotnet run ,

définissez la propriété commandLineArgs dans Properties/launchSettings.json . Ce qui


suit configure également l’URL de lancement ( launchUrl ) :

JSON

"commandLineArgs": "--pathbase=/{RELATIVE URL PATH (no trailing slash)}",


"launchUrl": "{RELATIVE URL PATH (no trailing slash)}",

Utilisation de CoolApp comme exemple :

JSON

"commandLineArgs": "--pathbase=/CoolApp",
"launchUrl": "CoolApp",

En utilisant dotnet run avec l’option --pathbase ou une configuration de profil de


lancement qui définit le chemin de base, l’application Blazor WebAssembly répond
localement à l’adresse http://localhost:port/CoolApp .

Pour plus d’informations sur le fichier launchSettings.json , consultez Utiliser plusieurs


environnements dans ASP.NET Core. Pour plus d’informations sur les chemins d’accès et
l’hébergement de base de l’applicationBlazor, consultez <base href="/" /> ou
l’alternative de balise de base pour l’intégration MVC Blazor (dotnet/aspnetcore
#43191) .

Obtenir le chemin de base de l’application à


partir de la configuration
L’aide suivante explique comment obtenir le chemin pour la balise <base> à partir d’un
fichier de paramètres d’application pour différents environnements.

Ajoutez le fichier de paramètres d’application à l’application. L’exemple suivant concerne


l’environnement Staging ( appsettings.Staging.json ) :

JSON

{
"AppBasePath": "staging/"
}
Dans une application Blazor côté serveur, chargez le chemin de base à partir de la
configuration dans le contenu <head> :

razor

@inject IConfiguration Config

...

<head>
...
<base href="/@(Config.GetValue<string>("AppBasePath"))" />
...
</head>

Une application côté serveur peut également obtenir la valeur à partir de la


configuration pour UsePathBase. Placez le code suivant en premier dans le pipeline de
traitement des requêtes de l’application ( Program.cs ), immédiatement après la
génération de WebApplicationBuilder ( builder.Build() ). L’exemple suivant utilise la clé
de configuration AppBasePath :

C#

app.UsePathBase($"/{app.Configuration.GetValue<string>("AppBasePath")}");

Dans une application Blazor WebAssembly côté client :

Supprimez la balise <base> de wwwroot/index.html :

diff

- <base href="..." />

Indiquez le chemin de base de l’application via un composant HeadContent dans


le composant App ( App.razor ) :

razor

@inject IConfiguration Config

...

<HeadContent>
<base href="/@(Config.GetValue<string>("AppBasePath"))" />
</HeadContent>
S’il n’y a aucune valeur de configuration à charger, par exemple dans des
environnements non intermédiaires, le href précédent est résolu en chemin racine / .

Les exemples de cette section se concentrent sur l’utilisation des paramètres


d’application pour fournir le chemin de base de l’application, mais l’approche consistant
à lire le chemin à partir de IConfiguration est valable pour tout fournisseur de
configuration. Pour plus d’informations, consultez les ressources suivantes :

C ASP.NET Core Blazor


Configuration dans ASP.NET Core

Configuration Blazor Server MapFallbackToPage


Cette section s’applique uniquement aux applications Blazor Server. MapFallbackToPage
n’est pas pris en charge dans Blazor Web Apps et les applications Blazor WebAssembly.

Dans les scénarios où une application nécessite une zone distincte avec des ressources
et des composants Razor personnalisés :

Créez un dossier dans le dossier Pages de l’application pour y placer les


ressources. Par exemple, une section d’administrateur d’une application est créée
dans un nouveau dossier nommé Admin ( Pages/Admin ).

Créez une page racine ( _Host.cshtml ) pour la zone. Par exemple, créez un fichier
Pages/Admin/_Host.cshtml à partir de la page racine principale de l’application

( Pages/_Host.cshtml ). Ne fournissez pas de directive @page dans la page _Host


d’administration.

Ajoutez une disposition au dossier de la zone (par exemple,


Pages/Admin/_Layout.razor ). Dans la disposition de la zone distincte, définissez

l’étiquette <base> href pour qu’elle corresponde au dossier de la zone (par


exemple, <base href="/Admin/" /> ). À des fins de démonstration, ajoutez ~/ aux
ressources statiques dans la page. Par exemple :
~/css/bootstrap/bootstrap.min.css
~/css/site.css

~/BlazorSample.styles.css (l’espace de noms de l’exemple d’application est

BlazorSample )
~/_framework/blazor.server.js (script Blazor)

Si la zone doit avoir son propre dossier de ressources statiques, ajoutez le dossier
et spécifiez son emplacement sur Middleware de fichiers statiques dans
Program.cs (par exemple, app.UseStaticFiles("/Admin/wwwroot") ).
Les composants Razor sont ajoutés dans le dossier de la zone. Au minimum,
ajoutez un composant Index au dossier de zone avec la directive @page correcte
pour cette zone. Par exemple, ajoutez un fichier Pages/Admin/Index.razor en
fonction du fichier Pages/Index.razor par défaut de l’application. Indiquez la zone
Administration comme modèle de route en haut du fichier ( @page "/admin" ).
Ajoutez des composants supplémentaires si nécessaire. Par exemple,
Pages/Admin/Component1.razor avec une directive @page et un modèle de route

@page "/admin/component1 .

Dans Program.cs , appelez MapFallbackToPage pour le chemin de demande de la


zone immédiatement avant le chemin de la page racine de secours vers la page
_Host :

C#

...
app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("~/Admin/{*clientroutes:nonfile}",
"/Admin/_Host");
app.MapFallbackToPage("/_Host");

app.Run();

Déploiement
Pour obtenir des conseils de déploiement, consultez les rubriques suivantes :

Héberger et déployer ASP.NET Core Blazor WebAssembly


Héberger et déployer des applications Blazor ASP.NET Core côté serveur

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Héberger et déployer des applications
Blazor côté serveur
Article • 09/02/2024

Cet article explique comment héberger et déployer une application Blazor côté serveur
avec ASP.NET Core.

Valeurs de configuration de l’hôte


Les applications Blazor côté serveur peuvent accepter les valeurs de configuration
d’hôte générique.

Déploiement
En utilisant un modèle d’hébergement côté serveur, Blazor est exécutée sur le serveur à
partir d’une application ASP.NET Core. Les mises à jour de l’interface utilisateur, la
gestion des événements et les appels JavaScript sont gérés par le biais d’une connexion
SignalR.

Un serveur web capable d’héberger une application ASP.NET Core est nécessaire. Visual
Studio inclut un modèle de projet d’application côté serveur. Pour plus d’informations
sur les modèles de projet Blazor, consultez ASP.NET structure de projet principale
Blazor.

Extensibilité
Lorsque vous envisagez la scalabilité d’un serveur unique (scale-up), la mémoire
disponible pour une application est probablement la première ressource que
l’application épuise à mesure que les demandes des utilisateurs augmentent. La
mémoire disponible sur le serveur affecte :

Le nombre de circuits actifs qu’un serveur peut prendre en charge.


La latence de l’interface utilisateur sur le client.

Pour obtenir des conseils sur la génération d’applications côté Blazor serveur sécurisées
et évolutives, consultez les ressources suivantes :

Conseils d’atténuation des menaces pour le rendu statique côté serveur de Blazor
ASP.NET Core
Conseils d’atténuation des menaces pour le rendu interactif côté serveur de Blazor
ASP.NET Core

Chaque circuit utilise environ 250 Ko de mémoire pour une application de style Hello
World minimale. La taille d’un circuit dépend du code de l’application et des exigences
de maintenance d’état associées à chaque composant. Nous vous recommandons de
mesurer les demandes de ressources pendant le développement de votre application et
de votre infrastructure, mais la base de référence suivante peut être un point de départ
dans la planification de votre cible de déploiement : si vous prévoyez que votre
application prenne en charge 5 000 utilisateurs simultanés, envisagez de budgéter au
moins 1,3 Go de mémoire serveur pour l’application (ou environ 273 Ko par utilisateur).

Configuration SignalR
Les conditions d’hébergement et de mise à l’échelle de SignalR s’appliquent aux
applications Blazor qui utilisent SignalR.

Transports
Blazor fonctionne le mieux lors de l’utilisation de WebSockets en tant que transport
SignalR en raison d’une latence plus faible, d’une meilleure fiabilité et d’une sécurité
améliorée. L’interrogation longue est utilisée par SignalR lorsque WebSockets n’est
pas disponible ou lorsque l’application est explicitement configurée pour utiliser
l’interrogation longue. Lors du déploiement sur Azure App Service, configurez
l’application pour utiliser WebSockets dans les paramètres du Portail Azure pour le
service. Pour plus d’informations sur la configuration de l’application pour Azure App
Service, consultez les instructions de publication de SignalR.

Un avertissement de console s’affiche si l’interrogation longue est utilisée :

Échec de la connexion via WebSockets, avec le transport de secours d’interrogation


longue. Cela peut être dû au blocage de la connexion par un VPN ou un proxy.

Échecs de déploiement global et de connexion


Recommandations pour les déploiements globaux vers des centres de données
géographiques :

Déployez l’application dans les régions où résident la plupart des utilisateurs.


Prenez en compte la latence accrue du trafic entre les continents.
Pour l’hébergement Azure, utilisez Azure SignalR Service.
Si une application déployée affiche fréquemment l’interface utilisateur de reconnexion
en raison de délais d’expiration de ping causés par la latence Internet, allongez les délais
d’expiration du serveur et du client :

Serveur

Au moins le double du temps d’aller-retour maximal attendu entre le client et le


serveur. Testez, surveillez et révisez les délais d’expiration en fonction des besoins.
Pour le hub SignalR, définissez ClientTimeoutInterval (valeur par défaut : 30
secondes) et HandshakeTimeout (valeur par défaut : 15 secondes). L’exemple
suivant suppose que KeepAliveInterval utilise la valeur par défaut de 15 secondes.

) Important

KeepAliveInterval n’est pas directement lié à l’affichage de l’interface


utilisateur de reconnexion. L’intervalle Keep-Alive n’a pas nécessairement
besoin d’être modifié. Si le problème d’apparence de l’interface utilisateur de
reconnexion est dû à des délais d’expiration, ClientTimeoutInterval et
HandshakeTimeout peuvent être augmentés, et l’intervalle Keep-Alive peut
rester le même. La considération importante est que si vous modifiez
l’intervalle de Keep-Alive, vous devez vous assurer que la valeur du délai
d’expiration du client est au moins le double de la valeur de l’intervalle Keep-
Alive, et que l’intervalle Keep-Alive sur le client correspond au paramètre du
serveur.

Dans l’exemple suivant, ClientTimeoutInterval est augmenté à 60 secondes et


HandshakeTimeout est augmenté à 30 secondes.

Dans le fichier Program.cs du projet de serveur :

C#

builder.Services.AddRazorComponents().AddInteractiveServerComponents()
.AddHubOptions(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});

Pour plus d’informations, consultez Conseils relatifs à ASP.NET Core BlazorSignalR.

Client
En règle générale, doublez la valeur utilisée pour le KeepAliveInterval du serveur pour
définir le délai d’expiration du serveur du client ( withServerTimeout ou ServerTimeout,
par défaut : 30 secondes).

) Important

L’intervalle Keep-Alive ( withKeepAliveInterval ou KeepAliveInterval) n’est pas


directement lié à l’affichage de l’interface utilisateur de reconnexion. L’intervalle
Keep-Alive n’a pas nécessairement besoin d’être modifié. Si le problème
d’apparence de l’interface utilisateur de reconnexion est dû à des délais
d’expiration, le délai d’expiration serveur peut être augmenté, et l’intervalle Keep-
Alive peut rester le même. La considération importante est que si vous modifiez
l’intervalle de Keep-Alive, vous devez vous assurer que la valeur du délai
d’expiration est au moins le double de la valeur de l’intervalle Keep-Alive, et que
l’intervalle Keep-Alive sur le serveur correspond au paramètre du client.

Dans l’exemple suivant, une valeur personnalisée de 60 secondes est utilisée pour
le délai d’expiration du serveur.

Dans la configuration de démarrage d’une application Blazor côté serveur, après la


balise <script> du script Blazor ( blazor.*.js ).

Application web Blazor :

HTML

<script>
Blazor.start({
circuit: {
configureSignalR: function (builder) {
builder.withServerTimeout(60000);
}
}
});
</script>

Blazor Server:

HTML

<script>
Blazor.start({
configureSignalR: function (builder) {
builder.withServerTimeout(60000);
}
});
</script>

Lorsque vous créez une connexion hub dans un composant, définissez ServerTimeout
(par défaut : 30 secondes) au niveau de HubConnectionBuilder. Définissez
HandshakeTimeout (valeur par défaut : 15 secondes) au niveau de la HubConnection
générée.

L’exemple suivant est basé sur le composant Index dans le didacticiel SignalR avec
Blazor. Le délai d’expiration du serveur est augmenté à 60 secondes, et le délai
d’expiration de l’établissement d’une liaison est augmenté à 30 secondes :

C#

protected override async Task OnInitializedAsync()


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.WithServerTimeout(TimeSpan.FromSeconds(60))
.Build();

hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

hubConnection.On<string, string>("ReceiveMessage", (user, message) =>


...

await hubConnection.StartAsync();
}

Lors de la modification des valeurs du délai d’expiration du serveur (ServerTimeout) ou


de l’intervalle Keep-Alive (KeepAliveInterval :

Le délai d’expiration du serveur doit être au moins le double de la valeur affectée à


l’intervalle Keep-Alive.
L’intervalle Keep-Alive doit être inférieur ou égal à la moitié de la valeur affectée au
délai d’expiration du serveur.

Pour plus d’informations, consultez Conseils relatifs à ASP.NET Core BlazorSignalR.

Service Azure SignalR


Nous vous recommandons d’utiliser le Service Azure SignalR pour les applications Blazor
côté serveur. Le service fonctionne conjointement avec le hub Blazor de l’application
pour effectuer un scale-up d’une application Blazor côté serveur vers un grand nombre
de connexions SignalR simultanées. De plus, la portée générale et les centres de
données hautes performances du service SignalR contribuent de manière significative à
réduire la latence en raison de la zone géographique.

) Important

Lorsque les WebSockets sont désactivés , Azure App Service simule une
connexion en temps réel à l’aide de l’interrogation longue HTTP. L’interrogation
longue HTTP est sensiblement plus lente que l’exécution avec WebSockets activé,
qui n’utilise pas l’interrogation pour simuler une connexion client-serveur. Dans le
cas où l’interrogation longue doit être utilisée, vous devrez peut-être configurer
l’intervalle d’interrogation maximal ( MaxPollIntervalInSeconds ), qui définit
l’intervalle d’interrogation maximal autorisé pour les connexions d’interrogation
longue dans le service Azure SignalR si le service passe de WebSockets à
l’interrogation longue. Si la demande d’interrogation suivante ne vient pas dans
MaxPollIntervalInSeconds , le service Azure SignalR nettoie la connexion cliente.

Notez que le service Azure SignalR nettoie également les connexions lorsque la
taille de la mémoire tampon en attente d’écriture est supérieure à 1 Mo pour
garantir les performances du service. La valeur par défaut de
MaxPollIntervalInSeconds est 5 secondes. Le paramètre est limité à 1 à 300

secondes.

Nous vous recommandons d’utiliser WebSockets pour les applications Blazor


côté serveur déployées sur Azure App Service. Le service Azure SignalR utilise des
WebSockets par défaut. Si l’application n’utilise pas le service AzureSignalR,
consultez Publier une application ASP.NET Core SignalR pour Azure App Service.

Pour plus d'informations, voir :

Qu’est-ce qu’Azure SignalR Service ?


Guide des performances pour Azure SignalR Service
Publier une application ASP.NET Core SignalR sur Azure App Service

Configuration
Pour configurer une application pour le service Azure SignalR, l’application doit prendre
en charge les sessions persistantes, où les clients sont redirigés vers le même serveur lors
de la préversion. L’option ServerStickyMode ou la valeur de configuration est définie sur
Required . En règle générale, une application crée la configuration à l’aide de l’une des

approches suivantes :
Program.cs :

C#

builder.Services.AddSignalR().AddAzureSignalR(options =>
{
options.ServerStickyMode =
Microsoft.Azure.SignalR.ServerStickyMode.Required;
});

Configuration (utilisez l’une des approches suivantes) :

Dans appsettings.json :

JSON

"Azure:SignalR:ServerStickyMode": "Required"

Paramètres d’application de configuration> d’AppService dans le Portail Azure


(Nom : Azure__SignalR__ServerStickyMode , Valeur : Required ). Cette approche
est adoptée automatiquement pour l’application si vous approvisionnez le
service AzureSignalR.

7 Notes

L’erreur suivante est générée par une application qui n’a pas activé les sessions
persistantes Azure SignalR Service :

blazor.server.js:1 Erreur non interceptée (dans promise) : Appel annulé en


raison de la fermeture de la connexion sous-jacente.

Approvisionnez le Service Azure SignalR


Pour approvisionner le service Azure SignalR pour une application dans Visual Studio :

1. Créez un profil de publication Azure Apps dans Visual Studio pour l’application .
2. Ajoutez la SignalR dépendance Azure Service au profil. Si l’abonnement Azure n’a
pas de instance de service Azure SignalR préexistant à affecter à l’application,
sélectionnez Créer un nouveau service Azure SignalR instance pour
approvisionner un nouveau service instance.
3. Publiez l’application sur Azure.
L’approvisionnement du service Azure SignalR dans Visual Studio active
automatiquement les sessions persistantes et ajoute la chaîne de connexion SignalR à la
configuration du service d’application.

Scalabilité sur les Azure Container Apps


La mise à l’échelle des applications Blazor côté serveur sur Azure Container Apps
nécessite des considérations spécifiques en plus de l’utilisation du service AzureSignalR.
En raison de la façon dont le routage des requêtes est géré, le service de protection des
données ASP.NET Core doit être configuré pour conserver les clés dans un emplacement
centralisé auquel toutes les instances de conteneur peuvent accéder. Les clés peuvent
être stockées dans Stockage Blob Azure et protégées avec Azure Key Vault. Le service de
protection des données utilise les clés pour désérialiser les composants Razor.

7 Notes

Pour une exploration plus approfondie de ce scénario et de la mise à l’échelle des


applications conteneur, consultez Mise à l’échelle ASP.NET Core Apps sur Azure.
Le tutoriel explique comment créer et intégrer les services nécessaires pour
héberger des applications sur Azure Container Apps. Les étapes de base sont
également fournies dans cette section.

1. Pour configurer le service de protection des données afin qu’il utilise Stockage
Blob Azure et Azure Key Vault, référencez les packages NuGet suivants :

Azure.Identity : fournit les classes pour travailler avec les services de


gestion des identités et des accès Azure.
Microsoft.Extensions.Azure : fournit des méthodes d’extension utiles pour
effectuer des configurations Azure de base.
Azure.Extensions.AspNetCore.DataProtection.Blobs : permet de stocker
ASP.NET Core clés de protection des données dans Stockage Blob Azure afin
que les clés puissent être partagées entre plusieurs instances d’une
application web.
Azure.Extensions.AspNetCore.DataProtection.Keys : permet de protéger les
clés au repos à l’aide de la fonctionnalité Azure Key Vault Key
Encryption/Wrapping.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET,
consultez les articles figurant sous Installer et gérer des packages dans Flux de
travail de la consommation des packages (documentation NuGet). Vérifiez
les versions du package sur NuGet.org .

2. Mettez à jour Program.cs avec le code mis en évidence suivant :

C#

using Azure.Identity;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Azure;
var builder = WebApplication.CreateBuilder(args);
var BlobStorageUri = builder.Configuration["AzureURIs:BlobStorage"];
var KeyVaultURI = builder.Configuration["AzureURIs:KeyVault"];

builder.Services.AddRazorPages();
builder.Services.AddHttpClient();
builder.Services.AddServerSideBlazor();

builder.Services.AddAzureClientsCore();

builder.Services.AddDataProtection()
.PersistKeysToAzureBlobStorage(new Uri(BlobStorageUri),
new
DefaultAzureCredential())
.ProtectKeysWithAzureKeyVault(new Uri(KeyVaultURI),
new
DefaultAzureCredential());
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Les modifications précédentes permettent à l’application de gérer la protection


des données à l’aide d’une architecture centralisée et évolutive.
DefaultAzureCredential détecte l’identité managée de l’application conteneur
après le déploiement du code sur Azure et l’utilise pour se connecter au stockage
blob et au coffre de clés de l’application.

3. Pour créer l’identité managée de l’application conteneur et lui accorder l’accès au


stockage blob et à un coffre de clés, procédez comme suit :
a. Dans le portail Azure, accédez à la page de vue d’ensemble de l’application
conteneur.
b. Dans la navigation de gauche, sélectionnez Connecteur de services.
c. Sélectionnez + Créer dans la navigation supérieure.
d. Dans le menu volant Créer une connexion, entrez les valeurs suivantes :

Conteneur : sélectionnez l’application conteneur que vous avez créée pour


héberger votre application.
Type de service : sélectionnez Stockage Blob.
Abonnement : sélectionnez l’abonnement qui possède l’application
conteneur.
Nom de la connexion : entrez un nom de scalablerazorstorage .
Type de client : sélectionnez .NET puis Suivant.

e. Sélectionnez Identité managée affectée par le système, puis Suivant.


f. Utilisez les paramètres réseau par défaut et sélectionnez Suivant.
g. Une fois qu’Azure a validé les paramètres, sélectionnez Créer.

Répétez les paramètres précédents pour le coffre de clés. Sélectionnez le service


de coffre de clés et la clé appropriés dans l’onglet De base.

Azure App Service sans Azure SignalR Service


Lorsque le service Azure SignalRn’est pas utilisé, le App Service nécessite une
configuration pour l’affinité ARR (Application Request Routing) et les WebSockets. Les
clients connectent leurs WebSockets directement à l’application, et non au service Azure
SignalR.

Utilisez les conseils suivants pour configurer l’application :

Configurez l’application dans Azure App Service.


Limites du plan App Service.

IIS
Lorsque vous utilisez IIS, activez :
WebSockets sur IIS.
Sessions persistantes avec routage des demandes d’application.

Kubernetes
Créez une définition d’entrée avec les annotations Kubernetes suivantes pour les
sessions persistantes :

YAML

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: <ingress-name>
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "affinity"
nginx.ingress.kubernetes.io/session-cookie-expires: "14400"
nginx.ingress.kubernetes.io/session-cookie-max-age: "14400"

Linux avec Nginx


Suivez les instructions pour une application ASP.NET Core SignalR avec les modifications
suivantes :

Remplacez le chemin d’accès location de /hubroute ( location /hubroute { ...


} ) par le chemin racine / ( location / { ... } ).

Supprimez la configuration de la mise en mémoire tampon du proxy


( proxy_buffering off; ), car le paramètre s’applique uniquement aux événements
envoyés par le serveur (SSE) , qui ne sont pas pertinents pour les interactions
client-serveur de l’applicationBlazor.

Pour plus d’informations et pour obtenir de l’aide sur la configuration, consultez les
ressources suivantes :

ASP.NET Core SignalR hébergement et mise à l’échelle de production


Héberger ASP.NET Core sur Linux avec Nginx
Configurer ASP.NET Core pour l’utilisation de serveurs proxy et d’équilibreurs de
charge
NGINX en tant que proxy WebSocket
Proxy WebSocket
Contactez des développeurs sur des forums d’assistance non-Microsoft :
Dépassement de capacité de la pile (étiquette : blazor)
Équipe ASP.NET Core Slack
Blazor Gitter

Linux avec Apache


Pour héberger une application Blazor derrière Apache sur Linux, configurez ProxyPass le
trafic HTTP et WebSockets.

Dans l’exemple suivant :

Kestrel le serveur s’exécute sur l’ordinateur hôte.


L’application écoute le trafic sur le port 5000.

ProxyPreserveHost On
ProxyPassMatch ^/_blazor/(.*) http://localhost:5000/_blazor/$1
ProxyPass /_blazor ws://localhost:5000/_blazor
ProxyPass / http://localhost:5000/
ProxyPassReverse / http://localhost:5000/

Activez les modules suivants :

a2enmod proxy
a2enmod proxy_wstunnel

Vérifiez les erreurs WebSockets dans la console du navigateur. Exemples d’erreurs :

Firefox ne peut pas établir de connexion au serveur à l’adresse ws://the-domain-


name.tld/_blazor?id=XXX
Erreur : échec du démarrage du transport « WebSockets » : Erreur : une erreur s’est
produite avec le transport.
Erreur : échec du démarrage du transport « Interrogation longue » : TypeError :
this.transport is undefined
Erreur : impossible de se connecter au serveur avec l’un des transports disponibles.
Échec des WebSockets
Erreur : impossible d’envoyer des données si la connexion n’est pas dans l’état «
Connecté ».

Pour plus d’informations et pour obtenir de l’aide sur la configuration, consultez les
ressources suivantes :
Héberger ASP.NET Core sur Linux avec Apache
Configurer ASP.NET Core pour l’utilisation de serveurs proxy et d’équilibreurs de
charge
Documentation Apache
Contactez des développeurs sur des forums d’assistance non-Microsoft :
Dépassement de capacité de la pile (étiquette : blazor)
Équipe ASP.NET Core Slack
Blazor Gitter

Mesurer la latence réseau


JS L’interopérabilité peut être utilisée pour mesurer la latence du réseau, comme le
montre l’exemple suivant.

MeasureLatency.razor :

razor

@inject IJSRuntime JS

<h2>Measure Latency</h2>

@if (latency is null)


{
<span>Calculating...</span>
}
else
{
<span>@(latency.Value.TotalMilliseconds)ms</span>
}

@code {
private DateTime startTime;
private TimeSpan? latency;

protected override async Task OnAfterRenderAsync(bool firstRender)


{
if (firstRender)
{
startTime = DateTime.UtcNow;
var _ = await JS.InvokeAsync<string>("toString");
latency = DateTime.UtcNow - startTime;
StateHasChanged();
}
}
}
Pour une expérience d’interface utilisateur raisonnable, nous recommandons une
latence soutenue de l’interface utilisateur de 250 ms ou moins.

Gestion de la mémoire
Sur le serveur, un nouveau circuit est créé pour chaque session utilisateur. Chaque
session utilisateur correspond au rendu d’un seul document dans le navigateur. Par
exemple, plusieurs onglets créent plusieurs sessions.

Blazor maintient une connexion constante au navigateur, appelé circuit, qui a lancé la
session. Les connexions peuvent être perdues à tout moment pour plusieurs raisons, par
exemple lorsque l’utilisateur perd la connectivité réseau ou ferme brusquement le
navigateur. En cas de perte de connexion, Blazor dispose d’un mécanisme de
récupération qui place un nombre limité de circuits dans un pool « déconnecté », ce qui
donne aux clients un délai limité pour se reconnecter et rétablir la session (par défaut : 3
minutes).

Après ce délai, Blazor libère le circuit et ignore la session. À partir de ce moment, le


circuit est éligible pour le garbage collection (GC) et est revendiqué lorsqu’une collecte
pour la génération GC du circuit est déclenchée. Un aspect important à comprendre est
que les circuits ont une longue durée de vie, ce qui signifie que la plupart des objets
enracinés par le circuit atteignent finalement Gen 2. Par conséquent, il se peut que vous
ne voyiez pas ces objets libérés tant qu’une collection Gen 2 n’a pas été effectuée.

Mesurer l’utilisation de la mémoire en général


Conditions préalables :

L’application doit être publiée dans la configuration Release. Les mesures de


configuration de débogage ne sont pas pertinentes, car le code généré n’est pas
représentatif du code utilisé pour un déploiement de production.
L’application doit s’exécuter sans débogueur attaché, car cela peut également
affecter le comportement de l’application et gâcher les résultats. Dans Visual
Studio, démarrez l’application sans débogage en sélectionnant>Démarrer sans
débogage dans la barre de menus ou Ctrl + F5 à l’aide du clavier.
Considérez les différents types de mémoire pour comprendre la quantité de
mémoire réellement utilisée par .NET. En règle générale, les développeurs
inspectent l’utilisation de la mémoire des applications dans le Gestionnaire des
tâches sur le système d’exploitation Windows, qui offre généralement une limite
supérieure de la mémoire réelle utilisée. Pour plus d’informations, consultez les
articles suivants :
Analyse des performances de la mémoire .NET : en particulier, consultez la
section Notions de base de la mémoire .
Flux de travail du diagnostic des problèmes de performances de mémoire (série
en trois parties) : les liens vers les trois articles de la série sont en haut de
chaque article de la série.

Utilisation de la mémoire appliquée à Blazor


Nous calculons la mémoire utilisée par blazor comme suit :

(Circuits actifs × mémoire par circuit) + (Circuits déconnectés × mémoire par circuit)

La quantité de mémoire utilisée par un circuit et le nombre maximal de circuits actifs


potentiels qu’une application peut gérer dépendent en grande partie de la façon dont
l’application est écrite. Le nombre maximal de circuits actifs possibles est
approximativement décrit par :

Mémoire / disponible maximale Mémoire = par circuit Nombre maximal de circuits


actifs potentiels

Pour qu’une fuite de mémoire se produise dans Blazor, les éléments suivants doivent
être vrais :

La mémoire doit être allouée par l’infrastructure, pas par l’application. Si vous
allouez un tableau de 1 Go dans l’application, l’application doit gérer la
suppression du tableau.
La mémoire ne doit pas être utilisée activement, ce qui signifie que le circuit n’est
pas actif et a été supprimé du cache des circuits déconnectés. Si le nombre
maximal de circuits actifs est en cours d’exécution, le manque de mémoire est un
problème de mise à l’échelle, et non une fuite de mémoire.
Un garbage collection (GC) pour la génération GC du circuit a été exécuté, mais le
garbage collector n’a pas pu revendiquer le circuit, car un autre objet de
l’infrastructure contient une référence forte au circuit.

Dans d’autres cas, il n’y a pas de fuite de mémoire. Si le circuit est actif (connecté ou
déconnecté), il est toujours en cours d’utilisation.

Si une collection pour la génération GC du circuit ne s’exécute pas, la mémoire n’est pas
libérée, car le garbage collector n’a pas besoin de libérer la mémoire à ce moment-là.

Si une collection pour une génération GC s’exécute et libère le circuit, vous devez valider
la mémoire par rapport aux statistiques GC, et non au processus, car .NET peut décider
de maintenir la mémoire virtuelle active.
Si la mémoire n’est pas libérée, vous devez trouver un circuit qui n’est ni actif ni
déconnecté et qui est enraciné par un autre objet dans l’infrastructure. Dans tous les
autres cas, l’impossibilité de libérer de la mémoire est un problème d’application dans le
code du développeur.

Réduisez l’utilisation de la mémoire


Adoptez l’une des stratégies suivantes pour réduire l’utilisation de la mémoire d’une
application :

Limitez la quantité totale de mémoire utilisée par le processus .NET. Pour plus
d’informations, consultez Options de configuration d’exécution pour le garbage
collection.
Réduisez le nombre de circuits déconnectés.
Réduisez le temps pendant lequel un circuit est autorisé à être à l’état déconnecté.
Déclenchez un garbage collection manuellement pour effectuer une collecte
pendant les périodes d’arrêt.
Configurez le garbage collection en mode Station de travail, qui déclenche de
manière agressive le garbage collection, au lieu du mode Serveur.

Actions supplémentaires
Capturez un vidage de la mémoire du processus lorsque les besoins en mémoire
sont élevés et identifiez les objets qui prennent le plus de mémoire et où ces
objets sont enracinés (ce qui contient une référence à eux).
.NET en mode Serveur ne libère pas immédiatement la mémoire sur le système
d’exploitation, sauf s’il doit le faire. Pour plus d’informations sur les paramètres de
fichier projet ( .csproj ) pour contrôler ce comportement, consultez Options de
configuration d’exécution pour le garbage collection.
Server GC suppose que votre application est la seule qui s’exécute sur le système
et peut utiliser toutes les ressources du système. Si le système a 50 Go, le garbage
collector cherche à utiliser la totalité de la mémoire disponible de 50 Go avant de
déclencher une collecte Gen 2.

Pour plus d’informations sur la configuration de la rétention de circuit déconnecté,


consultez ASP.NET Core BlazorSignalR instructions.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Héberger et déployer ASP.NET Core
Blazor WebAssembly
Article • 06/12/2023

Cet article explique comment héberger et déployer Blazor WebAssembly avec ASP.NET
Core, des réseaux de distribution de contenu (CDN), des serveurs de fichiers et GitHub
Pages.

Avec le modèle d’hébergement Blazor WebAssembly :

L’application Blazor, ses dépendances et le runtime .NET sont téléchargés sur le


navigateur en parallèle.
L’application est exécutée directement sur le thread d’interface utilisateur du
navigateur.

Cet article s’applique au scénario de déploiement dans lequel l’application Blazor est
placée sur un service ou serveur web d’hébergement statique et .NET n’est pas utilisé
pour servir l’application Blazor. Cette stratégie est abordée dans la section Déploiement
autonome, qui comprend des informations sur l’hébergement d’une application Blazor
WebAssembly en tant que sous-application IIS.

Format d’empaquetage Webcil pour les


assemblys .NET
Webcil est un format d’empaquetage convivial pour les assemblys .NET conçu pour
permettre l’utilisation de Blazor WebAssembly dans des environnements réseau
restrictifs. Les fichiers Webcil utilisent un wrapper WebAssembly standard, où les
assemblys sont déployés en tant que fichiers WebAssembly qui utilisent l’extension de
fichier .wasm standard.

Webcil est le format d’empaquetage par défaut lorsque vous publiez une application
Blazor WebAssembly. Pour désactiver l’utilisation de Webcil, définissez la propriété MS
Build suivante dans le fichier projet de l’application :

XML

<PropertyGroup>
<WasmEnableWebcil>false</WasmEnableWebcil>
</PropertyGroup>
Compilation anticipée (AOT)
Blazor WebAssembly prend en charge la compilation à l’avance (AOT), où vous pouvez
compiler votre code .NET directement en WebAssembly. La compilation AOT permet
d’améliorer les performances du runtime au détriment d’une plus grande taille
d’application.

Sans activation de la compilation AOT, les applications Blazor WebAssembly s’exécutent


sur le navigateur en utilisant un interpréteur de langage intermédiaire (IL) .NET
implémenté en WebAssembly avec une prise en charge partielle du runtime juste-à-
temps (JIT), qui est appelé de façon informelle Jiterpreter. Étant donné que le code IL
.NET est interprété, les applications s’exécutent généralement plus lentement qu’elles le
feraient sur un runtime juste-à-temps .NET côté serveur sans interprétation d’IL. La
compilation AOT résout ce problème de performances en compilant le code .NET d’une
application directement en WebAssembly pour l’exécution native de WebAssembly par
le navigateur. L’amélioration des performances avec la compilation AOT peut apporter
des améliorations spectaculaires pour les applications qui exécutent des tâches
sollicitant beaucoup l’UC. L’inconvénient de l’utilisation de la compilation AOT est que
les applications avec compilation AOT sont généralement plus volumineuses que leurs
équivalents interprétés par le langage intermédiaire, de sorte qu’elles prennent
généralement plus de temps à se télécharger sur le client lors de la première requête.

Pour obtenir des conseils sur l’installation des outils de génération de WebAssembly de
.NET, consultez Outils pour ASP.NET Core Blazor.

Pour activer la compilation AOT en WebAssembly, ajoutez la propriété


<RunAOTCompilation> définie sur true au fichier projet de l’application Blazor

WebAssembly :

XML

<PropertyGroup>
<RunAOTCompilation>true</RunAOTCompilation>
</PropertyGroup>

Pour compiler l’application en WebAssembly, publiez l’application. La publication de la


configuration Release garantit que la liaison en langage intermédiaire (IL) .NET est
également exécutée pour réduire la taille de l’application publiée :

CLI .NET

dotnet publish -c Release


La compilation AOT en WebAssembly n’est effectuée que lorsque le projet est publié. La
compilation AOT n’est pas utilisée lorsque le projet est exécuté pendant le
développement (environnement Development ), car la compilation AOT prend
généralement plusieurs minutes sur de petits projets, et peut être beaucoup plus longue
pour les projets plus volumineux. La réduction du temps de génération pour la
compilation AOT est en cours de développement pour les versions futures d’ASP.NET
Core.

La taille d’une application Blazor WebAssembly avec compilation AOT est généralement
supérieure à la taille de l’application si elle est compilée en langage intermédiaire .NET :

Bien que la différence de taille dépende de l’application, la plupart des applications


avec compilation AOT ont environ deux fois la taille de leurs versions compilées en
langage intermédiaire. Cela signifie que l’utilisation de la compilation AOT
représente un compromis entre les performances de temps de charge et les
performances d’exécution. Votre application particulière détermine si ce
compromis vaut la peine d’utiliser la compilation AOT. Les applications Blazor
WebAssembly gourmandes en processeur bénéficient généralement le plus de la
compilation AOT.

La plus grande taille d’une application compilée par AOT est due à deux
conditions :
Plus de code est nécessaire pour représenter les instructions en langage
intermédiaire .NET de haut niveau en WebAssembly natif.
AOT ne supprime pas les DLL managées lors de la publication de l’application.
Blazor nécessite les DLL pour les métadonnées de réflexion et pour prendre en
charge certaines fonctionnalités de runtime .NET. L’utilisation des DLL sur le
client augmente la taille du téléchargement, mais offre une expérience .NET plus
compatible.

7 Notes

Pour connaître les propriétés et cibles MSBuild Mono /WebAssembly, consultez


WasmApp.targets (dépôt GitHub dotnet/runtime) . La documentation officielle
pour les propriétés MSBuild courantes est planifiée selon les options de
configuration msbuild de blazor de document (dotnet/docs #27395) .

Découper .NET IL après la compilation


anticipée (AOT)
L’option WasmStripILAfterAOT MSBuild permet de supprimer le langage .NET
Intermediate Language (IL) pour les méthodes compilées après avoir effectué une
compilation AOT sur WebAssembly, ce qui réduit la taille du dossier _framework .

Dans le fichier projet de l’application :

XML

<PropertyGroup>
<RunAOTCompilation>true</RunAOTCompilation>
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
</PropertyGroup>

Ce paramètre supprime le code IL pour la plupart des méthodes compilées, y compris


les méthodes des bibliothèques et des méthodes dans l’application. Toutes les
méthodes compilées ne peuvent pas être supprimées, car certaines sont toujours
requises par l’interpréteur .NET au moment de l’exécution.

Pour signaler un problème avec l’option de découpage, soumettez un cas sur le


dotnet/runtime référentiel GitHub .

Désactivez la propriété de découpage si elle empêche votre application de s’exécuter


normalement :

XML

<WasmStripILAfterAOT>false</WasmStripILAfterAOT>

Nouvelle liaison du runtime


L’une des plus grandes parties d’une application Blazor WebAssembly est le runtime
.NET basé sur WebAssembly ( dotnet.wasm ) que le navigateur doit télécharger lors de la
première requête sur l’application par le navigateur d’un utilisateur. La nouvelle liaison
du runtime WebAssembly .NET réduit le code d’exécution inutilisé et améliore ainsi la
vitesse de téléchargement.

La nouvelle liaison du runtime nécessite l’installation des outils de génération


WebAssembly .NET. Pour plus d’informations, consultez Outils pour ASP.NET Core
Blazor.

Une fois les outils de génération WebAssembly .NET installés, la nouvelle liaison du
runtime est effectuée automatiquement lorsqu’une application est publiée dans la
configuration Release . La réduction de taille est particulièrement spectaculaire lorsque
l’on désactive la globalisation. Pour plus d’informations, consultez Globalisation et
localisation d’ASP.NET Core Blazor.

) Important

La nouvelle liaison du runtime supprime les méthodes .NET disponibles pour


JavaScript de l’instance de classe, sauf si elles sont protégées. Pour plus
d’informations, consultez Appeler des fonctions JavaScript à partir de méthodes
.NET dans ASP.NET Core Blazor.

Personnaliser le mode de chargement des


ressources de démarrage
Personnalisez la façon dont les ressources de démarrage sont chargées à l’aide de l’API
loadBootResource . Pour plus d’informations, consultez Démarrage ASP.NET Core Blazor.

Compression
Quand une application Blazor WebAssembly est publiée, la sortie est compressée
statiquement pendant la publication pour réduire la taille de l’application et ôter la
surcharge liée à la compression du runtime. Les algorithmes de compression suivants
sont utilisés :

Brotli (niveau le plus élevé)


Gzip

Blazor s’appuie sur l’hôte pour traiter les fichiers compressés appropriés. Lors de
l’hébergement d’une application autonome Blazor WebAssembly, des tâches
supplémentaires peuvent être nécessaires pour s’assurer que les fichiers compressés
statiquement sont traités :

Pour la configuration de la compression IIS web.config , consultez la section IIS :


compression Brotli et Gzip.
Lors de l'hébergement sur des solutions d'hébergement statiques qui ne prennent
pas en charge la négociation de contenu de fichier compressé statiquement,
envisagez de configurer l'application pour récupérer et décoder les fichiers
compressés Brotli :

Obtenez le décodeur Brotli JavaScript à partir du dépôt GitHub google/brotli . Le


fichier de décodeur minifié est nommé decode.min.js et se trouve dans le dossier js
du dépôt.

7 Notes

Si la version minifiée du script decode.js ( decode.min.js ) échoue, essayez d’utiliser


la version non minifiée ( decode.js ) à la place.

Mettez à jour l’application pour utiliser le décodeur.

Dans le fichier wwwroot/index.html , définissez autostart sur false sur la balise


<script> de Blazor :

HTML

<script src="_framework/blazor.webassembly.js" autostart="false"></script>

Après la balise <script> de Blazor et avant la balise fermante </body> , ajoutez le bloc
<script> de code JavaScript suivant.

Application webBlazor :

HTML

<script type="module">
import { BrotliDecode } from './decode.min.js';
Blazor.start({
webAssembly: {
loadBootResource: function (type, name, defaultUri, integrity) {
if (type !== 'dotnetjs' && location.hostname !== 'localhost' && type
!== 'configuration' && type !== 'manifest') {
return (async function () {
const response = await fetch(defaultUri + '.br', { cache: 'no-
cache' });
if (!response.ok) {
throw new Error(response.statusText);
}
const originalResponseBuffer = await response.arrayBuffer();
const originalResponseArray = new
Int8Array(originalResponseBuffer);
const decompressedResponseArray =
BrotliDecode(originalResponseArray);
const contentType = type ===
'dotnetwasm' ? 'application/wasm' : 'application/octet-
stream';
return new Response(decompressedResponseArray,
{ headers: { 'content-type': contentType } });
})();
}
}
}
});
</script>

Blazor WebAssembly autonome :

HTML

<script type="module">
import { BrotliDecode } from './decode.min.js';
Blazor.start({
loadBootResource: function (type, name, defaultUri, integrity) {
if (type !== 'dotnetjs' && location.hostname !== 'localhost' && type
!== 'configuration') {
return (async function () {
const response = await fetch(defaultUri + '.br', { cache: 'no-
cache' });
if (!response.ok) {
throw new Error(response.statusText);
}
const originalResponseBuffer = await response.arrayBuffer();
const originalResponseArray = new
Int8Array(originalResponseBuffer);
const decompressedResponseArray =
BrotliDecode(originalResponseArray);
const contentType = type ===
'dotnetwasm' ? 'application/wasm' : 'application/octet-stream';
return new Response(decompressedResponseArray,
{ headers: { 'content-type': contentType } });
})();
}
}
});
</script>

Pour plus d’informations sur le chargement des ressources de démarrage, consultez


Démarrage ASP.NET Core Blazor.

Pour désactiver la compression, ajoutez la propriété MSBuild CompressionEnabled au


fichier projet de l’application et définissez la valeur sur false :

XML

<PropertyGroup>
<CompressionEnabled>false</CompressionEnabled>
</PropertyGroup>
La propriété CompressionEnabled peut être passée à la commande dotnet publish avec la
syntaxe suivante dans un interpréteur de commandes :

CLI .NET

dotnet publish -p:CompressionEnabled=false

Réécriture d’URL pour un routage correct


Le routage des requêtes de composants de page dans une application Blazor
WebAssembly ne se résume pas simplement au routage de requêtes dans une
application Blazor Server. Considérez une application Blazor WebAssembly avec deux
composants :

Main.razor : Se charge à la racine de l’application et contient un lien vers le

composant About ( href="About" ).


About.razor : composant About .

Quand le document par défaut de l’application est demandé à l’aide de la barre


d’adresses du navigateur (par exemple, https://www.contoso.com/ ) :

1. Le navigateur effectue une requête.


2. La page par défaut est retournée, généralement index.html .
3. index.html amorce l’application.
4. Le composant Router se charge et le composant Razor Main est rendu.

Dans la page principale, la sélection du composant About fonctionne sur le client, car le
routeur Blazor empêche le navigateur d’effectuer une requête sur Internet à
www.contoso.com pour About et fournit lui-même le composant About rendu. Toutes les

requêtes de point de terminaison interne au sein de l’application Blazor WebAssembly


fonctionnent de la même façon : les requêtes ne déclenchent pas de requêtes basées
sur un navigateur pour les ressources hébergées sur le serveur sur Internet. Le routeur
gère les requêtes en interne.

Si une requête pour www.contoso.com/About est effectuée à l’aide de la barre d’adresses


du navigateur, elle échoue. Comme cette ressource n’existe pas sur l’hôte Internet de
l’application, une réponse 404 – Non trouvé est retournée.

Étant donné que les navigateurs envoient des requêtes aux hôtes basés sur Internet
pour des pages côté client, les serveurs web et les services d’hébergement doivent
réécrire toutes les requêtes pour les ressources qui ne se trouvent pas physiquement sur
le serveur afin qu’elles pointent vers la page index.html . Quand index.html est
retournée, le routeur Blazor de l’application prend le relais et répond avec la ressource
appropriée.

Lors du déploiement sur un serveur IIS, vous pouvez utiliser le module réécriture d’URL
avec le fichier web.config publié de l’application. Pour plus d’informations, consultez la
section IIS.

Déploiement autonome
Un déploiement autonome fournit l’application Blazor WebAssembly sous la forme d’un
ensemble de fichiers statiques qui sont demandés directement par les clients. N’importe
quel serveur de fichiers statiques est capable de servir l’application Blazor.

Les ressources de déploiement autonomes sont publiées dans le dossier


/bin/Release/{TARGET FRAMEWORK}/publish/wwwroot .

Azure App Service


Les applications Blazor WebAssembly peuvent être déployées sur Azure App Services sur
Windows, qui héberge l’application sur IIS.

Le déploiement d’une application Blazor WebAssembly autonome sur Azure App Service
pour Linux n’est actuellement pas pris en charge. Nous vous recommandons d’héberger
une application Blazor WebAssembly autonome à l’aide d’Azure Static Web Apps, qui
prend en charge ce scénario.

Azure Static Web Apps


Déployez une application Blazor WebAssembly sur Azure Static Web Apps à l’aide de
l’une des approches suivantes :

Déployer à partir de Visual Studio


Déployer à partir de GitHub

Déployer à partir de Visual Studio


Pour déployer à partir de Visual Studio, créez un profil de publication pour Azure Static
Web Apps :

1. Enregistrez tout travail non enregistré dans le projet, car un redémarrage de Visual
Studio peut être nécessaire pendant le processus.
2. Dans l’interface utilisateur Publier de Visual Studio, sélectionnez
Cible>Azure>Cible spécifique>Azure Static Web Apps pour créer un profil de
publication.

3. Si le composant Azure WebJobs Tools pour Visual Studio n’est pas installé, une
invite s’affiche pour installer le composant de développement web et ASP.NET.
Suivez les invites pour installer les outils à l’aide de Visual Studio Installer. Visual
Studio ferme et rouvre automatiquement lors de l’installation des outils. Une fois
les outils installés, commencez la première étape pour créer le profil de
publication.

4. Dans la configuration du profil de publication, fournissez le nom de l’abonnement.


Sélectionnez une instance existante ou sélectionnez Créer une instance. Lors de la
création d’une instance dans l’interface utilisateur Créer une application web
statique du portail Azure, définissez les Détails du déploiement>Source sur Autre.
Attendez que le déploiement se termine dans le portail Azure avant de continuer.

5. Dans la configuration du profil de publication, sélectionnez l’instance Azure Static


Web Apps dans le groupe de ressources de l’instance. Sélectionnez Terminer pour
créer le profil de publication. Si Visual Studio invite à installer l’interface CLI Static
Web Apps (SWA), installez-la en suivant les invites. L’interface CLI SWA nécessite
NPM/Node.js (documentation Visual Studio).

Une fois le profil de publication créé, déployez l’application sur l’instance Azure Static
Web Apps à l’aide du profil de publication en sélectionnant le bouton Publier.

Déployer à partir de GitHub

Pour déployer à partir d’un référentiel GitHub, consultez Didacticiel : Création d’une
application web statique avec Blazor dans Azure Static Web Apps.

IIS
IIS est un serveur de fichiers statiques compatible avec les applications Blazor. Pour
configurer IIS afin d’héberger Blazor, consultez Générer un site web statique sur IIS.

Les ressources publiées sont créées dans le dossier /bin/Release/{TARGET


FRAMEWORK}/publish ou bin\Release\{TARGET FRAMEWORK}\browser-wasm\publish , selon la

version de SDK utilisée et où l’espace réservé {TARGET FRAMEWORK} est le framework cible.
Hébergez le contenu du dossier publish sur le serveur web ou le service
d’hébergement.
web.config
Quand un projet Blazor est publié, un fichier web.config est créé avec la configuration
IIS suivante :

types MIME
La compression HTTP est activée pour les types MIME suivants :
application/octet-stream
application/wasm

Des règles du module de réécriture d’URL sont établies :


Servir le sous-répertoire où résident les ressources statiques de l’application
( wwwroot/{PATH REQUESTED} ).
Créer un routage de secours SPA afin que les requêtes pour des ressources
autres que des fichiers soient redirigées vers le document par défaut de
l’application dans son dossier de ressources statiques ( wwwroot/index.html ).

Utiliser un web.config personnalisé


Pour utiliser un fichier web.config personnalisé :

1. Placez le fichier web.config personnalisé dans le dossier racine du projet.


2. Publiez le projet. Pour plus d’informations, consultez Héberger et déployer
ASP.NET Core Blazor.

Si la génération ou la transformation web.config du SDK pendant la publication ne


déplace pas le fichier vers les ressources publiées dans le dossier publish ou modifie la
configuration personnalisée dans votre fichier web.config personnalisé, utilisez l’une des
approches suivantes si nécessaire pour prendre le contrôle total du processus :

Si le SDK ne génère pas le fichier, par exemple, dans une application Blazor
WebAssembly autonome sur /bin/Release/{TARGET FRAMEWORK}/publish/wwwroot ou
bin\Release\{TARGET FRAMEWORK}\browser-wasm\publish , selon la version du SDK

utilisée et où l’espace réservé {TARGET FRAMEWORK} est le framework cible, définissez


la propriété <PublishIISAssets> sur true dans le fichier projet ( .csproj ). En règle
générale, pour les applications WebAssembly autonomes, il s’agit du seul
paramètre requis pour déplacer un fichier web.config personnalisé et empêcher la
transformation du fichier par le SDK.

XML

<PropertyGroup>
<PublishIISAssets>true</PublishIISAssets>
</PropertyGroup>

Désactivez la transformation de web.config du SDK dans le fichier projet


( .csproj ) :

XML

<PropertyGroup>
<IsTransformWebConfigDisabled>true</IsTransformWebConfigDisabled>
</PropertyGroup>

Ajoutez une cible personnalisée au fichier projet ( .csproj ) pour déplacer un fichier
web.config personnalisé. Dans l’exemple suivant, le fichier web.config

personnalisé est placé par le développeur à la racine du projet. Si le fichier


web.config réside ailleurs, spécifiez le chemin d’accès au fichier dans SourceFiles .

L’exemple suivant spécifie le dossier publish avec $(PublishDir) , mais fournit un


chemin d’accès à DestinationFolder pour un emplacement de sortie personnalisé.

XML

<Target Name="CopyWebConfig" AfterTargets="Publish">


<Copy SourceFiles="web.config" DestinationFolder="$(PublishDir)" />
</Target>

Installer le module de réécriture d’URL


Le module de réécriture d’URL est nécessaire pour réécrire les URL. Il n’est pas installé
par défaut, et ne peut pas l’être en tant que fonctionnalité de service de rôle Serveur
Web (IIS). Vous devez le télécharger à partir du site web IIS. Utilisez Web Platform
Installer pour installer le module :

1. Localement, accédez à la page des téléchargements du Module de réécriture


d’URL . Pour la version anglaise, sélectionnez WebPI pour télécharger le
programme d’installation WebPI. Pour les autres langues, sélectionnez
l’architecture appropriée pour le serveur (x86/x64) afin de télécharger le
programme d’installation.
2. Copiez le programme d’installation sur le serveur. Exécutez le programme
d’installation. Sélectionnez le bouton Installer et acceptez les termes du contrat de
licence. Il n’est pas nécessaire de redémarrer le serveur après l’installation.

Configurer le site web


Affectez le dossier de l’application comme chemin physique du site web. Le dossier
contient :

Le fichier web.config utilisé par IIS pour configurer le site web, notamment les
règles de redirection nécessaires et les types de contenu de fichiers
Le dossier de ressources statiques de l’application

Hôte en tant que sous-application IIS


Si une application autonome est hébergée en tant que sous-application IIS, effectuez
l’une des opérations suivantes :

Désactivez le gestionnaire de module ASP.NET Core hérité.

Supprimez le gestionnaire dans le fichier web.config publié de l’application Blazor


en ajoutant une section <handlers> à la section <system.webServer> du fichier :

XML

<handlers>
<remove name="aspNetCore" />
</handlers>

Désactivez l’héritage de la section <system.webServer> de l’application racine


(parente) en affectant la valeur false à l’attribut inheritInChildApplications d’un
élément <location> :

XML

<?xml version="1.0" encoding="utf-8"?>


<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" ... />
</handlers>
<aspNetCore ... />
</system.webServer>
</location>
</configuration>

7 Notes

La désactivation de l’héritage de la section <system.webServer> de


l’application racine (parent) est la configuration par défaut pour les
applications publiées à l’aide du SDK .NET.

La suppression du gestionnaire ou la désactivation de l’héritage est effectuée en plus de


la configuration du chemin de base de l’application. Dans le fichier index.html de
l’application, définissez le chemin de base de l’application sur l’alias IIS utilisé lors de la
configuration de la sous-application dans IIS.

Compression Brotli et Gzip


Cette section s’applique uniquement aux applications Blazor WebAssembly autonomes.

IIS peut être configuré via web.config pour servir des ressources compressées Blazor
Brotli ou Gzip pour les applications autonomes Blazor WebAssembly. Pour obtenir un
exemple de fichier de configuration, consultez web.config .

Une configuration supplémentaire de l’exemple de fichier web.config peut être


nécessaire dans les scénarios suivants :

La spécification de l’application appelle l’un des éléments suivants :


Remise des fichiers compressés qui ne sont pas configurés par l’exemple de
fichier web.config .
Remise des fichiers compressés configurés par l’exemple de fichier web.config
dans un format non compressé.
La configuration IIS du serveur (par exemple, applicationHost.config ) fournit des
valeurs IIS par défaut au niveau du serveur. Selon la configuration au niveau du
serveur, l’application peut nécessiter une configuration IIS différente de celle que
contient l’exemple de fichier web.config .

Pour plus d’informations sur les fichiers personnalisés web.config , consultez la section
Utiliser un web.config personnalisé.

Résolution des problèmes


Si vous recevez un message 500 – Erreur interne du serveur et que le Gestionnaire IIS
lève des erreurs quand vous tentez d’accéder à la configuration du site web, vérifiez que
le module de réécriture d’URL est installé. Quand le module n’est pas installé, le fichier
web.config ne peut pas être analysé par IIS. Cela empêche le Gestionnaire IIS de charger

la configuration du site web et empêche le site web de fournir les fichiers statiques
Blazor.
Pour plus d’informations sur la résolution des problèmes des déploiements sur IIS,
consultez Résolution des problèmes ASP.NET Core sur Azure App Service et IIS.

Azure Storage
L’hébergement d’un fichier statique de stockage Azure permet l’hébergement
d’applications serverless Blazor. Les noms de domaine personnalisé, le réseau de
distribution de contenu Azure (CDN) et HTTPS sont pris en charge.

Lorsque le service blob est activé pour l’hébergement de site Web statique sur un
compte de stockage :

Définissez le nom du document d’index sur index.html .


Définissez le chemin d’accès au document d’erreur sur index.html . Les
composants de Razor et autres points de terminaison non-fichier ne se trouvent
sur les chemins d’accès physiques dans le contenu statique stocké par le service
blob. Lors de la réception d’une requête pour l’une de ces ressources que le
routeur Blazor doit gérer, l’erreur 404 - introuvable générée par le service blob
achemine la requête vers le chemin d’accès au document d’erreur. Le blob
index.html est retourné et le routeur Blazor charge et traite le chemin d’accès.

Si les fichiers ne sont pas chargés au moment de l’exécution en raison de types MIME
inappropriés dans les en-têtes Content-Type des fichiers, effectuez l’une des actions
suivantes :

Configurez vos outils pour définir les types MIME appropriés (en-têtes Content-
Type ) lors du déploiement des fichiers.

Modifiez les types MIME (en-têtes Content-Type ) des fichiers après le déploiement
de l’application.

Dans l’Explorateur Stockage (Portail Azure) pour chaque fichier :

1. Cliquez avec le bouton de droite sur le fichier et sélectionnez Propriétés.


2. Définissez ContentType et sélectionnez le bouton Enregistrer.

Pour plus d’informations, consultez Hébergement de site Web statique dans le stockage
Azure.

Nginx
Le fichier nginx.conf suivant est simplifié afin de montrer comment configurer Nginx
pour envoyer le fichier index.html chaque fois qu’il ne trouve aucun fichier
correspondant sur le disque.

events { }
http {
server {
listen 80;

location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html =404;
}
}
}

Lors de la définition de la limite de débit de rafale NGINX avec limit_req , les


applications Blazor WebAssembly peuvent nécessiter une valeur de paramètre burst
conséquente pour prendre en charge le nombre relativement élevé de requêtes
effectuées par une application. Initialement, définissez la valeur sur au moins 60 :

http {
server {
...

location / {
...

limit_req zone=one burst=60 nodelay;


}
}
}

Augmentez la valeur si les outils de développement de navigateur ou un outil de trafic


réseau indiquent que les requêtes reçoivent un code État 503 - Service indisponible.

Pour plus d’informations sur la configuration du serveur web Nginx de production,


consultez Creating NGINX Plus and NGINX Configuration Files (Création de fichiers de
configuration NGINX et NGINX Plus).

Apache
Pour déployer une application Blazor WebAssembly sur CentOS 7 ou version ultérieure :
1. Créez le fichier de configuration Apache. L’exemple suivant est un fichier de
configuration simplifié ( blazorapp.config ) :

<VirtualHost *:80>
ServerName www.example.com
ServerAlias *.example.com

DocumentRoot "/var/www/blazorapp"
ErrorDocument 404 /index.html

AddType application/wasm .wasm

<Directory "/var/www/blazorapp">
Options -Indexes
AllowOverride None
</Directory>

<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE application/octet-stream
AddOutputFilterByType DEFLATE application/wasm
<IfModule mod_setenvif.c>
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4.0[678] no-gzip
BrowserMatch bMSIE !no-gzip !gzip-only-text/html
</IfModule>
</IfModule>

ErrorLog /var/log/httpd/blazorapp-error.log
CustomLog /var/log/httpd/blazorapp-access.log common
</VirtualHost>

1. Placez le fichier de configuration Apache dans le répertoire /etc/httpd/conf.d/ ,


qui est le répertoire de configuration Apache par défaut dans CentOS 7.

2. Placez les fichiers de l’application dans le répertoire /var/www/blazorapp


(l’emplacement spécifié pour DocumentRoot dans le fichier de configuration).

3. Redémarrez le service Apache.

Pour plus d’informations, consultez mod_mime et mod_deflate .

GitHub Pages
L’action GitHub par défaut, qui déploie des pages, ignore le déploiement de dossiers
commençant par un trait de soulignement, par exemple, le dossier _framework . Pour
déployer des dossiers commençant par un trait de soulignement, ajoutez un fichier
.nojekyll vide à la branche Git.

Git traite les fichiers JavaScript (JS), comme blazor.webassembly.js , en tant que texte et
convertit les terminaisons de ligne CRLF (retour chariot) en LF (saut de ligne) dans le
pipeline de déploiement. Ces modifications apportées aux fichiers JS produisent des
hachages de fichiers différents de ceux que Blazor envoie au client dans le fichier
blazor.boot.json . Ces disparités entraînent des échecs de vérification de l’intégrité sur

le client. Une approche pour résoudre ce problème consiste à ajouter un fichier


.gitattributes avec la ligne *.js binary avant d’ajouter les ressources de l’application

à la branche Git. La ligne *.js binary configure Git pour traiter les fichiers JS en tant
que fichiers binaires, ce qui évite de traiter les fichiers dans le pipeline de déploiement.
Les hachages des fichiers non traités correspondent aux entrées du fichier
blazor.boot.json , et les vérifications d’intégrité côté client réussissent. Pour plus

d’informations, consultez la section Résoudre les échecs de vérification de l’intégrité.

Pour gérer les réécritures d’URL, ajoutez un fichier wwwroot/404.html avec un script qui
gère la redirection de la requête vers la page index.html . Pour obtenir un exemple,
consultez le dépôt GitHub SteveSandersonMS/BlazorOnGitHubPages :

wwwroot/404.html
Site en ligne

Quand vous utilisez un site de projet plutôt qu’un site d’entreprise, mettez à jour la
balise <base> dans wwwroot/index.html . Définissez la valeur d’attribut href sur le nom
du dépôt GitHub avec une barre oblique (par exemple, /my-repository/ ). Dans le dépôt
GitHub SteveSandersonMS/BlazorOnGitHubPages , le href de base est mis à jour au
moment de la publication par le fichier de configuration .github/workflows/main.yml .

7 Notes

Le dépôt GitHub SteveSandersonMS/BlazorOnGitHubPages n’est pas détenu,


géré ou pris en charge par .NET Foundation ou Microsoft.

Autonome avec Docker


Une application autonome Blazor WebAssembly est publiée sous la forme d’un
ensemble de fichiers statiques à héberger par un serveur de fichiers statique.
Pour héberger l’application dans Docker :

Choisissez un conteneur Docker avec prise en charge du serveur web, par exemple
Ngnix ou Apache.
Copiez les ressources du dossier publish dans un dossier d’emplacement défini
dans le serveur web pour servir des fichiers statiques.
Appliquez une configuration supplémentaire en fonction des besoins pour servir
l’application Blazor WebAssembly.

Pour des instructions de configuration, consultez les ressources suivantes :

Section Nginx ou Apache de cet article


Documentation Docker

Valeurs de configuration de l’hôte


Les applications Blazor WebAssembly peuvent accepter les valeurs de configuration
d’hôte suivantes en tant qu’arguments de ligne de commande au moment de
l’exécution dans l’environnement de développement.

Racine de contenu
L’argument --contentroot définit le chemin absolu du répertoire qui contient les fichiers
de contenu de l’application (racine du contenu). Dans les exemples suivants, /content-
root-path est le chemin racine du contenu de l’application.

Passez l’argument lors de l’exécution de l’application localement à une invite de


commandes. À partir du répertoire de l’application, exécutez :

CLI .NET

dotnet run --contentroot=/content-root-path

Ajoutez une entrée au fichier launchSettings.json de l’application dans le profil IIS


Express. Ce paramètre est utilisé en cas d’exécution de l’application avec le
débogueur Visual Studio et dans une invite de commandes avec dotnet run .

JSON

"commandLineArgs": "--contentroot=/content-root-path"
Dans Visual Studio, spécifiez l’argument dans Propriétés>Déboguer>Arguments
d’application. Le fait de définir l’argument dans la page de propriétés Visual Studio
l’ajoute au fichier launchSettings.json .

Console

--contentroot=/content-root-path

Base du chemin
L’argument --pathbase définit le chemin de base de l’application pour une application
s’exécutant localement avec un chemin d’URL relative non racine (le href de la balise
<base> a comme valeur un chemin autre que / pour la préproduction et la production).

Dans les exemples suivants, /relative-URL-path est la base du chemin de l’application.


Pour plus d’informations, consultez Chemin de base de l’application.

) Important

Contrairement au chemin fourni au href de la balise <base> , n’incluez pas de barre


oblique ( / ) quand vous passez la valeur d’argument --pathbase . Si vous spécifiez
<base href="/CoolApp/"> (inclut une barre oblique) comme chemin de base de

l’application dans la balise <base> , passez --pathbase=/CoolApp (aucune barre


oblique de fin) comme valeur d’argument de ligne de commande.

Passez l’argument lors de l’exécution de l’application localement à une invite de


commandes. À partir du répertoire de l’application, exécutez :

CLI .NET

dotnet run --pathbase=/relative-URL-path

Ajoutez une entrée au fichier launchSettings.json de l’application dans le profil IIS


Express. Ce paramètre est utilisé en cas d’exécution de l’application avec le
débogueur Visual Studio et dans une invite de commandes avec dotnet run .

JSON

"commandLineArgs": "--pathbase=/relative-URL-path"
Dans Visual Studio, spécifiez l’argument dans Propriétés>Déboguer>Arguments
d’application. Le fait de définir l’argument dans la page de propriétés Visual Studio
l’ajoute au fichier launchSettings.json .

Console

--pathbase=/relative-URL-path

URL
L’argument --urls définit les adresses IP ou les adresses d’hôtes avec les ports et
protocoles sur lesquels il faut écouter les demandes.

Passez l’argument lors de l’exécution de l’application localement à une invite de


commandes. À partir du répertoire de l’application, exécutez :

CLI .NET

dotnet run --urls=http://127.0.0.1:0

Ajoutez une entrée au fichier launchSettings.json de l’application dans le profil IIS


Express. Ce paramètre est utilisé en cas d’exécution de l’application avec le
débogueur Visual Studio et dans une invite de commandes avec dotnet run .

JSON

"commandLineArgs": "--urls=http://127.0.0.1:0"

Dans Visual Studio, spécifiez l’argument dans Propriétés>Déboguer>Arguments


d’application. Le fait de définir l’argument dans la page de propriétés Visual Studio
l’ajoute au fichier launchSettings.json .

Console

--urls=http://127.0.0.1:0

Configurer l’outil de découpage


Blazor effectue la suppression du langage intermédiaire (IL) lors de chaque génération
de version afin de supprimer tout langage intermédiaire inutile des assemblys de sortie.
Pour plus d’informations, consultez Configurer l’outil de suppression pour Blazor
ASP.NET Core.

Modifier l’extension de nom de fichier des


fichiers DLL
Cette section s’applique à ASP.NET Core 6.x ou 7.x. Dans ASP.NET Core 8.0 ou version
ultérieure, les assemblys .NET sont déployés en tant que fichiers WebAssembly ( .wasm ) à
l’aide du format de fichier Webcil. Dans ASP.NET Core 8.0 ou version ultérieure, cette
section s’applique uniquement si le format de fichier Webcil a été désactivé dans le fichier
projet de l’application.

Si un pare-feu, un programme antivirus ou une appliance de sécurité réseau bloque la


transmission des fichiers DLL (bibliothèque de liens dynamiques) de l’application ( .dll ),
vous pouvez suivre les instructions de cette section pour modifier les extensions de nom
de fichier des fichiers DLL publiés de l’application.

7 Notes

La modification des extensions de nom de fichier des fichiers DLL de l’application


peut ne pas résoudre le problème, car de nombreux systèmes de sécurité analysent
le contenu des fichiers de l’application, et pas simplement les extensions de fichier.

Pour une approche plus robuste dans les environnements qui bloquent le
téléchargement et l’exécution de fichiers DLL, utilisez ASP.NET Core 8.0 ou une
version ultérieure, qui par défaut emballe les assemblies .NET en tant que fichiers
WebAssembly ( .wasm ) à l’aide du format de fichier Webcil . Pour plus
d’informations, consultez la section Format d’empaquetage Webcil pour les
assemblys .NET dans une version 8.0 ou ultérieure de cet article.

Il existe des approches tierces pour résoudre ce problème. Pour plus


d’informations, consultez les ressources sur Awesome Blazor .

Après avoir publié l’application, utilisez un script shell ou un pipeline de build DevOps
pour renommer les fichiers .dll pour utiliser une autre extension de fichier dans le
répertoire de la sortie publiée de l’application.

Dans les exemples suivants :

PowerShell (PS) est utilisé pour mettre à jour les extensions de fichier.
Les fichiers .dll sont renommés pour utiliser l’extension de fichier .bin à partir
de la ligne de commande.
Les fichiers répertoriés dans le fichier blazor.boot.json publié avec une extension
de fichier .dll sont mis à jour vers l’extension de fichier .bin .
Si les ressources du Worker de service sont également utilisées, une commande
PowerShell met à jour les fichiers .dll répertoriés dans le fichier service-worker-
assets.js vers l’extension de fichier .bin .

Pour utiliser une extension de fichier différente de .bin , remplacez .bin dans les
commandes suivantes par l’extension de fichier souhaitée.

Sur Windows :

PowerShell

dir {PATH} | rename-item -NewName { $_.name -replace ".dll\b",".bin" }


((Get-Content {PATH}\blazor.boot.json -Raw) -replace '.dll"','.bin"') | Set-
Content {PATH}\blazor.boot.json

Dans la commande précédente, l’espace réservé {PATH} est le chemin d’accès au dossier
_framework publié (par exemple, .\bin\Release\net6.0\browser-

wasm\publish\wwwroot\_framework à partir du dossier racine du projet).

Si des ressources de travail de service sont également utilisées :

PowerShell

((Get-Content {PATH}\service-worker-assets.js -Raw) -replace


'.dll"','.bin"') | Set-Content {PATH}\service-worker-assets.js

Dans la commande précédente, l’espace réservé {PATH} est le chemin d’accès au fichier
service-worker-assets.js publié.

Sur Linux ou macOS :

Console

for f in {PATH}/*; do mv "$f" "`echo $f | sed -e 's/\.dll/.bin/g'`"; done


sed -i 's/\.dll"/.bin"/g' {PATH}/blazor.boot.json

Dans la commande précédente, l’espace réservé {PATH} est le chemin d’accès au dossier
_framework publié (par exemple, .\bin\Release\net6.0\browser-

wasm\publish\wwwroot\_framework à partir du dossier racine du projet).


Si des ressources de travail de service sont également utilisées :

Console

sed -i 's/\.dll"/.bin"/g' {PATH}/service-worker-assets.js

Dans la commande précédente, l’espace réservé {PATH} est le chemin d’accès au fichier
service-worker-assets.js publié.

Pour traiter les fichiers compressés blazor.boot.json.gz et blazor.boot.json.br ,


adoptez l’une des approches suivantes :

Supprimez les fichiers blazor.boot.json.gz et blazor.boot.json.br compressés. La


compression est désactivée avec cette approche.
Recompressez le fichier blazor.boot.json mis à jour.

Les conseils précédents pour le fichier blazor.boot.json compressé s’appliquent


également lorsque les ressources du Worker de service sont en cours d’utilisation.
Supprimez ou recompressez service-worker-assets.js.br et service-worker-
assets.js.gz . Sinon, les vérifications d’intégrité des fichiers échouent dans le navigateur.

L’exemple Windows suivant pour .NET 6.0 utilise un script PowerShell placé à la racine
du projet. Le script suivant, qui désactive la compression, est la base des modifications
supplémentaires si vous souhaitez recompresser le fichier blazor.boot.json .

ChangeDLLExtensions.ps1: :

PowerShell

param([string]$filepath,[string]$tfm)
dir $filepath\bin\Release\$tfm\browser-wasm\publish\wwwroot\_framework |
rename-item -NewName { $_.name -replace ".dll\b",".bin" }
((Get-Content $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\blazor.boot.json -Raw) -replace
'.dll"','.bin"') | Set-Content $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\blazor.boot.json
Remove-Item $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\blazor.boot.json.gz
Remove-Item $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\blazor.boot.json.br

Si des ressources Worker de service sont également utilisées, ajoutez les commandes
suivantes :

PowerShell
((Get-Content $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\service-worker-assets.js -Raw) -replace
'.dll"','.bin"') | Set-Content $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\wwwroot\service-worker-assets.js
Remove-Item $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\wwwroot\service-worker-assets.js.gz
Remove-Item $filepath\bin\Release\$tfm\browser-
wasm\publish\wwwroot\_framework\wwwroot\service-worker-assets.js.br

Dans le fichier projet, le script est exécuté après la publication de l’application pour la
configuration Release :

XML

<Target Name="ChangeDLLFileExtensions" AfterTargets="AfterPublish"


Condition="'$(Configuration)'=='Release'">
<Exec Command="powershell.exe -command &quot;&amp; {
.\ChangeDLLExtensions.ps1 '$(SolutionDir)' '$(TargetFramework)'}&quot;" />
</Target>

7 Notes

Lors du changement de nom et du chargement différé des mêmes assemblys,


consultez les instructions d’Assemblys de chargement différé dans ASP.NET Core
Blazor WebAssembly.

En règle générale, le serveur de l’application nécessite une configuration de ressource


statique pour traiter les fichiers avec l’extension mise à jour. Pour une application
hébergée par IIS, ajoutez une entrée de carte MIME ( <mimeMap> ) pour la nouvelle
extension de fichier dans la section de contenu statique ( <staticContent> ) dans un
fichier web.config personnalisé. L’exemple suivant suppose que l’extension de fichier est
modifiée de .dll à .bin :

XML

<staticContent>
...
<mimeMap fileExtension=".bin" mimeType="application/octet-stream" />
...
</staticContent>

Incluez une mise à jour pour les fichiers compressés si la compression est en cours
d’utilisation :
<mimeMap fileExtension=".bin.br" mimeType="application/octet-stream" />
<mimeMap fileExtension=".bin.gz" mimeType="application/octet-stream" />

Supprimez l’entrée de l’extension de fichier .dll :

diff

- <mimeMap fileExtension=".dll" mimeType="application/octet-stream" />

Supprimez les entrées des fichiers .dll compressés si la compression est en cours
d’utilisation :

diff

- <mimeMap fileExtension=".dll.br" mimeType="application/octet-stream" />


- <mimeMap fileExtension=".dll.gz" mimeType="application/octet-stream" />

Pour plus d’informations sur les fichiers personnalisés web.config , consultez la section
Utiliser un web.config personnalisé.

Endommagement par un déploiement


antérieur
Généralement lors du déploiement :

Seuls les fichiers modifiés sont remplacés, ce qui entraîne généralement un


déploiement plus rapide.
Les fichiers existants qui ne font pas partie du nouveau déploiement sont laissés en
place pour être utilisés par le nouveau déploiement.

Dans de rares cas, des fichiers persistants d’un déploiement précédent peuvent
endommager un nouveau déploiement. La suppression complète du déploiement
existant (ou de l’application publiée localement avant le déploiement) peut résoudre le
problème lié à un déploiement endommagé. Souvent, la suppression du déploiement
existant une fois suffit à résoudre le problème, y compris pour une génération et un
pipeline de déploiement DevOps.

Si vous déterminez que l’effacement d’un déploiement antérieur est toujours nécessaire
lorsqu’un pipeline de build et de déploiement DevOps est en cours d’utilisation, vous
pouvez ajouter temporairement une étape au pipeline de build pour supprimer le
déploiement précédent pour chaque nouveau déploiement jusqu’à ce que vous
déterminiez la cause exacte de l’endommagement.

Résoudre les échecs de vérification de


l’intégrité
Lorsque Blazor WebAssembly télécharge les fichiers de démarrage d’une application, il
indique au navigateur d’effectuer des vérifications d’intégrité sur les réponses. Blazor
envoie les valeurs de hachage SHA-256 pour la DLL ( .dll ), WebAssembly ( .wasm ) et
d’autres fichiers dans le fichier blazor.boot.json , qui n’est pas mis en cache sur les
clients. Les hachages de fichiers mis en cache sont comparés aux hachages du fichier
blazor.boot.json . Pour les fichiers mis en cache avec un hachage correspondant, Blazor

utilise les fichiers mis en cache. Sinon, les fichiers sont demandés auprès du serveur. Une
fois qu’un fichier est téléchargé, son hachage est à nouveau vérifié pour la validation de
l’intégrité. Une erreur est générée par le navigateur si la vérification de l’intégrité du
fichier téléchargé échoue.

L’algorithme Blazor pour la gestion de l’intégrité des fichiers :

Garantit que l’application ne risque pas de charger un ensemble incohérent de


fichiers, par exemple si un nouveau déploiement est appliqué à votre serveur web
pendant que l’utilisateur est en train de télécharger les fichiers de l’application. Les
fichiers incohérents peuvent entraîner un dysfonctionnement de l’application.
Garantit que le navigateur de l’utilisateur ne met jamais en cache les réponses
incohérentes ou non valides, ce qui peut empêcher l’application de démarrer
même si l’utilisateur actualise manuellement la page.
Permet de mettre en cache les réponses et de ne pas vérifier les modifications côté
serveur tant que les hachages SHA-256 attendus changent, de sorte que les
chargements de pages suivants impliquent moins de requêtes et se terminent plus
rapidement.

Si le serveur web retourne des réponses qui ne correspondent pas aux hachages SHA-
256 attendus, une erreur similaire à l’exemple suivant s’affiche dans la console du
développeur du navigateur :

Impossible de trouver une synthèse valide dans l’attribut « integrity » pour la


ressource « https://myapp.example.com/_framework/MyBlazorApp.dll » avec
l’intégrité SHA-256 calculée
« IIa70iwvmEg5WiDV17OpQ5eCztNYqL186J56852RpJY= ». La ressource a été
bloquée.
Dans la plupart des cas, l’avertissement n’indique pas un problème de vérification
d’intégrité. Au lieu de cela, l’avertissement signifie généralement qu’il existe un autre
problème.

Pour obtenir la source de référence de démarrage de Blazor WebAssembly, consultez le


fichier Boot.WebAssembly.ts dans le dépôt GitHub dotnet/aspnetcore .

7 Notes

Les liens de documentation vers la source de référence .NET chargent


généralement la branche par défaut du référentiel, qui représente le
développement actuel pour la prochaine version de .NET. Pour sélectionner une
balise pour une version spécifique, utilisez la liste déroulante Échanger les
branches ou les balises. Pour plus d’informations, consultez Comment sélectionner
une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs
#26205) .

Diagnostic des problèmes d’intégrité


Lorsqu’une application est générée, le manifeste blazor.boot.json généré décrit les
hachages SHA-256 des ressources de démarrage au moment où la sortie de build est
générée. La vérification de l’intégrité réussit tant que les hachages SHA-256 dans
blazor.boot.json correspondent aux fichiers remis au navigateur.

Les raisons courantes de cet échec sont :

La réponse du serveur web est une erreur (par exemple, 404 - Introuvable ou 500 -
Erreur de serveur interne) au lieu du fichier demandé par le navigateur. Cela est
signalé par le navigateur en tant qu’échec de vérification de l’intégrité et non en
tant qu’échec de réponse.
Quelque chose a modifié le contenu des fichiers entre la génération et la remise
des fichiers dans le navigateur. Cela peut se produire :
Si vous ou les outils de génération modifiez manuellement la sortie de build.
Si un aspect du processus de déploiement a modifié les fichiers. Par exemple, si
vous utilisez un mécanisme de déploiement basé sur Git, gardez à l’esprit que
Git convertit en toute transparence les terminaisons de ligne de style Windows
en fin de ligne de style Unix si vous validez des fichiers sur Windows et que vous
les extrayez sur Linux. La modification des fins de ligne de fichier modifie les
hachages SHA-256. Pour éviter ce problème, envisagez d’utiliser .gitattributes
pour traiter les artefacts de build comme des fichiers binary .
Le serveur web modifie le contenu du fichier dans le cadre de son service. Par
exemple, certains réseaux de distribution de contenu (CDN) tentent
automatiquement de minifier le code HTML, le modifiant ainsi. Vous devrez
peut-être désactiver ces fonctionnalités.
Le fichier blazor.boot.json ne parvient pas à se charger correctement ou est mis
en cache incorrectement sur le client. Les causes courantes incluent les suivantes :
Code de développeur personnalisé mal configuré ou défectueux.
Une ou plusieurs couches de mise en cache intermédiaire mal configurées.

Pour diagnostiquer lequel de ces éléments s’applique dans votre cas :

1. Notez le fichier qui déclenche l’erreur en lisant le message d’erreur.


2. Ouvrez les outils de développement de votre navigateur et recherchez l’onglet
Réseau. Si nécessaire, rechargez la page pour afficher la liste des demandes et des
réponses. Recherchez le fichier qui déclenche l’erreur dans cette liste.
3. Vérifiez le code d’état HTTP dans la réponse. Si le serveur retourne autre chose que
200 - OK (ou un autre code d’état 2xx), vous avez un problème côté serveur à
diagnostiquer. Par exemple, le code d’état 403 signifie qu’il existe un problème
d’autorisation, tandis que le code d’état 500 signifie que le serveur échoue de
manière non spécifiée. Consultez les journaux côté serveur pour diagnostiquer et
corriger l’application.
4. Si le code d’état est 200 - OK pour la ressource, examinez le contenu de la réponse
dans les outils de développement du navigateur et vérifiez que le contenu
correspond aux données attendues. Par exemple, un problème courant est la
mauvaise configuration du routage, faisant que les requêtes retournent vos
données pour index.html même pour d’autres fichiers. Assurez-vous que les
réponses aux requêtes .wasm sont des fichiers binaires WebAssembly et que les
réponses aux requêtes .dll sont des fichiers binaires d’assembly .NET. Si ce n’est
pas le cas, vous rencontrez un problème de routage côté serveur à diagnostiquer.
5. Essayez de valider la sortie publiée et déployée de l’application avec le script
PowerShell Résoudre les problèmes d’intégrité.

Si vous confirmez que le serveur retourne des données plausiblement correctes, il doit y
avoir autre chose qui modifie le contenu entre la génération et la remise du fichier. Pour
vérifier cela :

Examinez la chaîne d’outils de génération et le mécanisme de déploiement au cas


où ils modifieraient les fichiers une fois les fichiers générés. Par exemple, Git
transforme les terminaisons de ligne de fichier, comme décrit précédemment.
Examinez la configuration du serveur web ou du CDN au cas où ils seraient
configurés pour modifier les réponses de manière dynamique (par exemple, en
essayant de minifier le code HTML). Le serveur web peut implémenter la
compression HTTP (par exemple, en retournant content-encoding: br ou content-
encoding: gzip ), car cela n’affecte pas le résultat après la décompression.

Toutefois, le serveur web ne peut pas modifier les données non compressées.

Script PowerShell Résoudre les problèmes d’intégrité


Utilisez le script PowerShell integrity.ps1 pour valider une application Blazor publiée et
déployée. Le script est fourni pour PowerShell Core 7 ou version ultérieure comme point
de départ lorsque l’application rencontre des problèmes d’intégrité que le framework
Blazor ne peut pas identifier. La personnalisation du script peut être nécessaire pour vos
applications, y compris en cas d’exécution sur une version de PowerShell ultérieure à la
version 7.2.0.

Le script vérifie les fichiers du dossier publish et téléchargés à partir de l’application


déployée pour détecter les problèmes dans les différents manifestes qui contiennent
des hachages d’intégrité. Ces vérifications devraient détecter les problèmes les plus
courants :

Vous avez modifié un fichier dans la sortie publiée sans vous en rendre compte.
L’application n’a pas été correctement déployée sur la cible de déploiement, ou
quelque chose a changé dans l’environnement de la cible de déploiement.
Il existe des différences entre l’application déployée et la sortie de la publication de
l’application.

Appelez le script avec la commande suivante dans un interpréteur de commandes


PowerShell :

PowerShell

.\integrity.ps1 {BASE URL} {PUBLISH OUTPUT FOLDER}

Dans l’exemple suivant, le script est exécuté sur une application exécutée localement à
l’adresse https://localhost:5001/ :

PowerShell

.\integrity.ps1 https://localhost:5001/
C:\TestApps\BlazorSample\bin\Release\net6.0\publish\

Espaces réservés :

{BASE URL} : URL de l’application déployée. Une barre oblique de fin ( / ) est

requise.
{PUBLISH OUTPUT FOLDER} : chemin d’accès au dossier publish ou à l’emplacement

où l’application est publiée pour le déploiement.

7 Notes

Lors du clonage du dépôt GitHub dotnet/AspNetCore.Docs , le script integrity.ps1


peut être mis en quarantaine par Bitdefender ou un autre scanneur antivirus
présent sur le système. En règle générale, le fichier est piégé par la technologie
d’analyse heuristique d’un scanneur antivirus, qui recherche simplement des
modèles dans les fichiers susceptibles d’indiquer la présence de programmes
malveillants. Pour empêcher le scanneur antivirus de mettre en quarantaine le
fichier, ajoutez une exception au scanneur antivirus avant de cloner le dépôt.
L’exemple suivant est un chemin d’accès classique au script sur un système
Windows. Ajustez le chemin d’accès en fonction pour les autres systèmes. L’espace
réservé {USER} est le segment de chemin d’accès de l’utilisateur.

C:\Users\
{USER}\Documents\GitHub\AspNetCore.Docs\aspnetcore\blazor\host-and-
deploy\webassembly\_samples\integrity.ps1

Avertissement : la création d’exceptions antivirus est dangereuse et ne doit être


effectuée que lorsque vous êtes certain que le fichier est sécurisé.

La comparaison de la somme de contrôle d’un fichier à une valeur de somme de


contrôle valide ne garantit pas la sécurité des fichiers, mais la modification d’un
fichier d’une manière qui conserve la valeur de somme de contrôle n’est pas triviale
pour les utilisateurs malveillants. Par conséquent, les sommes de contrôle sont
utiles en tant qu’approche de sécurité générale. Comparez la somme de contrôle
du fichier integrity.ps1 local à l’une des valeurs suivantes :

SHA256 :
32c24cb667d79a701135cb72f6bae490d81703323f61b8af2c7e5e5dc0f0c2bb

MD5 : 9cee7d7ec86ee809a329b5406fbf21a8

Obtenez la somme de contrôle du fichier sur le système d’exploitation Windows


avec la commande suivante. Indiquez le chemin d’accès et le nom de fichier de
l’espace réservé {PATH AND FILE NAME} et indiquez le type de somme de contrôle à
produire pour l’espace réservé {SHA512|MD5} , SHA256 ou MD5 :

Console
CertUtil -hashfile {PATH AND FILE NAME} {SHA256|MD5}

Si vous êtes préoccupé par le fait que la validation de somme de contrôle n’est pas
suffisamment sécurisée dans votre environnement, consultez le leadership de
sécurité de votre organisation pour obtenir des conseils.

Pour plus d’informations, consultez Comprendre les programmes malveillants et


autres menaces.

Désactiver la vérification de l’intégrité pour les


applications non PWA
Dans la plupart des cas, ne désactivez pas la vérification d’intégrité. La désactivation de
la vérification de l’intégrité ne résout pas le problème sous-jacent qui a provoqué les
réponses inattendues, et entraîne la perte des avantages répertoriés précédemment.

Dans certains cas, vous ne pouvez pas vous fier au serveur web pour retourner des
réponses cohérentes, et vous n’avez pas d’autre choix que de désactiver
temporairement les vérifications d’intégrité jusqu’à ce que le problème sous-jacent soit
résolu.

Pour désactiver les vérifications d’intégrité, ajoutez ce qui suit à un groupe de propriétés
dans le fichier projet de l’application Blazor WebAssembly ( .csproj ) :

XML

<BlazorCacheBootResources>false</BlazorCacheBootResources>

BlazorCacheBootResources désactive également le comportement par défaut de mise en

cache des fichiers .dll , .wasm et autres de Blazor en fonction de leurs hachages SHA-
256, car la propriété indique que les hachages SHA-256 ne peuvent pas être utilisés
pour vérifier l’exactitude. Même avec ce paramètre, le cache HTTP normal du navigateur
peut toujours mettre en cache ces fichiers ; la configuration de votre serveur web et des
en-têtes cache-control qu’il sert détermine si cela se produit ou non.

7 Notes
La propriété BlazorCacheBootResources ne désactive pas les vérifications d’intégrité
pour les applications web progressives (PWA). Pour obtenir des conseils sur les
PWA, consultez la section Désactiver la vérification de l’intégrité pour les PWA.

Nous ne pouvons pas fournir une liste exhaustive des scénarios dans lesquels la
désactivation de la vérification de l’intégrité est nécessaire. Les serveurs peuvent
répondre à une requête de manière arbitraire en dehors de l’étendue du framework
Blazor. Le framework fournit le paramètre BlazorCacheBootResources permettant de
rendre l’application exécutable au prix de la perte de la garantie d’intégrité que
l’application peut fournir. Là encore, nous vous déconseillons de désactiver la vérification
de l’intégrité, en particulier pour les déploiements de production. Les développeurs
doivent chercher à résoudre le problème d’intégrité sous-jacent qui provoque l’échec de
la vérification de l’intégrité.

Voici quelques cas généraux qui peuvent entraîner des problèmes d’intégrité :

Exécution sur HTTP, où l’intégrité ne peut pas être vérifiée.


Si votre processus de déploiement modifie les fichiers après la publication de
quelque manière que ce soit.
Si votre hôte modifie les fichiers d’une manière quelconque.

Désactiver la vérification de l’intégrité pour les PWA


Le modèle d’application web progressive (PWA) de Blazor contient un fichier service-
worker.published.js suggéré qui est responsable de l’extraction et du stockage des

fichiers d’application pour une utilisation hors connexion. Il s’agit d’un processus distinct
du mécanisme de démarrage normal de l’application, qui a sa propre logique de
vérification de l’intégrité.

Dans le fichier service-worker.published.js , la ligne suivante est présente :

JavaScript

.map(asset => new Request(asset.url, { integrity: asset.hash }));

Pour désactiver la vérification de l’intégrité, supprimez le paramètre integrity en


remplaçant la ligne par ce qui suit :

JavaScript

.map(asset => new Request(asset.url));


Là encore, la désactivation de la vérification de l’intégrité signifie que vous perdez les
garanties de sécurité offertes par la vérification d’intégrité. Par exemple, il existe un
risque que si le navigateur de l’utilisateur met en cache l’application au moment exact
où vous déployez une nouvelle version, elle peut mettre en cache certains fichiers de
l’ancien déploiement et d’autres du nouveau déploiement. Si cela se produit,
l’application est bloquée dans un état rompu jusqu’à ce que vous déployiez une autre
mise à jour.

Configuration SignalR
Les conditions d’hébergement et de mise à l’échelle de SignalR s’appliquent aux
applications Blazor qui utilisent SignalR.

Transports
Blazor fonctionne le mieux lors de l’utilisation de WebSockets en tant que transport
SignalR en raison d’une latence plus faible, d’une meilleure fiabilité et d’une sécurité
améliorée. L’interrogation longue est utilisée par SignalR lorsque WebSockets n’est
pas disponible ou lorsque l’application est explicitement configurée pour utiliser
l’interrogation longue. Lors du déploiement sur Azure App Service, configurez
l’application pour utiliser WebSockets dans les paramètres du Portail Azure pour le
service. Pour plus d’informations sur la configuration de l’application pour Azure App
Service, consultez les instructions de publication de SignalR.

Un avertissement de console s’affiche si l’interrogation longue est utilisée :

Échec de la connexion via WebSockets, avec le transport de secours d’interrogation


longue. Cela peut être dû au blocage de la connexion par un VPN ou un proxy.

Échecs de déploiement global et de connexion


Recommandations pour les déploiements globaux vers des centres de données
géographiques :

Déployez l’application dans les régions où résident la plupart des utilisateurs.


Prenez en compte la latence accrue du trafic entre les continents.
Pour l’hébergement Azure, utilisez Azure SignalR Service.

Si une application déployée affiche fréquemment l’interface utilisateur de reconnexion


en raison de délais d’expiration de ping causés par la latence Internet, allongez les délais
d’expiration du serveur et du client :
Serveur

Au moins le double du temps d’aller-retour maximal attendu entre le client et le


serveur. Testez, surveillez et révisez les délais d’expiration en fonction des besoins.
Pour le hub SignalR, définissez ClientTimeoutInterval (valeur par défaut : 30
secondes) et HandshakeTimeout (valeur par défaut : 15 secondes). L’exemple
suivant suppose que KeepAliveInterval utilise la valeur par défaut de 15 secondes.

) Important

KeepAliveInterval n’est pas directement lié à l’affichage de l’interface


utilisateur de reconnexion. L’intervalle Keep-Alive n’a pas nécessairement
besoin d’être modifié. Si le problème d’apparence de l’interface utilisateur de
reconnexion est dû à des délais d’expiration, ClientTimeoutInterval et
HandshakeTimeout peuvent être augmentés, et l’intervalle Keep-Alive peut
rester le même. La considération importante est que si vous modifiez
l’intervalle de Keep-Alive, vous devez vous assurer que la valeur du délai
d’expiration du client est au moins le double de la valeur de l’intervalle Keep-
Alive, et que l’intervalle Keep-Alive sur le client correspond au paramètre du
serveur.

Dans l’exemple suivant, ClientTimeoutInterval est augmenté à 60 secondes et


HandshakeTimeout est augmenté à 30 secondes.

Dans le fichier Program de l’application de serveur :

C#

builder.Services.AddSignalR(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});

Pour plus d’informations, consultez Conseils relatifs à ASP.NET Core BlazorSignalR.

Client

En règle générale, doublez la valeur utilisée pour le KeepAliveInterval du serveur


pour définir le délai d’expiration du serveur du client (ServerTimeout, valeur par
défaut : 30 secondes).

) Important
L’intervalle Keep-Alive (KeepAliveInterval) n’est pas directement lié à
l’affichage de l’interface utilisateur de reconnexion. L’intervalle Keep-Alive n’a
pas nécessairement besoin d’être modifié. Si le problème d’apparence de
l’interface utilisateur de reconnexion est dû à des délais d’expiration, le délai
d’expiration serveur peut être augmenté, et l’intervalle Keep-Alive peut rester
le même. La considération importante est que si vous modifiez l’intervalle de
Keep-Alive, vous devez vous assurer que la valeur du délai d’expiration est au
moins le double de la valeur de l’intervalle Keep-Alive, et que l’intervalle
Keep-Alive sur le serveur correspond au paramètre du client.

Lors de la création d’une connexion hub dans un composant, vous pouvez


personnaliser les valeurs ServerTimeout (par défaut : 30 secondes) et
HandshakeTimeout (par défaut : 15 secondes) en fonction de vos besoins.

Dans l’exemple suivant, le délai d’expiration du serveur est augmenté à 60


secondes, et le délai d’expiration de l’établissement d’une liaison est augmenté à
30 secondes :

C#

protected override async Task OnInitializedAsync()


{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.WithServerTimeout(TimeSpan.FromSeconds(60))
.Build();

hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

hubConnection.On<string, string>("ReceiveMessage", (user, message) =>


...

await hubConnection.StartAsync();
}

Lors de la modification des valeurs du délai d’expiration du serveur (ServerTimeout) ou


de l’intervalle Keep-Alive (KeepAliveInterval :

Le délai d’expiration du serveur doit être au moins le double de la valeur affectée à


l’intervalle Keep-Alive.
L’intervalle Keep-Alive doit être inférieur ou égal à la moitié de la valeur affectée au
délai d’expiration du serveur.

Pour plus d’informations, consultez Conseils relatifs à ASP.NET Core BlazorSignalR.


6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Configurez l’outil de découpage
(Trimmer) pour ASP.NET Core Blazor
Article • 09/02/2024

Cet article explique comment contrôler l’outil de découpage (Trimmer) de langage


intermédiaire (IL) pendant la génération d’une application Blazor.

Blazor WebAssembly effectue le découpage du langage intermédiaire (IL) pour réduire


la taille de la sortie publiée. Par défaut, le découpage se produit lors de la publication
d’une application.

Le découpage peut avoir des effets néfastes pour l’application publiée. Dans les
applications qui utilisent la réflexion, l’outil de découpage IL est souvent incapable de
déterminer les types nécessaires à la réflexion au moment de l’exécution, et de les
découper. Ainsi, les types d’infrastructures complexes pour l’interopérabilité JS, par
exemple KeyValuePair, peuvent être découpés par défaut, et ne pas être disponibles au
moment de l’exécution pour les appels d’interopérabilité JS. Dans ce genre de situation,
nous vous recommandons de créer vos propres types personnalisés à la place. L’outil de
découpage IL n’est pas non plus capable de réagir au comportement dynamique d’une
application au moment de l’exécution. Pour vous assurer que l’application découpée
fonctionne correctement une fois déployée, testez fréquemment la sortie publiée lors du
développement.

Pour configurer l’outil de découpage IL, consultez l’article Options de découpage dans la
documentation relative aux notions de base de .NET, qui comprend des conseils d’aide
sur les sujets suivants :

Désactivez le découpage pour l’ensemble de l’application avec la propriété


<PublishTrimmed> dans le Fichier projet.

Contrôler l’intensité avec laquelle l’outil de découpage IL doit abandonner l’IL


inutilisé.
Empêcher l’outil de découpage IL de découper des assemblys spécifiques.
Assemblys « racine » pour le découpage.
Les avertissements de surface pour les types réfléchis en définissant la propriété
<SuppressTrimAnalysisWarnings> sur false dans le Fichier projet.

Contrôlez le découpage des symboles et la prise en charge du débogueur.


Définir des fonctionnalités d’outil de découpage IL pour le découpage des
fonctionnalités de bibliothèque d’infrastructure.
Ressources supplémentaires
Découper les exécutables et les déploiements autonomes
Meilleures pratiques d’ASP.NET Core Blazor en matière de performances

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Disposition du déploiement pour les
applications Blazor WebAssembly
hébergées sur ASP.NET Core
Article • 09/02/2024

Cet article explique comment activer les déploiements de Blazor WebAssembly


hébergés dans les environnements qui bloquent le téléchargement et l’exécution de
fichiers DLL (bibliothèque de liens dynamiques).

7 Notes

Ce guide traite les environnements qui empêchent les clients de télécharger et


d’exécuter des bibliothèque de liens dynamiques (DLL). Dans .NET 8 ou version
ultérieure, Blazor utilise le format de fichier Webcil pour résoudre ce problème.
Pour plus d’informations, consultez Héberger et déployer ASP.NET Core Blazor
WebAssembly. Le regroupement multipartite à l’aide du package NuGet
expérimental décrit dans cet article n’est pas pris en charge pour les applications
Blazor dans .NET 8 ou ultérieur. Pour plus d’informations, consultez Améliorer le
package Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle pour
définir un format de regroupement personnalisé (dotnet/aspnetcore #36978) .
Vous pouvez utiliser les instructions de cet article pour créer votre propre package
NuGet de regroupement multipartite pour .NET 8 ou ultérieur.

Les applications Blazor WebAssembly nécessitent des DLL (bibliothèques de liens


dynamiques) pour fonctionner, mais certains environnements empêchent les clients de
télécharger et d’exécuter les DLL. Dans un sous-ensemble de ces environnements, le
changement de l’extension de nom de fichier des fichiers DLL (.dll) suffit pour
contourner les restrictions de sécurité. Toutefois, les produits de sécurité peuvent
souvent analyser le contenu des fichiers parcourant le réseau, et bloquer ou mettre en
quarantaine les fichiers DLL. Cet article décrit une approche permettant d’activer les
applications Blazor WebAssembly dans ces environnements, où un fichier de bundle en
plusieurs parties est créé à partir des DLL de l’application pour que ces DLL puissent être
téléchargées ensemble en contournant les restrictions de sécurité.

Une application Blazor WebAssembly hébergée peut personnaliser ses fichiers publiés et
son package de DLL d’application à l’aide des fonctionnalités suivantes :

Initialiseurs JavaScript, qui permettent de personnaliser le processus de démarrage


de Blazor.
Extensibilité MSBuild pour transformer la liste des fichiers publiés et définir des
extensions de publication Blazor. Les extensions de publication Blazor sont des
fichiers définis durant le processus de publication. Elles fournissent une autre
représentation de l’ensemble des fichiers nécessaires à l’exécution d’une
application Blazor WebAssembly publiée. Dans cet article, une extension de
publication Blazor est créée pour produire un bundle en plusieurs parties, où
toutes les DLL de l’application sont packagées dans un seul fichier afin d’être
téléchargées ensemble.

L’approche présentée dans cet article sert de point de départ aux développeurs pour
leur permettre de concevoir leurs propres stratégies et processus de chargement
personnalisés.

2 Avertissement

Toute approche adoptée pour contourner une restriction de sécurité doit être
soigneusement examinée en fonction de ses implications sur la sécurité. Nous vous
recommandons d’approfondir le sujet avec les professionnels de la sécurité réseau
de votre organisation avant d’adopter l’approche décrite dans cet article. Les
alternatives à prendre en compte sont les suivantes :

Activez les appliances de sécurité et les logiciels de sécurité pour permettre


aux clients réseau de télécharger et d’utiliser les fichiers exacts nécessaires à
une application Blazor WebAssembly.
Passez du modèle d’hébergement Blazor WebAssembly au modèle
d’hébergement Blazor Server, qui conserve l’ensemble du code C# de
l’application sur le serveur et ne nécessite pas le téléchargement de DLL sur
les clients. Blazor Server offre également l’avantage de garder le code C#
privé sans nécessiter l’utilisation d’applications API web pour la confidentialité
du code C# avec les applications Blazor WebAssembly.

Package NuGet expérimental et exemple


d’application
L’approche décrite dans cet article est utilisée par le package
expérimentalMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle
(NuGet.org) pour les applications ciblant .NET 6 ou ultérieur. Le package contient des
cibles MSBuild pour personnaliser la sortie de publication de Blazor ainsi qu’un
initialiseur JavaScript pour utiliser un chargeur de ressources de démarrage
personnalisé, chacun d’eux étant décrit en détail plus loin dans cet article.

Code expérimental (inclut la source de référence du package NuGet et l’exemple


d’application CustomPackagedApp)

2 Avertissement

Les fonctionnalités expérimentales et les fonctionnalités en préversion sont fournies


pour collecter des commentaires. Elles ne sont pas prises en charge en production.

Plus loin dans cet article, la section Personnaliser le processus de chargement de Blazor
WebAssembly via un package NuGet avec ses trois sous-sections fournit des
explications détaillées sur la configuration et le code dans le package
Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle . Il est important de

comprendre les explications détaillées quand vous créez votre propre stratégie et votre
propre processus de chargement personnalisé pour les applications Blazor
WebAssembly. Pour utiliser le package NuGet publié, expérimental et non pris en charge
sans personnalisation en tant que démonstration locale, suivez les étapes ci-dessous :

1. Utilisez une solutionBlazor WebAssembly hébergée existante, ou créez une


solution à partir du modèle de projet Blazor WebAssembly en utilisant Visual
Studio ou en passant l’option -ho|--hosted à la commandedotnet new ( dotnet new
blazorwasm -ho ). Pour plus d’informations, consultez Outils pour ASP.NET Core

Blazor.

2. Dans le projet Client, ajoutez le package


Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle expérimental.

7 Notes

Pour obtenir des conseils sur l’ajout de packages à des applications .NET,
consultez les articles figurant sous Installer et gérer des packages dans Flux de
travail de la consommation des packages (documentation NuGet). Vérifiez
les versions du package sur NuGet.org .

3. Dans le projet Server, ajoutez un point de terminaison pour la mise à disposition


du fichier bundle ( app.bundle ). Vous trouverez un exemple de code dans la section
Mettre à disposition le bundle à partir de l’application de serveur hôte au sein de
cet article.
4. Publiez l’application dans la configuration Release.

Personnaliser le processus de chargement de


Blazor WebAssembly via un package NuGet

2 Avertissement

Les conseils d’aide de cette section et de ses trois sous-sections concernent la


création d’un package NuGet à partir de zéro pour implémenter votre propre
stratégie et votre processus de chargement personnalisé. Le package
expérimentalMicrosoft.AspNetCore.Components.WebAssembly.MultipartBundle
(NuGet.org) pour .NET 6 et 7 est basé sur les instructions de cette section. Quand
vous utilisez le package fourni dans une démonstration locale de l’approche basée
sur le téléchargement de bundle en plusieurs parties, vous n’avez pas besoin de
suivre les conseils d’aide de cette section. Pour obtenir des conseils d’aide sur
l’utilisation du package fourni, consultez la section Package NuGet expérimental et
exemple d’application.

Les ressources d’application Blazor sont compressées dans un fichier bundle en


plusieurs parties et chargées par le navigateur via un initialiseur JavaScript (JS)
personnalisé. Pour une application qui consomme le package avec l’initialiseur JS, celle-
ci nécessite uniquement la mise à disposition du fichier bundle sur demande. Tous les
autres aspects de cette approche sont gérés de manière transparente.

Quatre personnalisations sont nécessaires pour le mode de chargement d’une


application Blazor publiée par défaut :

Une tâche MSBuild pour transformer les fichiers de publication.


Un package NuGet avec des cibles MSBuild, qui se connecte au processus de
publication Blazor, transforme la sortie et définit un ou plusieurs fichiers
d’extension de publication Blazor (dans ce cas, un seul bundle).
Un initialiseur JS pour mettre à jour le rappel du chargeur de ressources Blazor
WebAssembly afin qu’il charge le bundle et fournisse les fichiers individuels à
l’application.
Un outil d’assistance sur l’application Server hôte pour vérifier que le bundle est
mis à disposition des clients sur demande.

Créer une tâche MSBuild pour personnaliser la liste des


fichiers publiés et définir de nouvelles extensions
Créez une tâche MSBuild en tant que classe C# publique pouvant être importée dans le
cadre d’une compilation MSBuild et pouvant interagir avec la build.

Les éléments suivants sont nécessaires pour la classe C# :

Un nouveau projet de bibliothèque de classes.


Un framework cible de projet netstandard2.0 .
Références aux packages MSBuild :
Microsoft.Build.Framework
Microsoft.Build.Utilities.Core

7 Notes

Le package NuGet des exemples de cet article est nommé d’après le package fourni
par Microsoft, Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle . Pour
obtenir des conseils d’aide sur le nommage et la production de votre propre
package NuGet, consultez les articles NuGet suivants :

Meilleures pratiques de création de packages


Réservation du préfixe d’ID de package

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks/Microsoft.AspNetC

ore.Components.WebAssembly.MultipartBundle.Tasks.csproj :

XML

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>8.0</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="
{VERSION}" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="
{VERSION}" />
</ItemGroup>

</Project>

Déterminez quelles sont les dernières versions de package pour les espaces réservés
{VERSION} sur NuGet.org :
Microsoft.Build.Framework
Microsoft.Build.Utilities.Core

Pour créer la tâche MSBuild, créez une classe C# publique qui étend
Microsoft.Build.Utilities.Task (et non System.Threading.Tasks.Task), puis déclarez trois
propriétés :

PublishBlazorBootStaticWebAsset : liste des fichiers à publier pour l’application

Blazor.
BundlePath : chemin où le bundle est écrit.
Extension : nouvelles extensions de publication à inclure dans la build.

L’exemple de classe BundleBlazorAssets suivant est le point de départ d’une


personnalisation supplémentaire :

Dans la méthode Execute , le bundle est créé à partir des trois types de fichier
suivants :
Fichiers JavaScript ( dotnet.js )
Fichiers WASM ( dotnet.wasm )
DLL d’application ( .dll )
Un bundle multipart/form-data est créé. Chaque fichier est ajouté au bundle avec
ses descriptions respectives via l’en-tête Content-Disposition et l’en-tête
Content-Type .
Une fois le bundle créé, il est écrit dans un fichier.
La build est configurée pour l’extension. Le code suivant crée un élément
d’extension et l’ajoute à la propriété Extension . Chaque élément d’extension
contient trois sortes de données :
Chemin du fichier d’extension.
Chemin d’URL relatif à la racine de l’application Blazor WebAssembly.
Nom de l’extension, qui regroupe les fichiers produits par une extension
donnée.

Une fois les objectifs atteints, la tâche MSBuild est créée pour permettre la
personnalisation de la sortie de publication Blazor. Blazor se charge de collecter les
extensions et de vérifier qu’elles sont copiées à l’emplacement approprié dans le dossier
de sortie de publication (par exemple bin\Release\net6.0\publish ). Les fichiers
JavaScript, WASM et DLL bénéficient des mêmes optimisations (par exemple la
compression) que celles que Blazor applique aux autres fichiers.

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks/BundleBlazorAsset

s.cs :
C#

using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks
{
public class BundleBlazorAssets : Task
{
[Required]
public ITaskItem[]? PublishBlazorBootStaticWebAsset { get; set; }

[Required]
public string? BundlePath { get; set; }

[Output]
public ITaskItem[]? Extension { get; set; }

public override bool Execute()


{
var bundle = new MultipartFormDataContent(
"--0a7e8441d64b4bf89086b85e59523b7d");

foreach (var asset in PublishBlazorBootStaticWebAsset)


{
var name =
Path.GetFileName(asset.GetMetadata("RelativePath"));
var fileContents = File.OpenRead(asset.ItemSpec);
var content = new StreamContent(fileContents);
var disposition = new ContentDispositionHeaderValue("form-
data");
disposition.Name = name;
disposition.FileName = name;
content.Headers.ContentDisposition = disposition;
var contentType = Path.GetExtension(name) switch
{
".js" => "text/javascript",
".wasm" => "application/wasm",
_ => "application/octet-stream"
};
content.Headers.ContentType =
MediaTypeHeaderValue.Parse(contentType);
bundle.Add(content);
}

using (var output = File.Open(BundlePath,


FileMode.OpenOrCreate))
{
output.SetLength(0);

bundle.CopyToAsync(output).ConfigureAwait(false).GetAwaiter()
.GetResult();
output.Flush(true);
}

var bundleItem = new TaskItem(BundlePath);


bundleItem.SetMetadata("RelativePath", "app.bundle");
bundleItem.SetMetadata("ExtensionName", "multipart");

Extension = new ITaskItem[] { bundleItem };

return true;
}
}
}

Créer un package NuGet pour transformer


automatiquement la sortie de publication
Générez un package NuGet avec des cibles MSBuild qui sont automatiquement incluses
quand le package est référencé :

Créez un projet de bibliothèque de classes (RCL) Razor.


Créez un fichier de cibles en suivant les conventions NuGet pour importer
automatiquement le package dans les projets qui le consomment. Par exemple,
créez build\net6.0\{PACKAGE ID}.targets , où {PACKAGE ID} est l’identificateur de
package du package.
Collectez la sortie de la bibliothèque de classes contenant la tâche MSBuild, puis
vérifiez qu’elle est compressée au bon emplacement.
Ajoutez le code MSBuild nécessaire à l’attachement au pipeline Blazor, puis
appelez la tâche MSBuild pour générer le bundle.

L’approche décrite dans cette section utilise uniquement le package pour remettre des
cibles et du contenu, ce qui diffère de la plupart des packages où le package comprend
une DLL de bibliothèque.

2 Avertissement

L’exemple de package décrit dans cette section montre comment personnaliser le


processus de publication Blazor. L’exemple de package NuGet est à utiliser à des
fins de démonstration locale uniquement. L’utilisation de ce package en
production n’est pas prise en charge.

7 Notes
Le package NuGet des exemples de cet article est nommé d’après le package fourni
par Microsoft, Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle . Pour
obtenir des conseils d’aide sur le nommage et la production de votre propre
package NuGet, consultez les articles NuGet suivants :

Meilleures pratiques de création de packages


Réservation du préfixe d’ID de package

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/Microsoft.AspNetCore.Co
mponents.WebAssembly.MultipartBundle.csproj :

XML

<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<NoWarn>NU5100</NoWarn>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>
Sample demonstration package showing how to customize the Blazor
publish
process. Using this package in production is not supported!
</Description>
<IsPackable>true</IsPackable>
<IsShipping>true</IsShipping>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>

<ItemGroup>
<None Update="build\**"
Pack="true"
PackagePath="%(Identity)" />
<Content Include="_._"
Pack="true"
PackagePath="lib\net6.0\_._" />
</ItemGroup>

<Target Name="GetTasksOutputDlls"
BeforeTargets="CoreCompile">
<MSBuild
Projects="..\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tas
ks\Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.csproj"
Targets="Publish;PublishItemsOutputGroup"
Properties="Configuration=Release">
<Output TaskParameter="TargetOutputs"
ItemName="_TasksProjectOutputs" />
</MSBuild>
<ItemGroup>
<Content Include="@(_TasksProjectOutputs)"
Condition="'%(_TasksProjectOutputs.Extension)' == '.dll'"
Pack="true"
PackagePath="tasks\%(_TasksProjectOutputs.TargetPath)"
KeepMetadata="Pack;PackagePath" />
</ItemGroup>
</Target>

</Project>

7 Notes

La propriété <NoWarn>NU5100</NoWarn> de l’exemple précédent supprime


l’avertissement relatif aux assemblys placés dans le dossier tasks . Pour plus
d’informations, consultez l’Avertissement NuGet NU5100.

Ajoutez un fichier .targets pour connecter la tâche MSBuild au pipeline de build. Dans
ce fichier, les objectifs suivants sont atteints :

Importer la tâche dans le processus de génération. Notez que le chemin de la DLL


est relatif à l’emplacement final du fichier dans le package.
La propriété ComputeBlazorExtensionsDependsOn attache la cible personnalisée au
pipeline Blazor WebAssembly.
Capturer la propriété Extension dans la sortie de la tâche, puis l’ajouter à
BlazorPublishExtension pour indiquer à Blazor l’existence de l’extension. L’appel

de la tâche dans la cible produit le bundle. La liste des fichiers publiés est fournie
par le pipeline Blazor WebAssembly dans le groupe d’éléments
PublishBlazorBootStaticWebAsset . Le chemin du bundle est défini à l’aide de
IntermediateOutputPath (généralement dans le dossier obj ). Pour finir, le bundle

est copié automatiquement à l’emplacement approprié dans le dossier de sortie de


publication (par exemple bin\Release\net6.0\publish ).

Quand le package est référencé, il génère un bundle des fichiers Blazor durant la
publication.

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/build/net6.0/Microsoft.

AspNetCore.Components.WebAssembly.MultipartBundle.targets :

XML

<Project>
<UsingTask

TaskName="Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.Tasks.
BundleBlazorAssets"

AssemblyFile="$(MSBuildThisProjectFileDirectory)..\..\tasks\Microsoft.AspNet
Core.Components.WebAssembly.MultipartBundle.Tasks.dll" />

<PropertyGroup>
<ComputeBlazorExtensionsDependsOn>
$(ComputeBlazorExtensionsDependsOn);_BundleBlazorDlls
</ComputeBlazorExtensionsDependsOn>
</PropertyGroup>

<Target Name="_BundleBlazorDlls">
<BundleBlazorAssets
PublishBlazorBootStaticWebAsset="@(PublishBlazorBootStaticWebAsset)"
BundlePath="$(IntermediateOutputPath)bundle.multipart">
<Output TaskParameter="Extension"
ItemName="BlazorPublishExtension"/>
</BundleBlazorAssets>
</Target>

</Project>

Démarrer automatiquement Blazor à partir du bundle


Le package NuGet tire parti des initialiseurs JavaScript (JS) pour démarrer
automatiquement une application Blazor WebAssembly à partir du bundle, au lieu
d’utiliser des fichiers DLL individuels. Les initialiseurs JS permettent de changer le
chargeur de ressources de démarrageBlazoret d’utiliser le bundle.

Pour créer un initialiseur JS, ajoutez un fichier JS portant le nom {NAME}.lib.module.js


au dossier wwwroot du projet de package, où l’espace réservé {NAME} correspond à
l’identificateur de package. Par exemple, le fichier du package Microsoft se nomme
Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle.lib.module.js . Les

fonctions exportées beforeWebAssemblyStart et afterWebAssemblyStarted gèrent le


chargement.

Les initialiseurs JS :

Détectent si l’extension de publication est disponible en recherchant


extensions.multipart , qui correspond au nom de l’extension ( ExtensionName )

fourni dans la section Créer une tâche MSBuild pour personnaliser la liste des
fichiers publiés et définir de nouvelles extensions.
Téléchargent le bundle, et analysent son contenu dans un mappage des ressources
à l’aide des URL d’objet générées.
Mettent à jour le chargeur de ressources de démarrage
(options.loadBootResource) avec une fonction personnalisée qui résout les
ressources à l’aide des URL d’objets.
Une fois l’application démarrée, révoquent les URL d’objets pour libérer de la
mémoire dans la fonction afterWebAssemblyStarted .

Microsoft.AspNetCore.Components.WebAssembly.MultipartBundle/wwwroot/Microsoft.AspNe

tCore.Components.WebAssembly.MultipartBundle.lib.module.js :

JavaScript

const resources = new Map();

export async function beforeWebAssemblyStart(options, extensions) {


if (!extensions || !extensions.multipart) {
return;
}

try {
const integrity = extensions.multipart['app.bundle'];
const bundleResponse =
await fetch('app.bundle', { integrity: integrity, cache: 'no-cache'
});
const bundleFromData = await bundleResponse.formData();
for (let value of bundleFromData.values()) {
resources.set(value, URL.createObjectURL(value));
}
options.loadBootResource = function (type, name, defaultUri, integrity)
{
return resources.get(name) ?? null;
}
} catch (error) {
console.log(error);
}
}

export async function afterWebAssemblyStarted(blazor) {


for (const [_, url] of resources) {
URL.revokeObjectURL(url);
}
}

Mettre à disposition le bundle à partir de


l’application de serveur hôte
Pour des raisons de sécurité, ASP.NET Core ne met pas à disposition le fichier
app.bundle par défaut. Un outil d’assistance aux demandes est nécessaire pour mettre à

disposition le fichier quand il est demandé par les clients.


7 Notes

Dans la mesure où les mêmes optimisations sont appliquées de manière


transparente aux extensions de publication et aux fichiers de l’application, les
fichiers de ressources compressés app.bundle.gz et app.bundle.br sont produits
automatiquement au moment de la publication.

Placez le code C# dans le Program.cs du projet Server immédiatement avant la ligne qui
définit le fichier de secours en tant que index.html
( app.MapFallbackToFile("index.html"); ) pour répondre à une demande de fichier
bundle (par exemple app.bundle ) :

C#

app.MapGet("app.bundle", (HttpContext context) =>


{
string? contentEncoding = null;
var contentType =
"multipart/form-data; boundary=\"-
-0a7e8441d64b4bf89086b85e59523b7d\"";
var fileName = "app.bundle";

var acceptEncodings = context.Request.Headers.AcceptEncoding;

if (Microsoft.Net.Http.Headers.StringWithQualityHeaderValue
.StringWithQualityHeaderValue
.TryParseList(acceptEncodings, out var encodings))
{
if (encodings.Any(e => e.Value == "br"))
{
contentEncoding = "br";
fileName += ".br";
}
else if (encodings.Any(e => e.Value == "gzip"))
{
contentEncoding = "gzip";
fileName += ".gz";
}
}

if (contentEncoding != null)
{
context.Response.Headers.ContentEncoding = contentEncoding;
}

return Results.File(
app.Environment.WebRootFileProvider.GetFileInfo(fileName)
.CreateReadStream(), contentType);
});

Le type de contenu correspond au type défini plus tôt dans la tâche de build. Le point
de terminaison vérifie les codages de contenu acceptés par le navigateur et met à
disposition le fichier optimal, Brotli ( .br ) ou Gzip ( .gz ).

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
ASP.NET Core Blazor avec
Entity Framework Core (EF Core)
Article • 09/02/2024

Cet article explique comment utiliser Entity Framework Core (EF Core) dans les
applications Blazor côté serveur.

Blazor côté serveur est un framework d’application avec état. L’application maintient une
connexion continue au serveur, et l’état de l’utilisateur est conservé dans la mémoire du
serveur dans un circuit. Des données conservées dans des instances de service
d’injection de dépendances qui sont limitées au circuit constituent un exemple d’état
utilisateur. Le modèle d’application unique fourni par Blazor nécessite une approche
spéciale pour utiliser Entity Framework Core.

7 Notes

Cet article aborde EF Core dans les applications Blazor côté serveur. Les
applications Blazor WebAssembly s’exécutent dans un bac à sable (sandbox)
WebAssembly qui empêche la plupart des connexions de base de données directes.
L’exécution d’EF Core dans Blazor WebAssembly n’entre pas dans le cadre de cet
article.

Ces conseils d’aide s’appliquent aux composants qui adoptent le rendu côté serveur
interactif (SSR interactif) dans une application web Blazor fonctionnant via une
connexion SignalR avec le client.

Les exemples de composants de la documentation ne configurent généralement pas de


mode d’affichage interactif avec une directive @rendermode dans le fichier de définition
du composant ( .razor ), mais le mode d’affichage Serveur interactif doit être appliqué
au composant ( @rendermode InteractiveServer ). Ce mode peut être spécifié dans le
fichier de définition du composant ou hérité d’un composant parent. Pour plus
d’informations, consultez Modes de rendu ASP.NET Core Blazor.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure de projet ASP.NET Core Blazor, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

Le meilleur moyen d’exécuter le code de démonstration est de télécharger les exemples


d’applications BlazorSample_{PROJECT TYPE} à partir du dépôt GitHub d’exemples
Blazor qui correspond à la version de .NET que vous ciblez. Pour le moment, les
exemples de la documentation ne figurent pas tous dans les exemples d’applications,
mais nous nous employons à transférer la majorité des exemples de l’article .NET 8 dans
les exemples d’applications. Nous aurons terminé ces transferts dans le courant du
premier trimestre 2024.

Exemple d’application
L’exemple d’application a été généré comme référence pour les applications Blazor côté
serveur qui utilisent EF Core. L’exemple d’application inclut une grille avec des
opérations de tri et de filtrage, de suppression, d’ajout et de mise à jour. L’exemple
illustre l’utilisation d’EF Core pour gérer l’accès concurrentiel optimiste.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

L’exemple utilise une base de données SQLite locale afin qu’elle puisse être utilisée
sur n’importe quelle plateforme. L’exemple configure également la journalisation de la
base de données pour afficher les requêtes SQL qui sont générées. Cette fonctionnalité
est configurée dans appsettings.Development.json :

JSON

{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}

Les composants de grille, d’ajout et d’affichage utilisent le modèle « contexte par


opération », où un contexte est créé pour chaque opération. Le composant d’édition
utilise le modèle « contexte par composant », où un contexte est créé pour chaque
composant.

7 Notes

Certains des exemples de code de cette rubrique nécessitent des espaces de noms
et des services qui ne sont pas affichés. Pour inspecter le code entièrement
opérationnel, ce qui inclut les directives @using et @inject requises pour des
exemples Razor, consultez l’exemple d’application .

Accès aux bases de données


EF Core s’appuie sur un DbContext comme moyen de configurer l’accès à la base de
données et d’agir comme une unité de travail . EF Core fournit l’extension
AddDbContext pour les applications ASP.NET Core qui inscrit le contexte en tant que
service délimité par défaut. Dans les applications Blazor côté serveur, les inscriptions de
service délimité peuvent être problématiques, car l’instance est partagée entre les
composants du circuit de l’utilisateur. DbContext n’est pas thread-safe et n’est pas
conçu pour une utilisation simultanée. Les durées de vie existantes sont inappropriées
pour les raisons suivantes :

Singleton partage l’état entre tous les utilisateurs de l’application et conduit à une
utilisation simultanée inappropriée.
Délimité (valeur par défaut) pose un problème similaire entre les composants pour
le même utilisateur.
Temporaire entraîne la création d’une instance par requête. Toutefois, comme les
composants peuvent être de longue durée, il en résulte un contexte d’une plus
longue durée que prévu.

Les recommandations suivantes sont conçues pour fournir une approche cohérente de
l’utilisation d’EF Core dans les applications Blazor côté serveur.

Par défaut, envisagez d’utiliser un contexte par opération. Le contexte est conçu
pour une instanciation rapide et à faible charge :

C#

using var context = new MyContext();

return await context.MyEntities.ToListAsync();

Utilisez un indicateur pour empêcher plusieurs opérations simultanées :

C#

if (Loading)
{
return;
}

try
{
Loading = true;

...
}
finally
{
Loading = false;
}

Placez les opérations après la ligne Loading = true; dans le bloc try .

La logique de chargement ne nécessite pas de verrouillage des enregistrements de


base de données, car la cohérence de thread n’est pas un problème. La logique de
chargement est utilisée pour désactiver les contrôles d’interface utilisateur afin que
les utilisateurs ne sélectionnent pas par inadvertance des boutons ou ne mettent
pas à jour les champs pendant la récupération de données.

S’il est possible que plusieurs threads accèdent au même bloc de code, injectez
une fabrique et créez une nouvelle instance par opération. Autrement, l’injection et
l’utilisation du contexte sont généralement suffisantes.

Pour les opérations de plus longue durée qui tirent parti du suivi des modifications
ou du contrôle d’accès concurrentiel, d’EF Core, limitez le contexte à la durée de
vie du composant.

Nouvelles instances DbContext


Le moyen le plus rapide de créer une instance DbContext consiste à utiliser new .
Toutefois, certains scénarios nécessitent la résolution de dépendances supplémentaires :

Utilisation d’DbContextOptions pour configurer le contexte.


Utilisation d’une chaîne de connexion par DbContext, comme lorsque vous utilisez
le modèle Identity d’ASP.NET Core. Pour plus d’informations, consultez
Architecture multilocataire (documentation EF Core).

L’approche recommandée pour créer un DbContext avec des dépendances consiste à


utiliser une fabrique. EF Core 5.0 ou version ultérieure fournit une fabrique intégrée pour
la création de contextes.

L’exemple suivant configure SQLite et active la journalisation des données. Le code


utilise une méthode d’extension ( AddDbContextFactory ) afin de configurer la fabrique de
base de données pour l’injection de dépendances et de fournir des options par défaut :
C#

builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"));

La fabrique est injectée dans les composants et est utilisée pour créer des instances
DbContext .

Sur la page d'accueil de l’exemple d’application, IDbContextFactory<ContactContext> est


injecté dans le composant :

razor

@inject IDbContextFactory<ContactContext> DbFactory

Un DbContext est créé à l’aide de la fabrique ( DbFactory ) pour supprimer un contact


dans la méthode DeleteContactAsync :

razor

private async Task DeleteContactAsync()


{
using var context = DbFactory.CreateDbContext();
Filters.Loading = true;

if (Wrapper is not null && context.Contacts is not null)


{
var contact = await context.Contacts
.FirstAsync(c => c.Id == Wrapper.DeleteRequestId);

if (contact is not null)


{
context.Contacts?.Remove(contact);
await context.SaveChangesAsync();
}
}

Filters.Loading = false;

await ReloadAsync();
}

7 Notes

Filters est un IContactFilters injecté et Wrapper est une référence de

composant au composant GridWrapper . Consultez le composant Home


( Components/Pages/Home.razor ) dans l’exemple d’application.

Délimiter à la durée de vie du composant


Vous pouvez souhaiter créer un DbContext qui existe pendant toute la durée de vie d’un
composant. Cela vous permet de l’utiliser comme unité de travail et de tirer parti des
fonctionnalités intégrées, telles que le suivi des modifications et la résolution de l’accès
concurrentiel.

Vous pouvez utiliser la fabrique pour créer un contexte et le suivre pendant la durée de
vie du composant. Commencez par implémenter IDisposable et injectez la fabrique
comme indiqué dans le composant EditContact ( Components/Pages/EditContact.razor ) :

razor

@implements IDisposable
@inject IDbContextFactory<ContactContext> DbFactory

L’exemple d’application garantit que le contexte est supprimé lorsque le composant est
supprimé :

C#

public void Dispose()


{
Context?.Dispose();
}

Enfin, OnInitializedAsync est remplacé pour créer un nouveau contexte. Dans l’exemple
d’application, OnInitializedAsync charge le contact dans la même méthode :

C#

protected override async Task OnInitializedAsync()


{
Busy = true;

try
{
Context = DbFactory.CreateDbContext();

if (Context is not null && Context.Contacts is not null)


{
var contact = await Context.Contacts.SingleOrDefaultAsync(c =>
c.Id == ContactId);
if (contact is not null)
{
Contact = contact;
}
}
}
finally
{
Busy = false;
}

await base.OnInitializedAsync();
}

Activer la journalisation des données sensibles


EnableSensitiveDataLogging inclut les données d’application dans les messages
d’exception et la journalisation d’infrastructure. Les données journalisées peuvent inclure
les valeurs attribuées aux propriétés des instances d’entité et les valeurs de paramètres
pour les commandes envoyées à la base de données. La journalisation des données avec
EnableSensitiveDataLogging constitue un risque de sécurité, car elle peut exposer des
mots de passe et d’autres informations d’identification personnelle lors de la
journalisation d’instructions SQL exécutées sur la base de données.

Nous vous recommandons d’activer EnableSensitiveDataLogging uniquement pour le


développement et le test :

C#

#if DEBUG
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")
.EnableSensitiveDataLogging());
#else
services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source=
{nameof(ContactContext.ContactsDb)}.db"));
#endif

Ressources supplémentaires
Documentation EF Core
Blazor exemples de référentiel GitHub (dotnet/blazor-samples)
6 Collaborer avec nous sur Commentaires sur ASP.NET
GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Éviter les problèmes de mise en cache
HTTP lors de la mise à niveau
d’applications ASP.NET Core Blazor
Article • 09/02/2024

Lorsque les applications Blazor ne sont pas correctement mises à niveau ou configurées,
cela peut entraver la fluidité des mises à niveau pour les utilisateurs existants. Cet article
décrit certains des problèmes courants de mise en cache HTTP qui peuvent se produire
lors de la mise à niveau d’applications Blazor entre des versions principales. Il fournit
également certaines actions recommandées pour garantir une transition fluide pour vos
utilisateurs.

Bien que les futures versions de Blazor puissent fournir de meilleures solutions pour
traiter les problèmes de mise en cache HTTP, il revient finalement à l’application de
configurer correctement la mise en cache. Une configuration de mise en cache
appropriée garantit que les utilisateurs de l’application disposent toujours de la version
la plus récente de l’application, ce qui améliore leur expérience et réduit la probabilité
de rencontrer des erreurs.

Les problèmes courants qui ont un impact négatif sur l’expérience de mise à niveau de
l’utilisateur sont les suivants :

Gestion incorrecte des mises à jour de projets et de packages : cela se produit si


vous ne mettez pas à jour tous les projets déployés de l’application pour qu’ils
utilisent la même version principale du framework ou si vous utilisez des packages
d’une version précédente lorsqu’une version plus récente est disponible dans le
cadre de la mise à niveau majeure.
Configuration incorrecte des en-têtes de mise en cache : les en-têtes de mise en
cache HTTP contrôlent comment, où et pendant combien de temps les réponses
de l’application sont mises en cache. Si les en-têtes ne sont pas configurés
correctement, les utilisateurs peuvent recevoir du contenu obsolète.
Configuration incorrecte d’autres couches : les réseaux de distribution de contenu
(CDN) et d’autres couches de l’application déployée peuvent entraîner des
problèmes s’ils sont mal configurés. Par exemple, les CDN sont conçus pour mettre
en cache et fournir du contenu afin d’améliorer les performances et de réduire la
latence. Si un CDN sert incorrectement des versions mises en cache des ressources,
cela peut entraîner une remise de contenu obsolète à l’utilisateur.
Détecter et diagnostiquer les problèmes de
mise à niveau
Les problèmes de mise à niveau apparaissent généralement comme un échec de
démarrage de l’application dans le navigateur. Normalement, un avertissement indique
la présence d’une ressource obsolète ou d’une ressource manquante ou incohérente
avec l’application.

Tout d’abord, vérifiez si l’application se charge correctement dans une instance de


navigateur propre. Utilisez un mode de navigateur privé pour charger l’application,
par exemple en mode InPrivate Microsoft Edge ou en mode Incognito Google
Chrome. Si l’application ne parvient pas à charger, cela signifie probablement
qu’un ou plusieurs packages n’ont pas été correctement mis à jour ou que
l’infrastructure n’a pas correctement été mise à jour.
Si l’application se charge correctement dans une instance de navigateur propre, il
est probable que l’application soit traitée à partir d’un cache obsolète. Dans la
plupart des cas, une actualisation forcée du navigateur avec Ctrl + F5 vide le
cache, ce qui permet à l’application de charger et de s’exécuter avec les dernières
ressources.
Si l’application continue d’échouer, il est probable qu’un cache CDN obsolète serve
l’application. Essayez de vider le cache DNS via le mécanisme proposé par votre
fournisseur CDN.

Actions recommandées avant une mise à


niveau
Le processus précédent de service de l’application peut rendre le processus de mise à
jour plus difficile. Par exemple, avoir évité ou mal utilisé des en-têtes de mise en cache
par le passé peut entraîner des problèmes actuels de mise en cache pour les utilisateurs.
Vous pouvez effectuer les actions décrites dans les sections suivantes pour atténuer le
problème et améliorer le processus de mise à niveau pour les utilisateurs.

Aligner les packages d’infrastructure avec la version du


framework
Assurez-vous que les packages d’infrastructure correspondent à la version du
framework. L’utilisation de packages à partir d’une version précédente lorsqu’une
version plus récente est disponible peut entraîner des problèmes de compatibilité. Il est
également important de s’assurer que tous les projets déployés de l’application utilisent
la même version principale du framework. Cette cohérence permet d’éviter un
comportement ou des erreurs inattendus.

Vérifier la présence d’en-têtes de mise en cache corrects


Les en-têtes de mise en cache corrects doivent être présents sur les réponses aux
demandes de ressources. Cela inclut ETag , Cache-Control et d’autres en-têtes de mise
en cache. La configuration de ces en-têtes dépend du service d’hébergement ou de la
plateforme de serveur d’hébergement. Ils sont particulièrement importants pour les
ressources telles que le script Blazor ( blazor.webassembly.js ) et tout ce que le script
télécharge.

Des en-têtes de mise en cache HTTP incorrects peuvent également avoir un impact sur
les workers de service. Les workers de service s’appuient sur la mise en cache des en-
têtes pour gérer efficacement les ressources mises en cache. Par conséquent, les en-
têtes incorrects ou manquants peuvent perturber la fonctionnalité du worker de service.

Utiliser Clear-Site-Data pour supprimer l’état dans le


navigateur
Envisagez d’utiliser l’en-tête Clear-Site-Data pour supprimer l’état dans le navigateur.

En règle générale, la source des problèmes d’état du cache est limitée au cache du
navigateur HTTP. L’utilisation de la directive cache doit donc être suffisante. Cette action
peut vous aider à vous assurer que le navigateur récupère les ressources les plus
récentes du serveur, plutôt que de servir du contenu obsolète à partir du cache.

Vous pouvez éventuellement inclure la directive storage pour effacer les caches de
stockage locaux en même temps que vous effacez le cache du navigateur HTTP.
Toutefois, les applications qui utilisent le stockage client peuvent rencontrer une perte
d’informations importantes si la directive storage est utilisée.

Ajouter une chaîne de requête à la balise de script Blazor


Si aucune des actions recommandées précédentes n’est efficace ou utilisable pour votre
déploiement, ou qu’elles ne peuvent être appliquées à votre application, envisagez
d’ajouter temporairement une chaîne de requête à la source de balise <script> du
script Blazor. Dans la plupart des cas, cette action devrait suffire à obliger le navigateur à
contourner le cache HTTP local et à télécharger une nouvelle version de l’application. Il
n’est pas nécessaire de lire ou d’utiliser la chaîne de requête dans l’application.
Dans l’exemple suivant, la chaîne de requête temporaryQueryString=1 est
temporairement appliquée à l’URI source externe relative de la balise <script> :

HTML

<script src="_framework/blazor.webassembly.js?temporaryQueryString=1">
</script>

Une fois que tous les utilisateurs de l’application ont rechargé l’application, la chaîne de
requête peut être supprimée.

Vous pouvez également appliquer une chaîne de requête persistante avec le contrôle de
version approprié. L’exemple suivant suppose que la version de l’application correspond
à la version de publication .NET ( 8 pour .NET 8) :

HTML

<script src="_framework/blazor.webassembly.js?version=8"></script>

Pour connaître l’emplacement de la balise <script> du script Blazor, consultez ASP.NET


Core Blazor structure du projet.

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Scénarios avancés ASP.NET Core Blazor
(arborescence de rendu)
Article • 21/12/2023

Cet article décrit le scénario avancé de création d’arborescences de rendu Blazor


manuellement avec RenderTreeBuilder.

2 Avertissement

L’utilisation de RenderTreeBuilder pour créer des composants est un scénario


avancé. Un composant mal formé (par exemple, une balise non fermée) peut
entraîner un comportement imprévisible. Ce comportement peut inclure un rendu
du contenu défectueux, la perte de fonctionnalités d’application et une sécurité
compromise.

Dans cet article, les termes serveur/côté serveur et client/côté client sont utilisés pour
distinguer les emplacements où le code d’application s’exécute :

Serveur/côté serveur : rendu côté serveur interactif (SSR interactif) d’une


application web Blazor.
Client/côté client
Rendu côté client (CSR) d’une application web Blazor.
Application Blazor WebAssembly.

Les exemples de composants de documentation ne configurent généralement pas de


mode de rendu interactif avec une directive @rendermode dans le fichier de définition du
composant ( .razor ) :

Dans une application web Blazor, le composant doit avoir un mode de rendu
interactif appliqué, soit dans le fichier de définition du composant, soit hérité d’un
composant parent. Pour plus d’informations, consultez Modes de rendu ASP.NET
Core Blazor.

Dans une application Blazor WebAssembly autonome, les composants


fonctionnent tels qu’ils sont présentés et ne nécessitent pas de mode de rendu, car
les composants s’exécutent toujours de manière interactive sur WebAssembly dans
une application Blazor WebAssembly.

Lors de l’utilisation des modes WebAssembly interactif ou Rendu automatique interactif,


le code du composant envoyé au client peut être décompilé et inspecté. N’insérez pas
de code privé, de secrets d’application ou d’autres informations personnelles dans les
composants du rendu client.

Pour obtenir de l’aide sur l’objectif et l’emplacement des fichiers et des dossiers,
consultez la structure du projet Blazor ASP.NET Core, qui décrit également
l’emplacement du script de démarrage Blazor et l’emplacement des contenus <head> et
<body>.

La meilleure façon d’exécuter le code de démonstration est de télécharger


les BlazorSample_{PROJECT TYPE} exemples d’applications à partir du référentiel GitHub
dotnet/blazor-samples qui correspond à la version de .NET que vous ciblez. Pour le
moment, tous les exemples de la documentation ne sont pas inclus dans les exemples
d’applications, mais nous nous efforçons de transférer la majorité des exemples de
l’article .NET 8 dans les exemples d’applications .NET 8. Nous aurons terminé ces
transferts dans le courant du premier trimestre 2024.

Créer manuellement une arborescence de


rendu ( RenderTreeBuilder )
RenderTreeBuilder fournit des méthodes pour manipuler des composants et des
éléments, notamment la création manuelle de composants dans du code C#.

Considérez le composant PetDetails suivant, qui peut être restitué manuellement dans
un autre composant.

PetDetails.razor :

razor

<h2>Pet Details</h2>

<p>@PetDetailsQuote</p>

@code
{
[Parameter]
public string? PetDetailsQuote { get; set; }
}

Dans le composant BuiltContent suivant, la boucle de la méthode CreateComponent


génère trois composants PetDetails .

Dans les méthodes RenderTreeBuilder avec un numéro de séquence, les numéros de


séquence sont des numéros de ligne de code source. L’algorithme de différence Blazor
s’appuie sur les numéros de séquence correspondant à des lignes de code distinctes, et
non à des appels d’appel distincts. Lors de la création d’un composant avec des
méthodes RenderTreeBuilder, codez en dur les arguments des numéros de séquence.
L’utilisation d’un calcul ou d’un compteur pour générer le numéro de séquence peut
entraîner des performances médiocres. Pour plus d’informations, consultez la section
Les numéros de séquence sont liés aux numéros de ligne de code et non à l’ordre
d’exécution.

BuiltContent.razor :

razor

@page "/built-content"

<PageTitle>Built Content</PageTitle>

<h1>Built Content Example</h1>

<div>
@CustomRender
</div>

<button @onclick="RenderComponent">
Create three Pet Details components
</button>

@code {
private RenderFragment? CustomRender { get; set; }

private RenderFragment CreateComponent() => builder =>


{
for (var i = 0; i < 3; i++)
{
builder.OpenComponent(0, typeof(PetDetails));
builder.AddAttribute(1, "PetDetailsQuote", "Someone's best
friend!");
builder.CloseComponent();
}
};

private void RenderComponent()


{
CustomRender = CreateComponent();
}
}

2 Avertissement
Les types dans Microsoft.AspNetCore.Components.RenderTree autorisent le
traitement des résultats des opérations de rendu. Il s’agit des détails internes de
l’implémentation du framework Blazor. Ces types doivent être considérés comme
instables et susceptibles d’être modifiés dans les versions ultérieures.

Les numéros de séquence sont liés aux numéros de ligne


de code et non à l’ordre d’exécution
Les fichiers de composants Razor ( .razor ) sont toujours compilés. L’exécution du code
compilé présente un avantage potentiel par rapport à l’interprétation du code, car
l’étape de compilation qui génère le code compilé peut être utilisée pour injecter des
informations qui améliorent les performances de l’application au moment de
l’exécution.

Un exemple clé de ces améliorations concerne les numéros de séquence. Les numéros de
séquence indiquent au runtime quelles sorties proviennent de quelles lignes de code
distinctes et ordonnées. Le runtime utilise ces informations pour générer des différences
d’arborescence efficaces en temps linéaire, ce qui est beaucoup plus rapide que ce qui
est normalement possible pour un algorithme différentiel d’arborescence générale.

Considérez le fichier de composants Razor suivant ( .razor ) :

razor

@if (someFlag)
{
<text>First</text>
}

Second

Le balisage et le contenu texte Razor précédents sont compilés en code C#, comme ce
qui suit :

C#

if (someFlag)
{
builder.AddContent(0, "First");
}

builder.AddContent(1, "Second");
Lorsque le code s’exécute pour la première fois et que someFlag est true , le générateur
reçoit la séquence dans le tableau suivant.

ノ Agrandir le tableau

Sequence Type Données

0 Nœud de texte Premier

1 Nœud de texte Seconde

Imaginez que someFlag devient false et que le balisage est rendu à nouveau. Cette fois,
le générateur reçoit la séquence dans le tableau suivant.

ノ Agrandir le tableau

Sequence Type Données

1 Nœud de texte Seconde

Lorsque le runtime détermine la différence, il voit que l’élément à la séquence 0 a été


supprimé, de sorte qu’il génère le script de modification trivial suivant avec une seule
étape :

Supprimez le premier nœud de texte.

Le problème de la génération de numéros de séquence


par programmation
Imaginez plutôt que vous avez écrit la logique de générateur d’arborescence de rendu
suivante :

C#

var seq = 0;

if (someFlag)
{
builder.AddContent(seq++, "First");
}

builder.AddContent(seq++, "Second");

La première sortie est reflétée dans le tableau suivant.


ノ Agrandir le tableau

Sequence Type Données

0 Nœud de texte Premier

1 Nœud de texte Seconde

Ce résultat étant identique au cas précédent, il n’existe aucun problème négatif.


someFlag est false sur le deuxième rendu, et la sortie est affichée dans le tableau

suivant.

ノ Agrandir le tableau

Sequence Type Données

0 Nœud de texte Seconde

Cette fois, l’algorithme différentiel constate que deux modifications se sont produites.
L’algorithme génère le script de modification suivant :

Remplacez la valeur du premier nœud de texte par Second .


Supprimez le deuxième nœud de texte.

La génération des numéros de séquence a perdu toutes les informations utiles sur
l’emplacement où les branches et les boucles if/else étaient présentes dans le code
d’origine. Cela entraîne une différence deux fois plus longue qu’auparavant.

Il s’agit d’un exemple trivial. Dans des cas plus réalistes avec des structures complexes et
profondément imbriquées, et en particulier avec des boucles, le coût des performances
est généralement plus élevé. Au lieu d’identifier immédiatement les blocs de boucle ou
les branches qui ont été insérés ou supprimés, l’algorithme différentiel doit itérer
profondément dans les arborescences de rendu. Cela entraîne généralement la création
de scripts de modification plus longs, car l’algorithme différentiel est mal renseigné sur
la façon dont les anciennes et nouvelles structures sont liées les unes aux autres.

Conseils et conclusions
Les performances de l’application souffrent si les numéros de séquence sont
générés dynamiquement.
Le framework ne peut pas créer automatiquement ses propres numéros de
séquence au moment de l’exécution, car les informations nécessaires n’existent
pas, sauf si elles sont capturées au moment de la compilation.
N’écrivez pas de longs blocs de logique RenderTreeBuilder implémentée
manuellement. Préférez des fichiers .razor et autorisez le compilateur à traiter les
numéros de séquence. Si vous ne parvenez pas à éviter une logique de
RenderTreeBuilder manuelle, fractionnez les longs blocs de code en petits
éléments encapsulés dans des appels OpenRegion/CloseRegion. Chaque région a
son propre espace de numéros de séquences distincts. Vous pouvez donc
redémarrer à partir de zéro (ou de tout autre nombre arbitraire) à l’intérieur de
chaque région.
Si les numéros de séquences sont codés en dur, l’algorithme différentiel exige
uniquement que les numéros de séquences augmentent en valeur. La valeur
initiale et les écarts ne sont pas pertinents. Une option légitime consiste à utiliser le
numéro de ligne de code comme numéro de séquence, ou à commencer à zéro et
à augmenter de 1 ou de centaines (ou tout intervalle préféré).
Blazor utilise des numéros de séquence, tandis que d’autres infrastructures
d’interface utilisateur à arborescence différentielle ne les utilisent pas. Le calcul de
la différence est beaucoup plus rapide lorsque des numéros de séquence sont
utilisés, et Blazor présente l’avantage d’avoir une étape de compilation qui traite
automatiquement les numéros de séquence pour les développeurs qui créent des
fichiers .razor .

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Tutoriel : Créer une application ASP.NET
Core avec Angular dans Visual Studio
Article • 13/01/2024

Dans cet article, vous allez apprendre à créer un projet ASP.NET Core en tant que back-
end d’API et un projet Angular en tant qu’interface utilisateur.

Visual Studio inclut les modèles d’applications monopages (SPA) ASP.NET Core qui
prennent en charge Angular et React. Les modèles fournissent un dossier d’application
client intégré dans vos projets ASP.NET Core qui contient les fichiers de base et les
dossiers de chaque infrastructure.

Vous pouvez utiliser la méthode décrite dans cet article pour créer des applications
monopages ASP.NET qui :

Placent l’application cliente dans un projet distinct, en dehors du projet ASP.NET


Core
Créent le projet client en fonction de l’interface CLI installée sur votre ordinateur

7 Notes

Cet article décrit le processus de création de projet en tirant parti du modèle mis à
jour dans Visual Studio 2022 version 17.8.

Prérequis
Veillez à installer les éléments suivants :

Visual Studio 2022 version 17.8 ou ultérieure avec la charge de travail


Développement web et ASP.NET installée. Accédez à la page de téléchargements
de Visual Studio pour l’installer gratuitement. Si vous devez installer la charge de
travail et que vous avez déjà installé Visual Studio, cliquez sur Outils>Obtenir les
outils et les fonctionnalités..., ce qui ouvre Visual Studio Installer. Choisissez la
charge de travail Développement web et ASP.NET, puis Modifier.
npm (https://www.npmjs.com/ ), inclus avec Node.js
Angular CLI (https://angular.io/cli ) Il peut s’agir de la version de votre choix

Créer l’application front-end


1. Dans la fenêtre Démarrage (choisissez Fichier> Fenêtre de démarrage à ouvrir),
sélectionnez Créer un nouveau projet.

2. Recherchez Angular dans la barre de recherche en haut, puis sélectionnez Angular


et ASP.NET Core (préversion).

3. Nommez le projet AngularWithASP, puis choisissez Créer.

L’Explorateur de solutions affiche les éléments suivants :


Par rapport au modèle Angular autonome, vous voyez de nouveaux fichiers et des
fichiers modifiés pour l’intégration à ASP.NET Core :

aspnetcore-https.js
proxy.conf.js
package.json (modifié)
angular.json (modifié)
app.components.ts
app.module.ts

Définissez les propriétés du projet


1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet
AngularWithASP.Server,puis choisissez Propriétés.
2. Dans la page Propriétés, ouvrez l’onglet Déboguer et sélectionnez l’option Ouvrir
l’interface utilisateur de profils de lancement de débogage. Décochez l’option
Lancer le navigateur pour le profil nommé après le projet ASP.NET Core (ou https,
le cas échéant).
Cette valeur empêche l’ouverture de la page web avec les données
météorologiques sources.

7 Notes

Dans Visual Studio, launch.json stocke les paramètres de démarrage associés


au bouton Démarrer dans la barre d’outils Débogage. launch.json doit se
trouver sous le dossier .vscode.

Démarrer le projet
Appuyez sur F5 ou sélectionnez le bouton Démarrer en haut de la fenêtre pour
démarrer l’application. Deux invites de commandes s’affichent :

Le projet API ASP.NET Core en cours d’exécution


L’infrastructure CLI Angular exécutant la commande ng start

7 Notes

Vérifiez la présence de messages dans la sortie de la console. Par exemple, un


message peut indiquer de mettre à jour Node.js.

L’application Angular s’affiche et est remplie via l’API. Si vous ne voyez pas l’application,
consultez Résolution des problèmes.
Publication du projet
À partir de Visual Studio 2022 version 17.3, vous pouvez publier la solution intégrée à
l’aide de l’outil de publication de Visual Studio.

7 Notes

Pour utiliser la publication, créez votre projet JavaScript à l’aide de Visual Studio
2022 version 17.3 ou ultérieure.

1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet


AngularWithASP.Server, puis sélectionnez Ajouter>Référence du projet.

Vérifiez que le projet angularwithasp.client est sélectionné.

2. Choisissez OK.

3. Cliquez de nouveau avec le bouton droit sur le projet ASP.NET Core et sélectionnez
Modifier le fichier projet.

Cela ouvre le fichier .csproj pour le projet.

4. Dans le fichier .csproj, vérifiez que la référence de projet inclut un élément


<ReferenceOutputAssembly> avec la valeur définie sur false .

Cette référence doit ressembler à ce qui suit.

XML

<ProjectReference
Include="..\angularwithasp.client\angularwithasp.client.esproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>

5. Cliquez avec le bouton droit sur le projet ASP.NET Core, puis choisissez Recharger
le projet si l’option est disponible.

6. Dans Program.cs, vérifiez que le code suivant est présent.

C#

app.UseDefaultFiles();
app.UseStaticFiles();

// Configure the HTTP request pipeline.


if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

7. Pour publier, cliquez avec le bouton droit sur le projet ASP.NET Core, choisissez
Publier, puis sélectionnez les options qui correspondent à votre scénario de
publication souhaité, comme Azure, publier dans un dossier, etc.

Le processus de publication prend plus de temps que pour un projet ASP.NET


Core, car la commande npm run build est appelée lors de la publication.
BuildCommand exécute npm run build par défaut.

Dépannage

Erreur proxy
L'erreur suivante peut apparaître :

Invite de commandes Windows

[HPM] Error occurred while trying to proxy request /weatherforecast from


localhost:4200 to https://localhost:5001 (ECONNREFUSED)
(https://nodejs.org/api/errors.html#errors_common_system_errors)

Si vous voyez ce problème, le front-end a probablement démarré avant le back-end.


Une fois que vous voyez l’invite de commandes back-end en cours d’exécution,
actualisez simplement l’application Angular dans le navigateur.

Vérifier le port
Si les données météorologiques ne se chargent pas correctement, vous devrez peut-être
également vérifier que vos ports sont corrects.

1. Accédez au fichier launchSettings.json dans votre projet ASP.NET Core (dans le


dossier Propriétés). Obtenez le numéro de port à partir de la propriété
applicationUrl .

S’il existe plusieurs propriétés applicationUrl , recherchez-en une à l’aide d’un


point de terminaison https . Elle doit ressembler à https://localhost:7049 .
2. Accédez ensuite au fichier proxy.conf.js de votre projet Angular (recherchez dans le
dossier src). Mettez à jour la propriété cible pour qu’elle corresponde à la propriété
applicationUrl dans launchSettings.json. Lorsque vous le mettez à jour, cette

valeur doit ressembler à ceci :

JavaScript

target: 'https://localhost:7049',

Étapes suivantes
Pour plus d’informations sur les applications SPA dans ASP.NET Core, consultez la
section Angular sous Développement d’applications monopage. L’article en lien fournit
un contexte supplémentaire pour les fichiers projet comme aspnetcore-https.js et
proxy.conf.js, bien que les détails de l’implémentation soient différents en raison de
différences de modèle. Par exemple, au lieu d’un dossier ClientApp, les fichiers Angular
sont contenus dans un projet distinct.

Pour les informations MSBuild spécifiques au projet client, consultez propriétés MSBuild
pour JSPS.
Tutoriel : Créer une application ASP.NET
Core avec React dans Visual Studio
Article • 08/12/2023

Dans cet article, vous allez apprendre à créer un projet ASP.NET Core en tant que back-
end d’API et un projet React en tant qu’interface utilisateur.

Actuellement, Visual Studio inclut les modèles d’applications monopages (SPA) ASP.NET
Core ,qui prennent en charge Angular et React. Les modèles fournissent un dossier
d’application client intégré dans vos projets ASP.NET Core qui contient les fichiers de
base et les dossiers de chaque infrastructure.

Vous pouvez utiliser la méthode décrite dans cet article pour créer des applications
monopages ASP.NET qui :

Placent l’application cliente dans un projet distinct, en dehors du projet ASP.NET


Core
Créent le projet client en fonction de l’interface CLI installée sur votre ordinateur

7 Notes

Cet article décrit le processus de création de projet en utilisant le modèle mis à jour
dans Visual Studio 2022 version 17.8, qui utilise l’interface CLI Vite.

Prérequis
Visual Studio 2022 version 17.8 ou ultérieure avec la charge de travail
Développement web et ASP.NET installée. Accédez à la page de téléchargements
de Visual Studio pour l’installer gratuitement. Si vous devez installer la charge de
travail et que vous avez déjà installé Visual Studio, cliquez sur Outils>Obtenir les
outils et les fonctionnalités..., ce qui ouvre Visual Studio Installer. Choisissez la
charge de travail Développement web et ASP.NET, puis Modifier.
npm (https://www.npmjs.com/ ), inclus avec Node.js
npx (https://www.npmjs.com/package/npx )

Créer l’application front-end


1. Dans la fenêtre Démarrer, sélectionnez Créer un projet.
2. Recherchez React dans la barre de recherche en haut, puis sélectionnez React et
ASP.NET Core (préversion). C’est un modèle JavaScript.

3. Nommez le projet ReactWithASP, puis choisissez Créer.

L’Explorateur de solutions affiche les informations sur le projet suivantes :


Par rapport au modèle React autonome, vous voyez de nouveaux fichiers et des
fichiers modifiés pour l’intégration à ASP.NET Core :

vite.config.js
App.js (modifié)
App.test.js (modifié)

4. Sélectionnez un navigateur installé dans la barre d’outils Déboguer, par exemple


Chrome ou Microsoft Edge.

Si le navigateur souhaité n’est pas encore installé, installez d’abord le navigateur,


puis sélectionnez-le.

Définissez les propriétés du projet


1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet
ReactWithASP.Server et choisissez Propriétés.
2. Dans la page Propriétés, ouvrez l’onglet Déboguer et sélectionnez l’option Ouvrir
l’interface utilisateur de profils de lancement de débogage. Décochez l’option
Lancer le navigateur pour le profil nommé après le projet ASP.NET Core (ou https,
le cas échéant).
Cette valeur empêche l’ouverture de la page web avec les données
météorologiques sources.

7 Notes

Dans Visual Studio, launch.json stocke les paramètres de démarrage associés


au bouton Démarrer dans la barre d’outils Débogage. Actuellement,
launch.json doit se trouver sous le dossier .vscode.

Démarrer le projet
Appuyez sur F5 ou sélectionnez le bouton Démarrer en haut de la fenêtre pour
démarrer l’application. Deux invites de commandes s’affichent :

Projet API ASP.NET Core en cours d’exécution

Interface CLI Vite affichant un message de type VITE v4.4.9 ready in 780 ms

7 Notes

Vérifiez la présence de messages dans la sortie de la console. Par exemple, il


est possible qu’un message indique de mettre à jour Node.js.

L’application React s’affiche qui est renseignée via l’API. Si vous ne voyez pas
l’application, consultez Résolution des problèmes.

Publication du projet
1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet
ReactWithASP.Server, puis sélectionnez Ajouter>Référence du projet.

Vérifiez que le projet reactwithasp.client est sélectionné.

2. Choisissez OK.

3. Cliquez de nouveau avec le bouton droit sur le projet ASP.NET Core et sélectionnez
Modifier le fichier projet.

Cela ouvre le fichier .csproj pour le projet.


4. Dans le fichier .csproj, vérifiez que la référence de projet inclut un élément
<ReferenceOutputAssembly> avec la valeur définie sur false .

Cette référence doit ressembler à ce qui suit.

XML

<ProjectReference
Include="..\reactwithasp.client\reactwithasp.client.esproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>

5. Cliquez avec le bouton droit sur le projet ASP.NET Core, puis choisissez Recharger
le projet si l’option est disponible.

6. Dans Program.cs, vérifiez que le code suivant est présent.

C#

app.UseDefaultFiles();
app.UseStaticFiles();

// Configure the HTTP request pipeline.


if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

7. Pour publier, cliquez avec le bouton droit sur le projet ASP.NET Core, choisissez
Publier, puis sélectionnez les options qui correspondent à votre scénario de
publication souhaité, comme Azure, publier dans un dossier, etc.

Le processus de publication prend plus de temps que pour un projet ASP.NET


Core, car la commande npm run build est appelée lors de la publication.
BuildCommand exécute npm run build par défaut.

Dépannage

Erreur proxy
L'erreur suivante peut apparaître :

Invite de commandes Windows


[HPM] Error occurred while trying to proxy request /weatherforecast from
localhost:4200 to https://localhost:7183 (ECONNREFUSED)
(https://nodejs.org/api/errors.html#errors_common_system_errors)

Si vous voyez ce problème, le front-end a probablement démarré avant le back-end.


Une fois que vous voyez l’invite de commandes back-end en cours d’exécution,
actualisez simplement l’application React dans le navigateur.

Vérifier les ports


Si les données météorologiques ne se chargent pas correctement, il est possible que
vous deviez également vérifier que vos ports sont corrects.

1. Vérifiez que les numéros de port correspondent. Accédez au fichier


launchSettings.json dans le projet ASP.NET Core ReactWithASP.Server (dans le
dossier Propriétés). Obtenez le numéro de port à partir de la propriété
applicationUrl .

S’il existe plusieurs propriétés applicationUrl , recherchez-en une à l’aide d’un


point de terminaison https . Il ressemble à https://localhost:7183 .

2. Ouvrez le fichier vite.config.js pour le projet React. Mettez à jour la propriété


target pour qu’elle corresponde à la propriété applicationUrl dans

launchSettings.json. La valeur mise à jour ressemble à ce qui suit :

JavaScript

target: 'https://localhost:7183/',

Erreur de confidentialité
Vous pouvez voir l’erreur de certificat suivante :

Your connection isn't private

Essayez de supprimer les certificats React dans %appdata%\local\asp.net\https ou


%appdata%\roaming\asp.net\https, puis réessayez.

Étapes suivantes
Pour plus d’informations sur les applications SPA dans ASP.NET Core, consultez la
section React sous Développement d’applications monopage. L’article en lien fournit un
contexte supplémentaire pour les fichiers projet comme aspnetcore-https.js, bien que les
détails de l’implémentation soient différents en fonction des différences entre les
modèles. Par exemple, au lieu d’un dossier ClientApp, les fichiers React sont contenus
dans un projet distinct.

Pour les informations MSBuild spécifiques au projet client, consultez propriétés MSBuild
pour JSPS.
Didacticiel : Créer une application
ASP.NET Core avec Vue dans
Visual Studio
Article • 24/11/2023

Dans cet article, vous allez apprendre à créer un projet ASP.NET Core en tant que back-
end d’API et un projet Vue en tant qu’interface utilisateur.

Visual Studio inclut les modèles d’applications monopage (SPA) ASP.NET Core qui
prennent en charge Angular, React et Vue. Les modèles fournissent un dossier
d’application client intégré dans vos projets ASP.NET Core qui contient les fichiers de
base et les dossiers de chaque infrastructure.

Vous pouvez utiliser la méthode décrite dans cet article pour créer des applications
monopages ASP.NET qui :

Placent l’application cliente dans un projet distinct, en dehors du projet ASP.NET


Core
Créent le projet client en fonction de l’interface CLI installée sur votre ordinateur

7 Notes

Cet article décrit le processus de création de projet en utilisant le modèle mis à jour
dans Visual Studio 2022 version 17.8, qui utilise l’interface CLI Vite.

Prérequis
Veillez à installer les éléments suivants :

Visual Studio 2022 version 17.8 ou ultérieure avec la charge de travail


Développement web et ASP.NET installée. Accédez à la page de téléchargements
de Visual Studio pour l’installer gratuitement. Si vous devez installer la charge de
travail et que vous avez déjà installé Visual Studio, cliquez sur Outils>Obtenir les
outils et les fonctionnalités..., ce qui ouvre Visual Studio Installer. Choisissez la
charge de travail Développement web et ASP.NET, puis Modifier.
npm (https://www.npmjs.com/ ), inclus avec Node.js.

Créer l’application front-end


1. Dans la fenêtre Démarrage (choisissez Fichier> Fenêtre de démarrage à ouvrir),
sélectionnez Créer un nouveau projet.

2. Recherchez Vue dans la barre de recherche en haut, puis sélectionnez Vue et


ASP.NET Core (préversion) avec JavaScript ou TypeScript comme langage
sélectionné.

3. Nommez le projet VueWithASP, puis choisissez Créer.

L’Explorateur de solutions affiche les informations sur le projet suivantes :


Par rapport au modèle Vue autonome, vous voyez de nouveaux fichiers et des
fichiers modifiés pour l’intégration à ASP.NET Core :

vite.config.json (modifié)
HelloWorld.vue (modifié)
package.json (modifié)

Définissez les propriétés du projet


1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le
VueWithASP.Server, puis choisissez Propriétés.
2. Dans la page Propriétés, ouvrez l’onglet Déboguer et sélectionnez l’option Ouvrir
l’interface utilisateur de profils de lancement de débogage. Décochez l’option
Lancer le navigateur pour le profil nommé après le projet ASP.NET Core (ou https,
le cas échéant).
Cette valeur empêche l’ouverture de la page web avec les données
météorologiques sources.

7 Notes

Dans Visual Studio, launch.json stocke les paramètres de démarrage associés


au bouton Démarrer dans la barre d’outils Débogage. Actuellement,
launch.json doit se trouver sous le dossier .vscode.

Démarrer le projet
Appuyez sur F5 ou sélectionnez le bouton Démarrer en haut de la fenêtre pour
démarrer l’application. Deux invites de commandes s’affichent :

Projet API ASP.NET Core en cours d’exécution


Interface CLI Vite affichant un message de type VITE v4.4.9 ready in 780 ms

7 Notes

Vérifiez la présence de messages dans la sortie de la console. Par exemple, il est


possible qu’un message indique de mettre à jour Node.js.

L’application Vue s’affiche qui est renseignée via l’API. Si vous ne voyez pas l’application,
consultez Résolution des problèmes.

Publication du projet
À partir de Visual Studio 2022 version 17.3, vous pouvez publier la solution intégrée à
l’aide de l’outil de publication de Visual Studio.

7 Notes

Pour utiliser la publication, créez votre projet JavaScript à l’aide de Visual Studio
2022 version 17.3 ou ultérieure.

1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet


VueWithASP.Server, puis sélectionnez Ajouter>Référence du projet.

Assurez-vous que le projet vuewithasp.client est sélectionné.


2. Choisissez OK.

3. Cliquez de nouveau avec le bouton droit sur le projet ASP.NET Core et sélectionnez
Modifier le fichier projet.

Cela ouvre le fichier .csproj pour le projet.

4. Dans le fichier .csproj, vérifiez que la référence de projet inclut un élément


<ReferenceOutputAssembly> avec la valeur définie sur false .

Cette référence doit ressembler à ce qui suit.

XML

<ProjectReference
Include="..\vuewithasp.client\vuewithasp.client.esproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>

5. Cliquez avec le bouton droit sur le projet ASP.NET Core, puis choisissez Recharger
le projet si l’option est disponible.

6. Dans Program.cs, vérifiez que le code suivant est présent.

C#

app.UseDefaultFiles();
app.UseStaticFiles();

// Configure the HTTP request pipeline.


if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

7. Pour publier, cliquez avec le bouton droit sur le projet ASP.NET Core, choisissez
Publier, puis sélectionnez les options qui correspondent à votre scénario de
publication souhaité, comme Azure, publier dans un dossier, etc.

Le processus de publication prend plus de temps que pour un projet ASP.NET


Core, car la commande npm run build est appelée lors de la publication.
BuildCommand exécute npm run build par défaut.

Dépannage
Erreur proxy
L'erreur suivante peut apparaître :

[HPM] Error occurred while trying to proxy request /weatherforecast from


localhost:4200 to https://localhost:5173 (ECONNREFUSED)
(https://nodejs.org/api/errors.html#errors_common_system_errors)

Si vous voyez ce problème, le front-end a probablement démarré avant le back-end.


Une fois que vous voyez l’invite de commandes back-end en cours d’exécution,
actualisez simplement l’application Vue dans le navigateur.

Sinon, si le port est en cours d’utilisation, essayez d’incrémenter le numéro de port de 1


dans launchSettings.json et vite.config.js.

Erreur de confidentialité
Vous pouvez voir l’erreur de certificat suivante :

Your connection isn't private

Essayez de supprimer les certificats Vue dans %appdata%\local\asp.net\https ou


%appdata%\roaming\asp.net\https, puis réessayez.

Vérifier les ports


Si les données météorologiques ne se chargent pas correctement, il est possible que
vous deviez également vérifier que vos ports sont corrects.

1. Vérifiez que les numéros de port correspondent. Accédez au fichier


launchSettings.json dans votre projet ASP.NET Core (dans le dossier Propriétés).
Obtenez le numéro de port à partir de la propriété applicationUrl .

S’il existe plusieurs propriétés applicationUrl , recherchez-en une à l’aide d’un


point de terminaison https . Elle doit ressembler à https://localhost:7142 .

2. Accédez ensuite au fichier vite.config.js de votre projet Vue. Mettez à jour la


propriété target pour qu’elle corresponde à la propriété applicationUrl dans
launchSettings.json. Lorsque vous le mettez à jour, cette valeur doit ressembler à
ceci :

JavaScript

target: 'https://localhost:7142/',

Version obsolète de Vue


Si vous voyez le message de console Fichier
« C:\Users\Me\source\repos\vueprojectname\package.json » introuvable quand vous
créez le projet, vous devez peut-être mettre à jour votre version de l’interface CLI Vite.
Après avoir mis à jour l’interface CLI Vite, vous devez peut-être également supprimer le
fichier .vuerc dans C:\Users\[yourprofilename].

Docker
Si vous activez la prise en charge de Docker pendant la création du projet d’API web, le
back-end peut démarrer en utilisant le profil Docker et ne pas écouter sur le port 5173
configuré. Pour résoudre ce problème :

Modifiez le profil Docker dans launchSettings.json en ajoutant les propriétés suivantes :

JSON

"httpPort": 5175,
"sslPort": 5173

Vous pouvez également réinitialiser à l’aide de la méthode suivante :

1. Dans les propriétés de la solution, définissez votre application back-end comme


projet de démarrage.
2. Dans le menu Déboguer, basculez le profil à l’aide du menu déroulant du bouton
Démarrer sur le profil de votre application back-end.
3. Ensuite, dans les propriétés de la solution, réinitialisez plusieurs projets de
démarrage.

Étapes suivantes
Pour plus d’informations sur les applications SPA dans ASP.NET Core, consultez
Développement d’applications monopage. L’article en lien fournit un contexte
supplémentaire pour les fichiers projet comme aspnetcore-https.js, bien que les détails
de l’implémentation diffèrent en raison de différences entre les modèles de projet et
l’infrastructure Vue.js par rapport aux autres infrastructures. Par exemple, au lieu d’un
dossier ClientApp, les fichiers Vue sont contenus dans un projet distinct.

Pour les informations MSBuild spécifiques au projet client, consultez propriétés MSBuild
pour JSPS.
JavaScript et TypeScript dans Visual
Studio
Article • 27/10/2023

Visual Studio 2022 assure une prise en charge complète du développement JavaScript, à
la fois directement et avec le langage de programmation TypeScript , qui a été
développé pour offrir une expérience de développement JavaScript plus productive et
plus agréable, en particulier pour des projets à grande échelle. Il est possible d’écrire du
code JavaScript ou TypeScript dans Visual Studio pour de nombreux types d’applications
et services.

Service de langage JavaScript


L’expérience JavaScript dans Visual Studio 2022 s’appuie sur le même moteur que la
prise en charge de TypeScript. Ce moteur vous assure une meilleure prise en charge de
fonctionnalités, une plus grande richesse et une intégration immédiatement prête à
l’emploi.

La possibilité de revenir à l’ancien service de langage JavaScript n’est plus disponible.


Les utilisateurs disposent du nouveau service de langage JavaScript intégré. Il s’appuie
uniquement sur le service de langage TypeScript, qui repose sur l’analyse statique, ce
qui nous permet de proposer de meilleurs outils : votre code JavaScript bénéficie ainsi
de fonctionnalités IntelliSense plus riches, basées sur les définitions de type. Le nouveau
service est léger et consomme moins de mémoire que l’ancien, ce qui assure de
meilleurs résultats lorsque le code prend de l’ampleur. Nous avons également amélioré
les performances du service de langage pour gérer les projets volumineux.

Prise en charge de TypeScript


Par défaut, Visual Studio 2022 prend en charge le langage pour les fichiers JavaScript et
TypeScript pour alimenter IntelliSense sans configuration de projet spécifique.

Pour la compilation de TypeScript, Visual Studio vous donne la possibilité de choisir la


version de TypeScript à utiliser par projet.

Dans les scénarios de compilation MSBuild comme ASP.NET Core, le package NuGet
TypeScript est la méthode recommandée pour ajouter la prise en charge de la
compilation TypeScript à votre projet. Visual Studio vous donne la possibilité d’ajouter
ce package la première fois que vous ajoutez un fichier TypeScript à votre projet. Ce
package est également disponible à tout moment via le gestionnaire de package NuGet.
Lorsque le package NuGet est utilisé, la version correspondante du service de langage
est utilisée pour la prise en charge linguistique dans votre projet. Remarque : la version
minimale prise en charge de ce package est 3.6.

Les projets configurés pour npm, par exemple les projets Node.js, peuvent spécifier leur
propre version du service de langage TypeScript en ajoutant le package npm
TypeScript . Vous pouvez spécifier la version à l’aide du gestionnaire npm dans les
projets pris en charge. Remarque : la version minimale prise en charge de ce package
est 2.1.

TypeScript SDK a été déconseillé dans Visual Studio 2022. Les projets existants qui
s’appuient sur le Kit de développement logiciel (SDK) doivent être mis à niveau pour
utiliser le package NuGet. Pour les projets qui ne peuvent pas être mis à niveau
immédiatement, le SDK est toujours disponible sur Visual Studio Marketplace et en
tant que composant facultatif dans Visual Studio Installer.

 Conseil

Pour les projets développés dans Visual Studio 2022, nous vous encourageons à
utiliser les packages TypeScript NuGet ou TypeScript npm afin de bénéficier d’une
meilleure portabilité entre les plateformes et les environnements. Pour plus
d’informations, consultez Compiler du code TypeScript à l’aide de NuGet et
Compiler du code TypeScript à l’aide de tsc.

Modèles de projet
À compter de Visual Studio 2022, il existe un nouveau type de projet
JavaScript/TypeScript (.esproj), appelé JavaScript Project System (JSPS), qui vous permet
de créer des projets Angular, React et Vue autonomes dans Visual Studio. Ces projets
front-end sont créés à l’aide des outils CLI de l’infrastructure que vous avez installés sur
votre ordinateur local. La version du modèle vous appartient donc. Pour migrer des
projets Node.js existants vers le nouveau système de projet, consultez Migrer des
projets Node.js. Pour plus d’informations sur MSBuild pour le nouveau type de projet,
consultez Propriétés MSBuild pour JSPS

Dans ces nouveaux projets, vous pouvez exécuter des tests unitaires JavaScript et
TypeScript, ajouter facilement des projets d’API ASP.NET Core et vous y connecter, et
télécharger vos modules npm à l’aide du gestionnaire npm. Consultez quelques-uns des
guides de démarrage rapide et tutoriels pour commencer. Pour plus d’informations,
consultez Tutoriels Visual Studio | JavaScript et TypeScript.
7 Notes

Un modèle simplifié et mis à jour est disponible à partir de Visual Studio 2022
version 17.5. Par rapport aux modèles SPA ASP.NET disponibles dans Visual Studio,
les modèles SPA .esproj pour ASP.NET Core offrent une meilleure gestion des
dépendances npm et une meilleure prise en charge de la génération et de la
publication.
Vue d’ensemble des applications
monopages (SPA) dans ASP.NET Core
Article • 30/11/2023

Visual Studio fournit des modèles de projet pour la création d’applications monopages
(SPA) basées sur des infrastructures JavaScript telles que Angular , React et Vue
qui ont un back-end ASP.NET Core. Ces modèles :

Créent une solution Visual Studio avec un projet front-end et un projet back-end.
Utilisent le type de projet Visual Studio pour JavaScript et TypeScript (.esproj) pour
le front-end.
Utilisent un projet ASP.NET Core pour le back-end.

Les projets créés à l’aide des modèles Visual Studio peuvent être exécutés à partir de la
ligne de commande sur Windows, Linux et macOS. Pour exécuter l’application, utilisez
dotnet run --launch-profile https pour exécuter le projet de serveur. L’exécution du

projet de serveur démarre automatiquement le serveur de développement JavaScript


frontal. Le profil de lancement https est actuellement requis.

Tutoriels Visual Studio


Pour commencer, suivez l’un des tutoriels de la documentation Visual Studio :

Créer une application ASP.NET Core avec Angular


Créer une application ASP.NET Core avec React
Créer une application ASP.NET Core avec Vue

Pour plus d’informations, consultez JavaScript et TypeScript dans Visual Studio

Modèles SPA ASP.NET Core


Visual Studio inclut des modèles de création d’applications ASP.NET Core avec un front-
end JavaScript ou TypeScript. Ces modèles sont disponibles dans Visual Studio 2022
version 17.8 ou ultérieure sur laquelle est installée la charge de travail ASP.NET et
développement web.

Les modèles Visual Studio de création d’applications ASP.NET Core avec un front-end
JavaScript ou TypeScript offrent les avantages suivants :

Nettoyer la séparation de projet du front-end et du back-end.


Rester à jour des dernières versions de l’infrastructure front-end.
Intégrer le dernier outillage de commande en ligne de l’infrastructure front-end,
tels que Vite .
Modèles pour JavaScript & TypeScript (uniquement TypeScript pour Angular).
Expérience de modification de code JavaScript et TypeScript enrichie.
Intégrer des outils de génération JavaScript au build .NET.
IU de gestion des dépendances npm.
Compatible avec le débogage et la configuration de lancement de Visual Studio
Code.
Exécuter des tests unitaires front-end dans l’Explorateur de tests à l’aide
d’infrastructures de tests JavaScript.

Modèles SPA ASP.NET Core hérités


Les versions antérieures du kit de développement logiciel (SDK) .NET incluaient ce que
sont désormais des modèles hérités de création d’applications SPA avec ASP.NET Core.
Pour obtenir une documentation sur ces modèles plus anciens, consultez la version
ASP.NET Core 7.0 de l’Aperçu SPA et les articles Angular et React.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Utiliser le modèle de projet Angular
avec ASP.NET Core
Article • 30/11/2023

Visual Studio fournit des modèles de projet pour la création d’applications monopages
(SPA) basées sur des infrastructures JavaScript telles que Angular , React et Vue
qui ont un back-end ASP.NET Core. Ces modèles :

Créent une solution Visual Studio avec un projet front-end et un projet back-end.
Utilisent le type de projet Visual Studio pour JavaScript et TypeScript (.esproj) pour
le front-end.
Utilisent un projet ASP.NET Core pour le back-end.

Les projets créés à l’aide des modèles Visual Studio peuvent être exécutés à partir de la
ligne de commande sur Windows, Linux et macOS. Pour exécuter l’application, utilisez
dotnet run --launch-profile https pour exécuter le projet de serveur. L’exécution du

projet de serveur démarre automatiquement le serveur de développement JavaScript


frontal. Le profil de lancement https est actuellement requis.

Tutoriel Visual Studio


Pour commencer à utiliser un projet Angular, suivez le tutoriel Créer une application
ASP.NET Core avec Angular dans la documentation Visual Studio.

Pour plus d’informations, consultez JavaScript et TypeScript dans Visual Studio

Modèles SPA ASP.NET Core


Visual Studio inclut des modèles de création d’applications ASP.NET Core avec un front-
end JavaScript ou TypeScript. Ces modèles sont disponibles dans Visual Studio 2022
version 17.8 ou ultérieure sur laquelle est installée la charge de travail ASP.NET et
développement web.

Les modèles Visual Studio de création d’applications ASP.NET Core avec un front-end
JavaScript ou TypeScript offrent les avantages suivants :

Nettoyer la séparation de projet du front-end et du back-end.


Rester à jour des dernières versions de l’infrastructure front-end.
Intégrer le dernier outillage de commande en ligne de l’infrastructure front-end,
tels que Vite .
Modèles pour JavaScript & TypeScript (uniquement TypeScript pour Angular).
Expérience de modification de code JavaScript et TypeScript enrichie.
Intégrer des outils de génération JavaScript au build .NET.
IU de gestion des dépendances npm.
Compatible avec le débogage et la configuration de lancement de Visual Studio
Code.
Exécuter des tests unitaires front-end dans l’Explorateur de tests à l’aide
d’infrastructures de tests JavaScript.

Modèles SPA ASP.NET Core hérités


Les versions antérieures du kit de développement logiciel (SDK) .NET incluaient ce que
sont désormais des modèles hérités de création d’applications SPA avec ASP.NET Core.
Pour obtenir une documentation sur ces modèles plus anciens, consultez la version
ASP.NET Core 7.0 de l’Aperçu SPA et les articles Angular et React.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Utiliser React avec ASP.NET Core
Article • 30/11/2023

Visual Studio fournit des modèles de projet pour la création d’applications monopages
(SPA) basées sur des infrastructures JavaScript telles que Angular , React et Vue
qui ont un back-end ASP.NET Core. Ces modèles :

Créent une solution Visual Studio avec un projet front-end et un projet back-end.
Utilisent le type de projet Visual Studio pour JavaScript et TypeScript (.esproj) pour
le front-end.
Utilisent un projet ASP.NET Core pour le back-end.

Les projets créés à l’aide des modèles Visual Studio peuvent être exécutés à partir de la
ligne de commande sur Windows, Linux et macOS. Pour exécuter l’application, utilisez
dotnet run --launch-profile https pour exécuter le projet de serveur. L’exécution du

projet de serveur démarre automatiquement le serveur de développement JavaScript


frontal. Le profil de lancement https est actuellement requis.

Tutoriel Visual Studio


Pour commencer, suivez le tutoriel Créer une application ASP.NET Core avec React dans
la documentation Visual Studio.

Pour plus d’informations, consultez JavaScript et TypeScript dans Visual Studio

Modèles SPA ASP.NET Core


Visual Studio inclut des modèles de création d’applications ASP.NET Core avec un front-
end JavaScript ou TypeScript. Ces modèles sont disponibles dans Visual Studio 2022
version 17.8 ou ultérieure sur laquelle est installée la charge de travail ASP.NET et
développement web.

Les modèles Visual Studio de création d’applications ASP.NET Core avec un front-end
JavaScript ou TypeScript offrent les avantages suivants :

Nettoyer la séparation de projet du front-end et du back-end.


Rester à jour des dernières versions de l’infrastructure front-end.
Intégrer le dernier outillage de commande en ligne de l’infrastructure front-end,
tels que Vite .
Modèles pour JavaScript & TypeScript (uniquement TypeScript pour Angular).
Expérience de modification de code JavaScript et TypeScript enrichie.
Intégrer des outils de génération JavaScript au build .NET.
IU de gestion des dépendances npm.
Compatible avec le débogage et la configuration de lancement de Visual Studio
Code.
Exécuter des tests unitaires front-end dans l’Explorateur de tests à l’aide
d’infrastructures de tests JavaScript.

Modèles SPA ASP.NET Core hérités


Les versions antérieures du kit de développement logiciel (SDK) .NET incluaient ce que
sont désormais des modèles hérités de création d’applications SPA avec ASP.NET Core.
Pour obtenir une documentation sur ces modèles plus anciens, consultez la version
ASP.NET Core 7.0 de l’Aperçu SPA et les articles Angular et React.

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Acquisition de bibliothèque côté client
dans ASP.NET Core avec LibMan
Article • 30/11/2023

Par Scott Addie

Le Gestionnaire de bibliothèque (LibMan) est un outil allégé d’acquisition de


bibliothèque côté client. LibMan télécharge des bibliothèques et infrastructures
populaires à partir du système de fichiers ou d’un réseau de distribution de contenu
(CDN) . Parmi les réseaux de distribution de contenu pris en charge, on trouve entre
autres CDNJS , jsDelivr et unpkg . Les fichiers de bibliothèque sélectionnés sont
récupérés et placés à l’emplacement approprié dans le projet ASP.NET Core.

Cas d’usage de LibMan


LibMan offre les avantages suivants :

Seuls les fichiers de bibliothèque dont vous avez besoin sont téléchargés.
Des outils supplémentaires, tels que Node.js , npm et WebPack , ne sont pas
nécessaires pour acquérir un sous-ensemble de fichiers dans une bibliothèque.
Les fichiers peuvent être placés à un emplacement spécifique sans avoir recours à
des tâches de génération ou à la copie manuelle de fichiers.

LibMan n’est pas un système de gestion de packages. Si vous utilisez déjà un


gestionnaire de package, tel que npm ou yarn , continuez ainsi. LibMan n’a pas été
développé pour remplacer ces outils.

Ressources supplémentaires
Utiliser LibMan avec ASP.NET Core dans Visual Studio
Utiliser l’interface CLI LibMan avec ASP.NET Core
Dépôt GitHub LibMan

6 Collaborer avec nous sur ASP.NET Core feedback


GitHub ASP.NET Core is an open source
La source de ce contenu se project. Select a link to provide
trouve sur GitHub, où vous feedback:
pouvez également créer et
examiner les problèmes et les  Ouvrir un problème de
demandes de tirage. Pour plus documentation
d’informations, consultez notre
guide du contributeur.  Indiquer des commentaires sur
le produit
Utiliser l’interface CLI LibMan avec
ASP.NET Core
Article • 15/01/2024

Le Gestionnaire de bibliothèque (LibMan) est un outil allégé d’acquisition de


bibliothèque côté client. LibMan télécharge des bibliothèques et infrastructures
populaires à partir du système de fichiers ou d’un réseau de distribution de contenu
(CDN) . Parmi les réseaux de distribution de contenu pris en charge, on trouve entre
autres CDNJS , jsDelivr et unpkg . Les fichiers de bibliothèque sélectionnés sont
récupérés et placés à l’emplacement approprié dans le projet ASP.NET Core.

Prérequis
Dernier Kit de développement logiciel (SDK) .NET

Installation
La commande suivante permet d’installer LibMan :

CLI .NET

dotnet tool install -g Microsoft.Web.LibraryManager.Cli

7 Notes

Par défaut, l’architecture des fichiers binaires .NET à installer représente


l’architecture du système d’exploitation en cours d’exécution. Pour spécifier une
architecture de système d’exploitation différente, consultez dotnet tool install, --
arch option. Pour plus d'informations, consultez le problème GitHub
dotnet/AspNetCore.Docs n° 29262 .

Un outil global .NET Core est installé à partir du package NuGet


Microsoft.Web.LibraryManager.Cli .

Utilisation
Console
libman

Pour afficher la version LibMan installée :

Console

libman --version

Pour afficher les commandes de l’interface CLI disponibles :

Console

libman --help

La commande précédente affiche une sortie similaire à ce qui suit :

Console

1.0.163+g45474d37ed

Usage: libman [options] [command]

Options:
--help|-h Show help information
--version Show version information

Commands:
cache List or clean libman cache contents
clean Deletes all library files defined in libman.json from the
project
init Create a new libman.json
install Add a library definition to the libman.json file, and download
the
library to the specified location
restore Downloads all files from provider and saves them to specified
destination
uninstall Deletes all files for the specified library from their
specified
destination, then removes the specified library definition from
libman.json
update Updates the specified library

Use "libman [command] --help" for more information about a command.

Les sections suivantes décrivent les commandes CLI disponibles.


Initialiser LibMan dans le projet
La libman init commande crée un libman.json fichier s’il n’en existe pas. Le fichier est
créé avec le contenu du modèle d’élément par défaut.

Synopsis
Console

libman init [-d|--default-destination] [-p|--default-provider] [--verbosity]


libman init [-h|--help]

Options
Les options suivantes sont disponibles pour la commande libman init :

-d|--default-destination <PATH>

Un chemin d’accès relatif au dossier actuel. Les fichiers de bibliothèque sont


installés à cet emplacement si aucune destination propriété n’est définie pour une
bibliothèque dans libman.json . La valeur <PATH> est écrite dans la propriété
defaultDestination de libman.json .

-p|--default-provider <PROVIDER>

Le fournisseur à utiliser si aucun fournisseur n'est défini pour une bibliothèque


donnée. La valeur <PROVIDER> est écrite dans la propriété defaultProvider de
libman.json . Remplacez <PROVIDER> par l’une des valeurs suivantes :

cdnjs

filesystem
jsdelivr

unpkg

-h|--help

Afficher les informations d’aide.

--verbosity <LEVEL>

Définissez la verbosité de la sortie. Remplacez <LEVEL> par l’une des valeurs


suivantes :
quiet
normal
detailed

Exemples
Pour créer un fichier libman.json dans un projet ASP.NET Core :

Accédez à la racine du projet.

Exécutez la commande suivante :

Console

libman init

Tapez le nom du fournisseur par défaut ou appuyez sur Enter pour utiliser le
fournisseur CDNJS par défaut. Les valeurs valides sont les suivantes :
cdnjs
filesystem

jsdelivr
unpkg

Un fichier libman.json est ajouté à la racine du projet avec le contenu suivant :

JSON

{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": []
}
Ajouter des fichiers de bibliothèque
La commande libman install télécharge et installe les fichiers de bibliothèque dans le
projet. Un fichier libman.json est ajouté s’il n’en existe pas. Le fichier libman.json est
modifié pour stocker les détails de configuration des fichiers de bibliothèque.

Synopsis
Console

libman install <LIBRARY> [-d|--destination] [--files] [-p|--provider] [--


verbosity]
libman install [-h|--help]

Arguments
LIBRARY

Nom de la bibliothèque à installer. Ce nom peut inclure la notation de numéro de


version (par exemple, @1.2.0 ).

Options
Les options suivantes sont disponibles pour la commande libman install :

-d|--destination <PATH>

Emplacement d’installation de la bibliothèque. S’il n’est pas spécifié, l’emplacement


par défaut est utilisé. Si aucune propriété defaultDestination n’est spécifiée dans
libman.json , cette option est obligatoire.

Remarque : il existe des limitations au chemin d’accès de destination. Par exemple,


lorsque la source du package présente une structure de projet complète et pas
seulement le dossier de distribution, vous ne pouvez pas spécifier le déplacement
d’un dossier. Pour plus d’informations, consultez les articles sur le problème
n°407 et le problème n°702

--files <FILE>

Spécifiez le nom du fichier à installer à partir de la bibliothèque. S’il n’est pas


spécifié, tous les fichiers de la bibliothèque sont installés. Fournissez une option --
files par fichier à installer. Les chemins d’accès relatifs sont également pris en

charge. Par exemple : --files dist/browser/signalr.js .

-p|--provider <PROVIDER>

Le nom du fournisseur à utiliser pour l’acquisition de la bibliothèque. Remplacez


<PROVIDER> par l’une des valeurs suivantes :

cdnjs
filesystem

jsdelivr
unpkg

Si elle n’est pas spécifiée, la propriété defaultProvider dans libman.json est


utilisée. Si aucune propriété defaultProvider n’est spécifiée dans libman.json ,
cette option est obligatoire.

-h|--help

Afficher les informations d’aide.

--verbosity <LEVEL>

Définissez la verbosité de la sortie. Remplacez <LEVEL> par l’une des valeurs


suivantes :
quiet
normal

detailed

Exemples
Examinons le fichier libman.json suivant :

JSON

{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": []
}

Pour installer le fichier jQuery version 3.2.1 jquery.min.js dans le dossier


wwwroot/scripts/jquery à l’aide du fournisseur CDNJS :
Console

libman install jquery@3.2.1 --provider cdnjs --destination


wwwroot/scripts/jquery --files jquery.min.js

Le fichier libman.json ressemble à ce qui suit :

JSON

{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "jquery@3.2.1",
"destination": "wwwroot/scripts/jquery",
"files": [
"jquery.min.js"
]
}
]
}

Pour installer les fichiers calendar.js et calendar.css à partir de


C:\temp\contosoCalendar\ à l’aide du fournisseur de système de fichiers :

Console

libman install C:\temp\contosoCalendar\ --provider filesystem --files


calendar.js --files calendar.css

L’invite suivante s’affiche pour deux raisons :

Le fichier libman.json ne contient pas de propriété defaultDestination .


La commande libman install ne contient pas l’option -d|--destination .
Après l’acceptation de la destination par défaut, le fichier libman.json ressemble à ce
qui suit :

JSON

{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "jquery@3.2.1",
"destination": "wwwroot/scripts/jquery",
"files": [
"jquery.min.js"
]
},
{
"library": "C:\\temp\\contosoCalendar\\",
"provider": "filesystem",
"destination": "wwwroot/lib/contosoCalendar",
"files": [
"calendar.js",
"calendar.css"
]
}
]
}

Restaurer des fichiers de bibliothèque


La commande libman restore installe les fichiers de bibliothèque définis dans
libman.json . Les règles suivantes s’appliquent :
S’il n’existe aucun fichier libman.json à la racine du projet, une erreur est
renvoyée.
Si une bibliothèque spécifie un fournisseur, la propriété defaultProvider dans
libman.json est ignorée.

Si une bibliothèque spécifie une destination, la propriété defaultDestination dans


libman.json est ignorée.

Synopsis
Console

libman restore [--verbosity]


libman restore [-h|--help]

Options
Les options suivantes sont disponibles pour la commande libman restore :

-h|--help

Afficher les informations d’aide.

--verbosity <LEVEL>

Définissez la verbosité de la sortie. Remplacez <LEVEL> par l’une des valeurs


suivantes :
quiet
normal

detailed

Exemples
Pour restaurer les fichiers de bibliothèque définis dans libman.json :

Console

libman restore

Supprimer des fichiers de bibliothèque


La commande libman clean supprime les fichiers de bibliothèque précédemment
restaurés via LibMan. Dossiers qui deviennent vides après la suppression de cette
opération. Les configurations associées aux fichiers de bibliothèque dans la propriété
libraries de libman.json ne sont pas supprimées.

Synopsis
Console

libman clean [--verbosity]


libman clean [-h|--help]

Options
Les options suivantes sont disponibles pour la commande libman clean :

-h|--help

Afficher les informations d’aide.

--verbosity <LEVEL>

Définissez la verbosité de la sortie. Remplacez <LEVEL> par l’une des valeurs


suivantes :
quiet

normal

detailed

Exemples
Pour supprimer les fichiers de bibliothèque installés via LibMan :

Console

libman clean

Désinstaller les fichiers de bibliothèque


La commande libman uninstall :
Supprime tous les fichiers associés à la bibliothèque spécifiée de la destination
dans libman.json .
Supprime la configuration de bibliothèque associée de libman.json .

Une erreur se produit dans les cas suivants :

Il n’existe aucun fichier libman.json à la racine du projet.


La bibliothèque spécifiée n’existe pas.

Si plusieurs bibliothèques portant le même nom sont installées, vous êtes invité à en
choisir une.

Synopsis
Console

libman uninstall <LIBRARY> [--verbosity]


libman uninstall [-h|--help]

Arguments
LIBRARY

Nom de la bibliothèque à désinstaller. Ce nom peut inclure la notation de numéro de


version (par exemple, @1.2.0 ).

Options
Les options suivantes sont disponibles pour la commande libman uninstall :

-h|--help

Afficher les informations d’aide.

--verbosity <LEVEL>

Définissez la verbosité de la sortie. Remplacez <LEVEL> par l’une des valeurs


suivantes :
quiet

normal

detailed
Exemples
Examinons le fichier libman.json suivant :

JSON

{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "jquery@3.3.1",
"files": [
"jquery.min.js",
"jquery.js",
"jquery.min.map"
],
"destination": "wwwroot/lib/jquery/"
},
{
"provider": "unpkg",
"library": "bootstrap@4.1.3",
"destination": "wwwroot/lib/bootstrap/"
},
{
"provider": "filesystem",
"library": "C:\\temp\\lodash\\",
"files": [
"lodash.js",
"lodash.min.js"
],
"destination": "wwwroot/lib/lodash/"
}
]
}

Pour désinstaller jQuery, l’une des commandes suivantes réussit :

Console

libman uninstall jquery

Console

libman uninstall jquery@3.3.1

Pour désinstaller les fichiers Lodash installés via le fournisseur filesystem :

Console
libman uninstall C:\temp\lodash\

Mettre à jour la version de la bibliothèque


La commande libman update met à jour une bibliothèque installée via LibMan vers la
version spécifiée.

Une erreur se produit dans les cas suivants :

Il n’existe aucun fichier libman.json à la racine du projet.


La bibliothèque spécifiée n’existe pas.

Si plusieurs bibliothèques portant le même nom sont installées, vous êtes invité à en
choisir une.

Synopsis
Console

libman update <LIBRARY> [-pre] [--to] [--verbosity]


libman update [-h|--help]

Arguments
LIBRARY

Nom de la bibliothèque à mettre à jour.

Options
Les options suivantes sont disponibles pour la commande libman update :

-pre

Obtenez la dernière version préliminaire de la bibliothèque.

--to <VERSION>

Obtenez une version spécifique de la bibliothèque.

-h|--help
Afficher les informations d’aide.

--verbosity <LEVEL>

Définissez la verbosité de la sortie. Remplacez <LEVEL> par l’une des valeurs


suivantes :
quiet

normal
detailed

Exemples
Pour mettre à jour jQuery vers la dernière version :

Console

libman update jquery

Pour mettre à jour jQuery vers la version 3.3.1 :

Console

libman update jquery --to 3.3.1

Pour mettre à jour jQuery vers la dernière version préliminaire :

Console

libman update jquery -pre

Gérer le cache de bibliothèque


La commande libman cache gère le cache de bibliothèque LibMan. Le fournisseur
filesystem n’utilise pas le cache de bibliothèque.

Synopsis
Console

libman cache clean [<PROVIDER>] [--verbosity]


libman cache list [--files] [--libraries] [--verbosity]
libman cache [-h|--help]

Arguments
PROVIDER

Utilisé uniquement avec la commande clean . Spécifie le cache du fournisseur à


nettoyer. Les valeurs valides sont les suivantes :

cdnjs

filesystem
jsdelivr

unpkg

Options
Les options suivantes sont disponibles pour la commande libman cache :

--files

Répertoriez les noms des fichiers mis en cache.

--libraries

Répertoriez les noms des bibliothèques mises en cache.

-h|--help

Afficher les informations d’aide.

--verbosity <LEVEL>

Définissez la verbosité de la sortie. Remplacez <LEVEL> par l’une des valeurs


suivantes :
quiet
normal

detailed

Exemples
Pour afficher les noms des bibliothèques mises en cache par fournisseur, utilisez
l’une des commandes suivantes :
Console

libman cache list

Console

libman cache list --libraries

Une sortie similaire à la suivante s’affiche à l’écran :

Console

Cache contents:
---------------
unpkg:
knockout
react
vue
cdnjs:
font-awesome
jquery
knockout
lodash.js
react

Pour afficher les noms des fichiers de bibliothèque mis en cache par fournisseur :

Console

libman cache list --files

Une sortie similaire à la suivante s’affiche à l’écran :

Console

Cache contents:
---------------
unpkg:
knockout:
<list omitted for brevity>
react:
<list omitted for brevity>
vue:
<list omitted for brevity>
cdnjs:
font-awesome
metadata.json
jquery
metadata.json
3.2.1\core.js
3.2.1\jquery.js
3.2.1\jquery.min.js
3.2.1\jquery.min.map
3.2.1\jquery.slim.js
3.2.1\jquery.slim.min.js
3.2.1\jquery.slim.min.map
3.3.1\core.js
3.3.1\jquery.js
3.3.1\jquery.min.js
3.3.1\jquery.min.map
3.3.1\jquery.slim.js
3.3.1\jquery.slim.min.js
3.3.1\jquery.slim.min.map
knockout
metadata.json
3.4.2\knockout-debug.js
3.4.2\knockout-min.js
lodash.js
metadata.json
4.17.10\lodash.js
4.17.10\lodash.min.js
react
metadata.json

Notez que la sortie précédente indique que les versions jQuery 3.2.1 et 3.3.1 sont
mises en cache sous le fournisseur CDNJS.

Pour vider le cache de bibliothèque pour le fournisseur CDNJS :

Console

libman cache clean cdnjs

Après avoir vidé le cache du fournisseur CDNJS, la commande libman cache list
affiche les éléments suivants :

Console

Cache contents:
---------------
unpkg:
knockout
react
vue
cdnjs:
(empty)
Pour vider le cache pour tous les fournisseurs pris en charge :

Console

libman cache clean

Après avoir vidé tous les caches du fournisseur, la commande libman cache list
affiche ce qui suit :

Console

Cache contents:
---------------
unpkg:
(empty)
cdnjs:
(empty)

Ressources supplémentaires
Installer un outil global
Utiliser LibMan avec ASP.NET Core dans Visual Studio
Dépôt GitHub LibMan

6 Collaborer avec nous sur Commentaires sur ASP.NET


GitHub Core
La source de ce contenu se ASP.NET Core est un projet open
trouve sur GitHub, où vous source. Sélectionnez un lien pour
pouvez également créer et fournir des commentaires :
examiner les problèmes et les
demandes de tirage. Pour plus  Ouvrir un problème de
d’informations, consultez notre documentation
guide du contributeur.
 Indiquer des commentaires sur
le produit
Utiliser LibMan avec ASP.NET Core dans
Visual Studio
Article • 30/11/2023

Par Scott Addie

Visual Studio prend en charge LibMan dans les projets ASP.NET Core, avec notamment :

La prise en charge de la configuration et de l’exécution des opérations de


restauration LibMan sur la build.
Les éléments de menu pour déclencher des opérations de restauration et de
nettoyage LibMan.
La boîte de dialogue de recherche pour rechercher des bibliothèques et ajouter les
fichiers à un projet.
La prise en charge de la modification de libman.json , le fichier manifeste LibMan.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Prérequis
Visual Studio 2019 avec la charge de travail ASP.NET et développement web

Ajouter des fichiers de bibliothèque


Les fichiers de bibliothèque peuvent être ajoutés à un projet ASP.NET Core de deux
manières différentes :

1. Utiliser la boîte de dialogue Ajouter une bibliothèque côté client


2. Configurer manuellement les entrées de fichier manifeste LibMan

Utiliser la boîte de dialogue Ajouter une bibliothèque


côté client
Procédez comme suit pour installer une bibliothèque côté client :

Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le dossier de


projet dans lequel les fichiers doivent être ajoutés. Choisissez
Ajouter>Bibliothèque côté client. La boîte de dialogue Ajouter une bibliothèque
côté client s’affiche :
Sélectionnez le fournisseur de bibliothèque dans la liste déroulante Fournisseur.
CDNJS est le fournisseur par défaut.

Tapez le nom de la bibliothèque à extraire dans la zone de texte Bibliothèque.


IntelliSense fournit une liste de bibliothèques commençant par le texte fourni.

Sélectionnez la bibliothèque dans la liste IntelliSense. Notez que le nom de la


bibliothèque comporte le symbole @ et la dernière version stable connue du
fournisseur sélectionné en suffixe.

Déterminez les fichiers à inclure :


Sélectionnez la case d’option Inclure tous les fichiers de bibliothèque pour
inclure tous les fichiers de la bibliothèque.
Sélectionnez la case d’option Choisir des fichiers spécifiques pour inclure un
sous-ensemble des fichiers de la bibliothèque. Lorsque la case d’option est
sélectionnée, l’arborescence du sélecteur de fichiers est activée. Cochez les
cases situées à gauche des noms des fichiers à télécharger.

Spécifiez le dossier de projet pour le stockage des fichiers dans la zone de texte
Emplacement cible. Comme recommandation, stockez chaque bibliothèque dans
un dossier distinct.

Le dossier Emplacement cible suggéré est basé sur l’emplacement à partir duquel
la boîte de dialogue a été lancée :
Si elle a été lancée à partir de la racine du projet :
wwwroot/lib est utilisé si wwwroot existe.
lib est utilisé si wwwroot n’existe pas.
Si elle est lancée à partir d’un dossier de projet, le nom de dossier
correspondant est utilisé.

La suggestion de dossier présente le nom de la bibliothèque en suffixe. Le tableau


suivant illustre les suggestions de dossier lors de l’installation de jQuery dans un
projet Razor Pages.

Emplacement de lancement Dossier suggéré

Racine du projet (si wwwroot existe) wwwroot/lib/jquery/

Racine du projet (si wwwroot n’existe pas) lib/jquery/

Dossier Pages dans le projet Pages/jquery/

Cliquez sur le bouton Installer pour télécharger les fichiers, conformément à la


configuration dans libman.json .

Pour plus d’informations sur l’installation, consultez le flux Gestionnaire de


bibliothèques de la fenêtre Sortie. Par exemple :

Console

Restore operation started...


Restoring libraries for project LibManSample
Restoring library jquery@3.3.1... (LibManSample)
wwwroot/lib/jquery/jquery.min.js written to destination (LibManSample)
wwwroot/lib/jquery/jquery.js written to destination (LibManSample)
wwwroot/lib/jquery/jquery.min.map written to destination (LibManSample)
Restore operation completed
1 libraries restored in 2.32 seconds

Configurer manuellement les entrées de fichier manifeste


LibMan
Toutes les opérations LibMan dans Visual Studio sont basées sur le contenu du
manifeste LibMan de la racine du projet ( libman.json ). Vous pouvez modifier
manuellement libman.json pour configurer les fichiers de bibliothèque pour le projet.
Visual Studio restaure tous les fichiers de bibliothèque une fois libman.json enregistré.

Pour ouvrir libman.json pour modification, les options suivantes existent :

Double-cliquez sur le fichier libman.json dans l’Explorateur de solutions.


Cliquez avec le bouton droit sur le projet dans l’Explorateur de solutions, puis
sélectionnez Gérer les bibliothèques côté client. †
Sélectionnez Gérer les bibliothèques côté client dans le menu Projet de Visual
Studio. †

† Si le fichier libman.json n’existe pas encore à la racine du projet, il est créé avec le
contenu du modèle d’élément par défaut.

Visual Studio offre une prise en charge complète de l’édition de JSON, comme la
colorisation, la mise en forme, IntelliSense et la validation de schéma. Le schéma JSON
du manifeste LibMan se trouve à l’adresse https://json.schemastore.org/libman .

Avec le fichier manifeste suivant, LibMan récupère les fichiers selon la configuration
définie dans la propriété libraries . Voici une explication des littéraux d’objet définis
dans libraries :

Un sous-ensemble de jQuery version 3.3.1 est récupéré à partir du fournisseur


CDNJS. Le sous-ensemble est défini dans la propriété files ; jquery.min.js ,
jquery.js et jquery.min.map. Les fichiers sont placés dans le dossier

wwwroot/lib/jquery du projet.
L’intégralité de Bootstrap version 4.1.3 est récupérée et placée dans un dossier
wwwroot/lib/bootstrap. La propriété provider du littéral d’objet remplace la valeur
de la propriété defaultProvider . LibMan récupère les fichiers Bootstrap à partir du
fournisseur unpkg.
Un sous-ensemble de Lodash a été approuvé par un organe directeur au sein de
l’organisation. Les fichiers lodash.js et lodash.min.js sont récupérés à partir du
système de fichiers local dans C:\temp\lodash\. Les fichiers sont copiés dans le
dossier wwwroot/lib/lodash du projet.

JSON

{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "jquery@3.3.1",
"files": [
"jquery.min.js",
"jquery.js",
"jquery.min.map"
],
"destination": "wwwroot/lib/jquery/"
},
{
"provider": "unpkg",
"library": "bootstrap@4.1.3",
"destination": "wwwroot/lib/bootstrap/"
},
{
"provider": "filesystem",
"library": "C:\\temp\\lodash\\",
"files": [
"lodash.js",
"lodash.min.js"
],
"destination": "wwwroot/lib/lodash/"
}
]
}

7 Notes

LibMan ne prend en charge qu’une seule version de chaque bibliothèque de


chaque fournisseur. Le fichier libman.json échoue à la validation du schéma s’il
contient deux bibliothèques portant le même nom pour un fournisseur donné.

Restaurer des fichiers de bibliothèque


Pour restaurer des fichiers de bibliothèque à partir de Visual Studio, il doit y avoir un
fichier libman.json valide dans la racine du projet. Les fichiers restaurés sont placés
dans le projet à l’emplacement spécifié pour chaque bibliothèque.

Les fichiers de bibliothèque peuvent être restaurés dans un projet ASP.NET Core de
deux manières :

1. Restaurer des fichiers pendant la génération


2. Restaurer des fichiers manuellement

Restaurer des fichiers pendant la génération


LibMan peut restaurer les fichiers de bibliothèque définis dans le cadre du processus de
génération. Par défaut, le comportement de restauration sur build est désactivé.

Pour activer et tester le comportement de restauration sur build :

Cliquez avec le bouton droit sur libman.json dans l’Explorateur de solutions, puis
sélectionnez Activer la restauration des bibliothèques côté client sur build dans
le menu contextuel.

Cliquez sur le bouton Oui lorsque vous êtes invité à installer un package NuGet. Le
package NuGet Microsoft.Web.LibraryManager.Build est ajouté au projet :
XML

<PackageReference Include="Microsoft.Web.LibraryManager.Build"
Version="1.0.113" />

Générez le projet pour confirmer que la restauration du fichier LibMan a lieu. Le


package Microsoft.Web.LibraryManager.Build injecte une cible MSBuild qui
exécute LibMan pendant l’opération de génération du projet.

Passez en revue le flux Build de la fenêtre Sortie pour un journal d’activité LibMan :

Console

1>------ Build started: Project: LibManSample, Configuration: Debug Any


CPU ------
1>
1>Restore operation started...
1>Restoring library jquery@3.3.1...
1>Restoring library bootstrap@4.1.3...
1>
1>2 libraries restored in 10.66 seconds
1>LibManSample ->
C:\LibManSample\bin\Debug\netcoreapp2.1\LibManSample.dll
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped
==========

Lorsque le comportement de restauration sur build est activé, le menu contextuel


libman.json affiche une option Désactiver la restauration côté client bibliothèques sur

build. La sélection de cette option supprime la référence de package


Microsoft.Web.LibraryManager.Build du fichier projet. Par conséquent, les bibliothèques

côté client ne sont plus restaurées sur chaque build.

Quel que soit le paramètre de restauration lors de la génération, vous pouvez effectuer
une restauration manuelle à tout moment à partir du menu contextuel libman.json .
Pour plus d’informations, consultez Restaurer des fichiers manuellement.

Restaurer des fichiers manuellement


Pour restaurer manuellement des fichiers de bibliothèque :

Pour tous les projets de la solution :


Cliquez avec le bouton droit sur le nom de la solution dans l’Explorateur de
solutions.
Sélectionnez l’option Restaurer les bibliothèques côté client.
Pour un projet spécifique :
Cliquez avec le bouton droit sur le fichier libman.json dans l’Explorateur de
solutions.
Sélectionnez l’option Restaurer les bibliothèques côté client.

Pendant l’exécution de l’opération de restauration :

L’icône du Centre d’état des tâches (TSC) dans la barre d’état de Visual Studio est
animée et indique Opération de restauration démarrée. Cliquer sur l’icône ouvre
une info-bulle répertoriant les tâches en arrière-plan connues.

Les messages sont envoyés à la barre d’état et au flux du Gestionnaire de


bibliothèques de la fenêtre Sortie. Par exemple :

Console

Restore operation started...


Restoring libraries for project LibManSample
Restoring library jquery@3.3.1... (LibManSample)
wwwroot/lib/jquery/jquery.min.js written to destination (LibManSample)
wwwroot/lib/jquery/jquery.js written to destination (LibManSample)
wwwroot/lib/jquery/jquery.min.map written to destination (LibManSample)
Restore operation completed
1 libraries restored in 2.32 seconds

Supprimer des fichiers de bibliothèque


Pour effectuer l’opération de nettoyage, qui supprime les fichiers de bibliothèque
précédemment restaurés dans Visual Studio :

Cliquez avec le bouton droit sur le fichier libman.json dans l’Explorateur de


solutions.
Sélectionnez l’option Nettoyer les bibliothèques côté client.

Pour éviter la suppression involontaire de fichiers autres que ceux de la bibliothèque,


l’opération de nettoyage ne supprime pas des répertoires entiers. Elle supprime
uniquement les fichiers inclus dans la restauration précédente.

Pendant l’exécution de l’opération de nettoyage :

L’icône TSC dans la barre d’état de Visual Studio est animée et indique Opération
de bibliothèques clientes démarrée. Cliquer sur l’icône ouvre une info-bulle
répertoriant les tâches en arrière-plan connues.
Les messages sont envoyés à la barre d’état et au flux du Gestionnaire de
bibliothèques de la fenêtre Sortie. Par exemple :
Console

Clean libraries operation started...


Clean libraries operation completed
2 libraries were successfully deleted in 1.91 secs

L’opération de nettoyage supprime uniquement les fichiers du projet. Les fichiers de


bibliothèque restent dans le cache pour une récupération plus rapide lors des
opérations de restauration futures. Pour gérer les fichiers de bibliothèque stockés dans
le cache de l’ordinateur local, utilisez l’interface CLI LibMan.

Désinstaller les fichiers de bibliothèque


Pour désinstaller des fichiers de bibliothèque :

Ouvrez libman.json .

Positionnez le caret à l’intérieur du littéral d’objet libraries correspondant.

Cliquez sur l’icône d’ampoule qui s’affiche dans la marge gauche, puis sélectionnez
Désinstaller <nom_bibliothèque>@<version_bibliothèque> :

Vous pouvez également modifier et enregistrer manuellement le manifeste Li

Vous aimerez peut-être aussi