Aspnet Core Aspnetcore 8.0
Aspnet Core Aspnetcore 8.0
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.
b
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
Concepts et fonctionnalités
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
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.
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.
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 :
Application web Pour maintenir une application MVC Bien démarrer avec
MVC
Scénario Didacticiel
Pour maintenir une application MVC MVC avec Entity Framework Core
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.
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#
C#
Étapes suivantes
Pour plus d’informations, consultez les ressources suivantes :
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.
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.
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
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#
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.
ノ Agrandir le tableau
Besoins multiplateformes
Si votre application web ou de service doit s’exécuter sur plusieurs plateformes, par
exemple Windows, Linux et macOS, utilisez .NET.
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.
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.
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.
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.
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.
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
Ce tutoriel montre comment créer et exécuter une application web ASP.NET Core à
l’aide de l’interface CLI .NET Core.
À 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
La commande précédente :
Windows
CLI .NET
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é.
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>
Étapes suivantes
Dans ce didacticiel, vous avez appris à :
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.
SignalR
Cette section décrit les nouvelles fonctionnalités pour SignalR.
C#
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.
Avant :
Après :
En-têtes HTTP
Chaînes de requête
Formulaires
Cookies
Afficher les données
Données de routage
Fonctionnalités
Ressources supplémentaires
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
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.
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).
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.
7 Notes
Pour plus d’informations sur le nouveau modèle Web App Blazor, consultez les articles
suivants :
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 .
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.
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.
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.
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.
Pour plus d’informations, consultez Rendre les composants Razor en dehors de ASP.NET
Core.
QuickGrid
Le composant QuickGrid Blazor n'est plus expérimental et fait désormais partie du
framework de .NET 8 Blazor.
Pour plus d’informations, consultez Virtualisation des composants ASP.NET Core Razor.
7 Notes
Pour plus d’informations, consultez Déboguer des applications Blazor ASP.NET Core.
Pour plus d’informations, consultez Appliquer une stratégie de sécurité de contenu pour
ASP.NET Core Blazor.
Pour plus d’informations, consultez Gérer les erreurs dans les applications ASP.NET Core
Blazor.
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>
<form method="dialog">
<button>Close</button>
</form>
</dialog>
</div>
@code {
private string? message;
un projet.
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
SignalR
JavaScript
connection.serverTimeoutInMilliseconds = 60000;
connection.keepAliveIntervalInMilliseconds = 30000;
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
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
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);
}
});
C#
builder.ServerTimeout = TimeSpan.FromSeconds(60);
builder.KeepAliveInterval = TimeSpan.FromSeconds(30);
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#
await builder.StartAsync();
La reconnexion avec état est disponible dans ASP.NET Core 8.0 et versions ultérieures.
C#
C#
C#
builder.AddSignalR().AddHubOptions<MyHub>(o =>
o.StatefulReconnectBufferSize = 1000);
JavaScript
L’option bufferSize est facultative avec une valeur par défaut de 100 000 octets.
C#
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.
C#
app.UseRequestLocalization(options =>
{
options.CultureInfoUseUserOverride = false;
});
Pour plus d’informations, consultez Liaison à des collections et à des types complexes à
partir de formulaires.
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#
builder.Services.AddAntiforgery();
app.UseAntiforgery();
app.Run();
L’intergiciel d’antifalsification :
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.
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 .
Les auteurs de bibliothèque qui espèrent prendre en charge l’AOT native sont
encouragés à :
Voici un exemple d’utilisation de cette API pour créer une petite application web :
C#
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.
CreateSlimBuilder.
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.
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.
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#
...
// Add types used in the minimal API app to source generated JSON serializer
content
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
JsonSerializerOptions.TypeInfoResolverChain
Générateurs de sources de chaîne
Modifications nécessaires à la prise en charge de la génération de source
C#
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.
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/
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.
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 :
C#
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.HttpSys;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseHttpSys();
var loggerFactory =
context.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger("Sample");
return next(context);
});
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.
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.
IAuthorizationRequirementData
Avant ASP.NET Core 8, ajouter une stratégie d’autorisation paramétrable à un point de
terminaison requérait l’implémentation de :
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>();
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);
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--;
}
return Task.CompletedTask;
}
}
diff
using AuthRequirementsData.Authorization;
using Microsoft.AspNetCore.Authorization;
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
builder.Services.AddControllers();
- builder.Services.AddSingleton<IAuthorizationPolicyProvider,
MinimumAgePolicyProvider>();
builder.Services.AddSingleton<IAuthorizationHandler,
MinimumAgeAuthorizationHandler>();
app.MapControllers();
app.Run();
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;
}
Divers
Les sections suivantes décrivent diverses nouvelles fonctionnalités dans ASP.NET Core 8.
C#
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();
smallCache.Get("date"));
app.MapControllers();
app.Run();
[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big-cache")]
public ActionResult<object> GetOk([FromKeyedServices("big")] ICache
cache)
{
return cache.Get("data-mvc");
}
}
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
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>]
ID de Cassant ou Description
diagnostic non
ASP0020 Non cassant Les types complexes référencés par les paramètres d’itinéraire
doivent être analysables
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
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 :
Les métriques offrent plusieurs améliorations par rapport aux compteurs d’événements
existants :
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.
.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 :
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 ».
// 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);
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);
C#
C#
Pour obtenir plus d’informations, consultez Journalisation HTTP dans .NET Core et
ASP.NET Core.
C#
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();
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)
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.
C#
@model Product?
C#
builder.Services.AddRazorPages();
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
options.ConsentCookieValue = "true";
});
Contrôleurs d’API
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);
}
C#
using Microsoft.AspNetCore.Mvc;
builder.Services.AddControllers();
builder.Services.AddSingleton<IDateTime, SystemDateTime>();
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.DisableImplicitFromServicesParameters = true;
});
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.
C#
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
builder.Services.AddControllers(options =>
{
options.ModelMetadataDetailsProviders.Add(new
SystemTextJsonValidationMetadataProvider());
});
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
C#
using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
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
Pour plus d’informations, consultez Filtres dans les applications API minimales
C#
// 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]}");
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
C#
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
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[]>));
}
}
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);
Assert.NotNull(okResult.Value);
Assert.Equal(1, okResult.Value.Id);
}
IContentTypeHttpResult
IFileHttpResult
INestedHttpResult
IStatusCodeHttpResult
IValueHttpResult
IValueHttpResult<TValue>
C#
C#
Pour plus d’informations, consultez OpenAPI dans les applications API minimales.
C#
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.
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;
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#
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#
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#
C#
CLI .NET
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.
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.
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;
});
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.
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
Options de navigation
Gérer/empêcher les changements d’emplacement
) Important
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
@code {
private string searchText;
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 :
Exemples :
razor
@* Elements *@
@* Components *@
@code {
private string text = "";
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.
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 .
SHA1
SHA256
SHA384
SHA512
HMACSHA1
HMACSHA256
HMACSHA384
HMACSHA512
AES-CBC
PBKDF2
HKDF
Pour plus d’informations, consultez Validation des formulaires Blazor ASP.NET Core.
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.
Dans .NET 7, le balisage HTML a été combiné avec la page _Host dans les modèles de
projet.
Pour plus d’informations, consultez Gestion des événements ASP.NET Core Blazor.
diff
- builder.Services.AddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
+ builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
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 :
Performances
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;
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 :
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.
L’utilisation de WebSockets sur HTTP/2 tire parti des nouvelles fonctionnalités, dont les
suivantes :
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.
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.
Serveur
Divers
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😍.
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 .
CLI .NET
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.
C#
services.AddW3CLogging(logging =>
{
logging.AdditionalRequestHeaders.Add("x-forwarded-for");
logging.AdditionalRequestHeaders.Add("x-client-ssl-protocol");
});
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.
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.
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 :
SignalR
Compilateur Razor
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.
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 .
é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.
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
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 .
C#
C#
_jsonWriter = null;
}
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
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
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.
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.
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
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).
) Important
Blazor Hybrid est en préversion et ne doit pas être utilisé dans les applications de
production avant la version finale.
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.
HeartbeatSlow .
Microsoft.AspNetCore.Server.Kestrel.BadRequests : ConnectionBadRequest ,
RequestProcessingError , RequestBodyMinimumDataRateNotSatisfied .
Microsoft.AspNetCore.Server.Kestrel.Connections : ConnectionAccepted ,
ConnectionStart , ConnectionStop , ConnectionPause , ConnectionResume ,
ApplicationAbortedConnection .
Microsoft.AspNetCore.Server.Kestrel.Http2 : Http2ConnectionError ,
Http2ConnectionClosing , Http2ConnectionClosed , Http2StreamError ,
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"
}
}
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"
}
]
}
C#
using Microsoft.AspNetCore.Http.Features;
using System.Diagnostics;
app.Run();
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.
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 .
C#
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.WebUtilities;
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 .
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.
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.
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.
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.
diff
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
+ "Microsoft.AspNetCore": "Warning"
C#
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
C#
app.Run();
Les journaux qui en résultent ressemblent désormais à l’exemple de sortie présenté ci-
dessous :
CLI .NET
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 />
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#
builder.Services.AddRazorPages()
.AddNewtonsoftJson(options =>
{
options.OutputFormatterMemoryBufferThreshold = 48 * 1024;
});
Pour plus d’informations, consultez cette demande de tirage (pull request) GitHub et
le fichier NewtonsoftJsonOutputFormatterTest.cs .
C#
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.
C#
C#
app.Run();
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
JSON
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware":
"Information"
}
}
}
C#
using Microsoft.AspNetCore.HttpLogging;
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#
de la syntaxe C# standard :
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 .
C#
using StackExchange.Redis.Profiling;
builder.Services.AddStackExchangeRedisCache(options =>
{
options.ProfilingSession = () => new ProfilingSession();
});
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
<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
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.
à 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
C#
[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}
Le fichier Person/Index.cshtml :
CSHTML
@model Person
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
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 :
CLI .NET
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>
C#
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApp1", Version =
"v1" });
});
}
C#
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
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.
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.
InputDate
InputNumber
InputSelect
Pour plus d’informations, consultez Vue d’ensemble des formulaires Blazor ASP.NET
Core.
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.
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
Pour plus d’informations, consultez Utiliser des filtres hub dans ASP.NET Core SignalR.
C#
Java
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.
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>();
});
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.
HTTP/2
Réductions significatives des allocations dans le chemin de code HTTP/2.
C#
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
C#
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
})
.AllowAnonymous();
});
}
Améliorations d’API
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#
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#
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.
C#
await host.RunAsync();
}
}
C#
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}"
}
}
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.
CSHTML
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
Pour plus d’informations, consultez Intégrer des composants dans Razor Pages et les
applications MVC.
C#
razor
razor
<div @onclick="OnSelectParentDiv">
<div @onclick="OnSelectChildDiv"
@onclick:stopPropagation="_stopPropagation">
...
</div>
</div>
@code {
private bool _stopPropagation = false;
}
Pour plus d’informations, consultez Gérer les erreurs dans les applications ASP.NET
CoreBlazor.
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 :
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.
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.
gRPC
gRPC :
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
JavaScript
Une implémentation personnalisée peut être transmise pour un contrôle total des
intervalles de reconnexion.
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
C#
if (IsUserAllowedToDoThis(resource.HubMethodName,
context.User.Identity.Name))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
return currentUsername.EndsWith("@jabbr.net",
StringComparison.OrdinalIgnoreCase));
}
}
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)
{
}
}
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");
});
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#
dessus.
Une fois la boucle for terminée et que la fonction locale se termine, l’achèvement du
flux est envoyé :
C#
Les applications clientes JavaScript utilisent SignalR Subject (ou un objet RxJS ) pour
l’argument stream de la méthode Hub UploadStream ci-dessus.
JavaScript
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.
Pour ajouter Json.NET à ASP.NET Core 3.0, consultez Ajouter la prise en charge du
format JSON basé sur Newtonsoft.Json.
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 :
C#
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#
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.
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 :
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,
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.
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
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");
});
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.
Pour plus d’informations sur la migration, consultez Port de votre code .NET Framework
vers .NET Core.
XML
<Project Sdk="Microsoft.NET.Sdk.Web">
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.
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 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.
HTTP/2 est une révision majeure du protocole HTTP. Les fonctionnalités notables de
HTTP/2 sont les suivantes :
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.
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 :
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.
Informations supplémentaires
Pour obtenir la liste complète des modifications, consultez les Notes de publication
d’ASP.NET Core 2.2 .
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.
Pour plus d’informations, consultez Créer une interface utilisateur réutilisable à l’aide du
projet Class LibraryRazor.
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.
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.
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 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());
}
}
[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).
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 Version de compatibilité pour ASP.NET Core MVC.
Informations supplémentaires
Pour obtenir la liste complète des modifications, consultez les Notes de publication
d’ASP.NET Core 2.1 .
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.
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.
d’adopter une approche flexible pour le filtrage entre fournisseurs et le filtrage propre
au fournisseur.
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 :
sur le contenu que vous transmettez. Vous pouvez définir ces valeurs sur le contenu
retourné avec du code semblable au suivant :
C#
Le fichier retourné aux visiteurs a les en-têtes HTTP appropriés pour les valeurs ETag et
LastModified .
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.
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 .
Informations supplémentaires
Pour obtenir la liste complète des modifications, consultez les Notes de publication
d’ASP.NET Core 2.0 .
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 .
h NOUVEAUTÉS
Janvier 2024
Décembre 2023
Novembre 2023
Octobre 2023
Septembre 2023
Août 2023
e VUE D’ENSEMBLE
p CONCEPT
Guide du contributeur
Communauté
h NOUVEAUTÉS
Communauté
h NOUVEAUTÉS
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.
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.
Inconvénients :
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 :
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.
À 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
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
Sélectionnez Suivant.
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
Visual Studio affiche la boîte de dialogue suivante lorsqu’un projet n’est pas encore
configuré pour utiliser SSL :
Visual Studio :
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#
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#
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.
C#
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.
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.
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.
C#
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models;
Visual Studio
Visual Studio
C#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Visual Studio
PowerShell
Add-Migration InitialCreate
Update-Database
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()’.
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)
{
}
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.
Tester l’application
1. Exécutez l’application et ajoutez /Movies à l’URL dans le navigateur
( http://localhost:port/movies ).
Console
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 .
Visual Studio
C#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);
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();
Étapes suivantes
Précédent : Bien démarrer
Ouvrir un problème de
documentation
Ce tutoriel décrit les pages Razor créées par génération de modèles automatique dans
le tutoriel précédent.
C#
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies;
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#
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;
C#
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
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>
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
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.
CSHTML
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 .
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";
}
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 .
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" />
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>
CSHTML
CSHTML
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.
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.
CSHTML
@{
Layout = "_Layout";
}
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;
}
[BindProperty]
public Movie Movie { get; set; } = default!;
_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 méthode OnPostAsync est exécutée quand la page publie les données de formulaire :
C#
_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 :
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">
CSHTML
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
Visual Studio
C#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
Visual Studio
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
3. Faites un clic droit sur la table Movie et sélectionnez Afficher les données :
C#
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
namespace RazorPagesMovie.Models;
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;
}
Visual Studio
C#
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
using RazorPagesMovie.Models;
builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
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 :
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 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.
C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models;
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
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.
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 .
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
CSHTML
@page "{id:int?}"
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();
}
if (Movie == null)
{
return NotFound();
}
return Page();
}
C#
_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);
}
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.
C#
[BindProperty]
public Movie Movie { get; set; } = default!;
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
Quand une requête HTTP GET est effectuée sur la page Movies/Edit, par exemple,
https://localhost:5001/Movies/Edit/3 :
sur la page.
Le formulaire d’édition s’affiche avec les valeurs relatives au film.
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.
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
Ouvrir un problème de
documentation
Dans les sections suivantes, la recherche de films par genre ou par nom est ajoutée.
C#
[BindProperty(SupportsGet = true)]
public string? SearchString { get; set; }
[BindProperty(SupportsGet = true)]
public string? MovieGenre { get; set; }
SearchString : Contient le texte que les utilisateurs entrent dans la zone de texte
« 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)]
C#
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?
CSHTML
@page "{searchString?}"
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.
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.*@
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
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;
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#
C#
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
Dans cette section, Migrations Entity Framework Code First est utilisé pour :
Quand vous utilisez EF Code First pour créer et suivre automatiquement une base de
données, Code First :
C#
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>
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 :
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.
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"
},
Générez la solution.
Visual Studio
PowerShell
Add-Migration Rating
Update-Database
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.
4. Sélectionnez OK.
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
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 à :
La prise en charge de la validation fournie par les Pages Razor et Entity Framework est
un bon exemple du principe DRY :
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;
[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;
}
RegularExpression Rating :
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 .
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.
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 :
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.
C#
if (!ModelState.IsValid)
{
return Page();
}
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.
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; }
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.
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.
C#
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.
C#
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.
C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models;
[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.
C#
SQL
Visual Studio
PowerShell
Add-Migration New_DataAnnotations
Update-Database
Examinez la méthode Up :
C#
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)");
}
SQL
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
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.
Prérequis
Visual Studio
Visual Studio utilise le modèle de projet par défaut pour le projet MVC créé. Le
projet créé :
Exécuter l'application
Visual Studio
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.
Modifiez le code.
Enregistrez le fichier.
Actualiser rapidement le navigateur et voir les modifications de code.
Visual Studio
Dans le prochain didacticiel de la série, vous allez découvrir MVC et commencer à écrire
du code.
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 :
Privacy .
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.
Dans l’Explorateur de solutions, cliquez avec le bouton droit sur Contrôleurs >
Ajouter > Contrôleur.
C#
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers;
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.
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 :
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 :
C#
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
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 :
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#
Le code précédent :
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 .
Cliquez avec le bouton droit sur le dossier Vues/HelloWorld, puis cliquez sur Ajouter
> Nouvel élément.
CSHTML
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>Hello from our View Template!</p>
Accédez à https://localhost:{PORT}/HelloWorld :
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 :
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 .
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>
Remarque : Le contrôleur Movies n’a pas été implémenté. À ce stade, le lien Movie App
ne fonctionne pas.
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.
CSHTML
@{
Layout = "_Layout";
}
CSHTML
@{
ViewData["Title"] = "Movie List";
}
CSHTML
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
Les contrôleurs sont chargés de fournir les données nécessaires pour qu’un modèle de
vue restitue une réponse.
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.
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;
L’objet dictionnaire ViewData contient des données qui seront passées à la vue.
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>
https://localhost:{PORT}/HelloWorld/Welcome?name=Rick&numtimes=4
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 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.
C#
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models;
La classe Movie contient un champ Id , qui est nécessaire à la base de données pour la
clé primaire.
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.
Visual Studio
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.VisualStudio.Web.CodeGeneration.Design
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.
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
PowerShell
Add-Migration InitialCreate
Update-Database
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 .
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 ».
Tester l’application
Visual Studio
Si vous obtenez une exception semblable à ce qui suit, vous avez peut-être manqué
la commande Update-Database à l’étape des migrations :
Console
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.
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)
{
}
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.
Visual Studio
C#
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));
Visual Studio
JSON
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Data Source=MvcMovieContext-ea7a4069-f366-4742-
bd1c-3f753a804ce1.db"
}
}
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);
});
}
migration Up .
C#
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 .
C#
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == 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 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.
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);
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 .
C#
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
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
Visual Studio
C#
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));
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 :
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;
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.
}
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;
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));
SeedData.Initialize(services);
}
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.
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.
C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
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.
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>
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
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();
}
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.
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.
C#
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
@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 .
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
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.
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);
}
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
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.
C#
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
La ligne suivante dans la méthode d’action Index crée une requête LINQ pour
sélectionner les films :
C#
La requête est seulement définie à ce stade ; elle n’a pas été exécutée sur la base de
données.
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
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.
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
C#
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
C#
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title!.Contains(id));
}
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#
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
CSHTML
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
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.
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.
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.
CSHTML
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
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
C#
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace MvcMovie.Models;
SearchString , qui contient le texte que les utilisateurs entrent dans la zone de
texte de recherche.
C#
// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string
searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
if (!string.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
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#
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).
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>
<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>
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
Dans cette section, Migrations Entity Framework Code First est utilisé pour :
Quand EF Code First est utilisé pour créer automatiquement une base de données, Code
First :
C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
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.
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 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 :
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.)
3. Utilisez les migrations Code First pour mettre à jour le schéma de base de
données.
Visual Studio
PowerShell
Add-Migration Rating
Update-Database
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.
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
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.
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;
[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.
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.
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.
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);
}
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.
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>
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.
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.
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#
[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.
C#
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.).
7 Notes
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
C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
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
C#
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
return View(movie);
}
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
pas un film réel). Si vous avez recherché un film null, l’application lève une exception.
C#
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == 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)
Précédent
Les didacticiels suivants fournissent des expériences de travail de base pour la création
d'applications Blazor.
Microsoft Learn
Blazor Parcours d'apprentissage
Blazor Modules d'apprentissage
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
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
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.
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"
}
]
Visual Studio
C#
namespace TodoApi.Models;
Vous pouvez placer des classes de modèle n’importe où dans le projet, mais le dossier
Models est utilisé par convention.
C#
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models;
C#
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Le code précédent :
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.
C#
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
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 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.
GET /api/todoitems
GET /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.
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.
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 :
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.
À 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
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();
}
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.
C#
namespace TodoApi.Models;
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;
// 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();
}
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();
}
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 :
) 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 de ce tutoriel . Consultez Guide pratique
pour télécharger.
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 .
" 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
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 .
Console
Console
mongosh
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.
Console
db.createCollection('Books')
Console
{ "ok" : 1 }
Console
Console
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("61a6058e6c43f32854e51f51"),
ObjectId("61a6058e6c43f32854e51f52")
]
}
7 Notes
Console
db.Books.find().pretty()
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"
}
1. Accédez à Fichier>Nouveau>Projet.
PowerShell
Install-Package MongoDB.Driver
C#
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace BookStoreApi.Models;
[BsonElement("Name")]
public string BookName { get; set; } = null!;
JSON
{
"BookStoreDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "BookStore",
"BooksCollectionName": "Books"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
C#
namespace BookStoreApi.Models;
C#
C#
using BookStoreApi.Models;
C#
using BookStoreApi.Models;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
namespace BookStoreApi.Services;
public BooksService(
IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
{
var mongoClient = new MongoClient(
bookStoreDatabaseSettings.Value.ConnectionString);
_booksCollection = mongoDatabase.GetCollection<Book>(
bookStoreDatabaseSettings.Value.BooksCollectionName);
}
builder.Services.AddSingleton<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 :
C#
public BooksService(
IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
{
var mongoClient = new MongoClient(
bookStoreDatabaseSettings.Value.ConnectionString);
_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.
représentant la collection. Dans ce didacticiel, les méthodes suivantes sont appelées sur
la collection :
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;
[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);
[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;
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 :
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"
}
]
JSON
{
"id": "61a6058e6c43f32854e51f52",
"bookName": "Clean Code",
"price": 43.15,
"category": "Computers",
"author": "Robert C. Martin"
}
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 .
C#
builder.Services.AddSingleton<BooksService>();
builder.Services.AddControllers()
.AddJsonOptions(
options => options.JsonSerializerOptions.PropertyNamingPolicy =
null);
[BsonElement("Name")]
[JsonPropertyName("Name")]
public string BookName { get; set; } = null!;
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.
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 :
) 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
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
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.
C#
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
if (builder.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
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">✖</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>
css
#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;
}
JavaScript
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);
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 tr = tBody.insertRow();
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.
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.
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.
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.
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));
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 :
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.
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.
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}";
Vous pouvez éventuellement déployer le service web sur un service cloud tel qu’Azure et
mettre à jour le RestUrl .
C#
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; }
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();
}
_todoList.Add(todoItem1);
_todoList.Add(todoItem2);
_todoList.Add(todoItem3);
}
}
}
C#
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
C#
[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ControllerBase
{
private readonly ITodoRepository _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
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#
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.
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.
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
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.
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();
}
...
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 :
C#
[ApiController]
[Route("/")]
public class WeatherForecastController : ControllerBase
1. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet, puis
sélectionnez Publier.
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.
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.
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.
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.
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.
Nettoyage
Après avoir testé l’application, accédez au portail Azure , puis supprimez l’application.
Ressources supplémentaires
Gestion des API Azure
Azure App Service
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 :
Prerequisites
Visual Studio
Examiner le code
Le fichier Program.cs contient le code suivant :
C#
app.Run();
Le code précédent :
Exécuter l’application
Visual Studio
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
Visual Studio
C#
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.
C#
using Microsoft.EntityFrameworkCore;
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.
C#
using Microsoft.EntityFrameworkCore;
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
return Results.NotFound();
});
app.Run();
C#
Visual Studio
Ce didacticiel utilise l’Explorateur des points de terminaison et les fichiers .http pour
tester l’API.
C#
Exécutez l'application. Le navigateur affiche une erreur 404, car il n’y a plus de point de
terminaison / .
@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
}
@TodoApi_HostAddress = https://localhost:7057
Post {{TodoApi_HostAddress}}/todoitems
Content-Type: application/json
{
"name":"walk dog",
"isComplete":true
}
###
Exécutez l'application.
C#
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.
Get {{TodoApi_HostAddress}}/todoitems
###
JSON
[
{
"id": 1,
"name": "walk dog",
"isComplete": false
}
]
###
JSON
{
"id": 1,
"name": "walk dog",
"isComplete": false
}
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 :
C#
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.
Visual Studio
###
Content-Type: application/json
{
"id": 1,
"name": "feed fish",
"isComplete": false
}
C#
Visual Studio
DELETE {{TodoApi_HostAddress}}/todoitems/1
###
C#
using Microsoft.EntityFrameworkCore;
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
return Results.NotFound();
});
app.Run();
Le code précédent dispose des modifications suivantes :
Testez les points de terminaison pour vérifier qu’ils fonctionnent de la même façon.
C#
using Microsoft.EntityFrameworkCore;
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
return TypedResults.NotFound();
}
C#
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#
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
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#
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#
// Act
var result = await TodosApi.GetAllTodos(db);
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#
Le champ secret doit être masqué dans cette application, mais une application
administrative peut choisir de l’exposer.
C#
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name,
todoItem.IsComplete);
}
C#
using Microsoft.EntityFrameworkCore;
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
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.
É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.
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 :
Prérequis
Visual Studio
Visual Studio
Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet, puis
sélectionnez Ajouter>Bibliothèque côté client.
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;
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.
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 :
"use strict";
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 :
Exécuter l’application
Visual Studio
Sélectionnez Ctrl + F5 pour exécuter l’application sans débogage.
Conseil
CLI .NET
dotnet dev-certs https --clean
dotnet dev-certs https --trust
É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)
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.
Prérequis
Node.js avec npm
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 :
Configurer le serveur
Dans cette section, vous allez configurer l’application web ASP.NET Core pour envoyer
et recevoir des messages SignalR.
builder.Services.AddSignalR();
C#
app.UseDefaultFiles();
app.UseStaticFiles();
C#
using Microsoft.AspNetCore.SignalR;
namespace SignalRWebpack.Hubs;
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.
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.
Console
npm init -y
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"
}
Console
option empêche les mises à niveau involontaires vers des versions de package plus
récentes.
JSON
"scripts": {
"build": "webpack --mode=development --watch",
"release": "webpack --mode=production",
"publish": "npm run release && dotnet publish -c Release"
},
JavaScript
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",
}),
],
};
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
*,
*::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;
}
JSON
{
"compilerOptions": {
"target": "es5"
}
}
index.ts :
TypeScript
divMessages.appendChild(m);
divMessages.scrollTop = divMessages.scrollHeight;
});
btnSend.addEventListener("click", send);
function send() {
connection.send("newMessage", username, tbMessage.value)
.then(() => (tbMessage.value = ""));
}
touche Entrée.
click : se déclenche lorsque l’utilisateur sélectionne le bouton Envoyer et
Console
Test de l'application
Vérifiez que l’application fonctionne avec les étapes suivantes :
Visual Studio
Console
Ressources supplémentaires
Client JavaScript SignalRASP.NET Core
Utiliser des hubs dans ASP.NET Core SignalR
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 :
Apprenez à :
Prérequis
Visual Studio
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.
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.
Confirmez que le Framework est .NET 8.0 ou version ultérieure. Sélectionnez Create
(Créer).
Dans la boîte de dialogue Gérer les packages NuGet, confirmez que la source du
package est définie sur nuget.org .
C#
using Microsoft.AspNetCore.SignalR;
namespace BlazorSignalRApp.Hubs;
C#
using Microsoft.AspNetCore.ResponseCompression;
using BlazorSignalRApp.Hubs;
C#
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
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");
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;
await hubConnection.StartAsync();
}
Exécuter l’application
Suivez l’aide relative à vos outils :
Visual Studio
Étapes suivantes
Dans ce didacticiel, vous avez appris à :
Pour obtenir une aide détaillée sur les frameworks SignalR et Blazor, consultez les
ensembles de documentation de référence suivants :
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)
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.
Prérequis
Visual Studio
Exécuter le service
Visual Studio
Visual Studio affiche la boîte de dialogue suivante lorsqu’un projet n’est pas
encore configuré pour utiliser SSL :
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
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.
utilisé par Kestrel. Pour plus d’informations, consultez Configuration dans ASP.NET
Core.
Program.cs , qui contient :
Visual Studio
PowerShell
Install-Package Grpc.Net.Client
Install-Package Google.Protobuf
Install-Package Grpc.Tools
Ajouter greet.proto
Créez un dossier Protos dans le projet du client gRPC.
JSON
Visual Studio
XML
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>
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
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();
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();
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();
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
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
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
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 .
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
Créez le projet.
PowerShell
Update-Database
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>
<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>
CSHTML
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
Le code précédent remplace le texte à propose de ASP.NET Core par le texte à propos
de cette application.
Le modèle de données
Les sections suivantes créent un modèle de données :
L’entité Student
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; }
primaire de classe Student est StudentID . Pour plus d’informations, consultez EF Core -
Clés.
é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.
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
}
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.
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
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; }
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.
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>
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
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.VisualStudio.Web.CodeGeneration.Design
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
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> .
C#
using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext (DbContextOptions<SchoolContext> options)
: base(options)
{
}
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.
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.
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")));
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();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
PowerShell
Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
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();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
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 :
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.
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
}
context.Students.AddRange(students);
context.SaveChanges();
context.Courses.AddRange(courses);
context.SaveChanges();
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.
C#
Visual Studio
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.
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.
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#
Voici quelques éléments à connaître lors de l’écriture de code asynchrone qui utilise EF
Core :
instructions qui ne font que changer un IQueryable , telles que var students =
context.Students.Where(s => s.LastName == "Davolio") .
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.
C#
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.
Étapes suivantes
Utiliser SQLite pour le développement, SQL Server pour la production
Tutoriel suivant
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.
C#
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
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#
if (Student == null)
{
return NotFound();
}
return Page();
}
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.
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.
généré.
C#
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 :
Recherche les champs de formulaire dotés d’un préfixe « Student ». Par exemple :
Student.FirstMidName . Il ne respecte pas la casse.
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#
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.
C#
C#
[BindProperty]
public StudentVM StudentVM { get; set; }
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 :
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");}
}
C#
if (Student == null)
{
return NotFound();
}
return Page();
}
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();
}
FirstOrDefaultAsync a été remplacé par FindAsync. Quand vous n’êtes pas tenu
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é
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.
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;
[BindProperty]
public Student Student { get; set; }
public string ErrorMessage { get; set; }
if (Student == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = String.Format("Delete {ID} failed. Try
again", id);
}
return Page();
}
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
@page
@model ContosoUniversity.Pages.Students.DeleteModel
@{
ViewData["Title"] = "Delete";
}
<h1>Delete</h1>
<p class="text-danger">@Model.ErrorMessage</p>
<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>
É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
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#
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;
}
Le code précédent :
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
C#
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
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#
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;
}
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#
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.
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 :
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.
C#
CurrentFilter = 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;
}
Le code précédent :
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#
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.
Pour plus d’informations, consultez How to use case-insensitive query with Sqlite
provider .
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 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 Recherche.
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.
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; }
this.AddRange(items);
}
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"
}
}
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;
CurrentFilter = searchString;
Le code précédent :
Remplace le type IList<Student> de la propriété Students par le type
PaginatedList<Student> .
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.
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.
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"
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
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>
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.
C#
using System;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }
CSHTML
@page
@model ContosoUniversity.Pages.AboutModel
@{
ViewData["Title"] = "Student Body Statistics";
}
<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>
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;
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 .
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.
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 :
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 :
PowerShell
Drop-Database
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.
C#
context.Database.EnsureCreated();
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
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");
}
migrationBuilder.DropTable(
name: "Course");
migrationBuilder.DropTable(
name: "Student");
}
}
}
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
Consultez Réinitialiser toutes les migrations pour supprimer toutes les migrations.
text
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.
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 :
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;
}
}
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]
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 :
L’attribut DataType émet des attributs HTML 5 data- (prononcé data dash en anglais).
Les attributs DataType ne fournissent aucune validation.
Attribut DisplayFormat
C#
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 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.
Attribut StringLength
C#
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.
C#
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
Visual Studio
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.
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#
Attribut Display
C#
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 ».
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
text
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.
7 Notes
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; }
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.
C#
Entité OfficeAssignment
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; }
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 .
table est à la fois une clé primaire et une clé étrangère dans une autre table.
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.
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; }
[Range(0, 5)]
public int Credits { 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 :
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.
C#
C#
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; }
[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; }
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; }
C#
Le ? dans le code précédent indique que la propriété peut accepter la valeur Null.
C#
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.
C#
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
C#
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
C#
C#
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 .
Les entités Instructor et Course ont une relation plusieurs-à-plusieurs à l’aide d’une
table de jointure pure (PJT).
C#
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}
C#
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 :
Pour plus d’informations sur les attributs et l’API Fluent, consultez Méthodes de
configuration.
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
}
context.AddRange(students);
context.AddRange(instructors);
context.AddRange(officeAssignments);
context.AddRange(departments);
context.AddRange(courses);
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.
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.
Visual Studio
PowerShell
Drop-Database
Add-Migration InitialCreate
Update-Database
base de données.
Visual Studio
Étapes suivantes
Les deux tutoriels suivants montrent comment lire et mettre à jour des données
associées.
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.
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.
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.
Visual Studio
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;
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.
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>
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.
HTML
C#
CourseViewModel :
C#
sont affichés. Il existe une relation un-à-plusieurs entre les entités Course et
Enrollment .
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; }
}
}
Visual Studio
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;
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;
}
}
}
}
C#
Instructor.OfficeAssignment
Instructor.Courses
Course.Department
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.
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;
}
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>
</table>
}
https://localhost:5001/Instructors?id=2
HTML
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.
HTML
HTML
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.
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.
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; }
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 .
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;
[BindProperty]
public Course Course { get; set; }
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");
}
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).
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");}
}
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.
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;
[BindProperty]
public Course Course { get; set; }
if (Course == null)
{
return NotFound();
}
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");
}
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.
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");}
}
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.
C#
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
if (Course == null)
{
return NotFound();
}
return Page();
}
CSHTML
@page
@model ContosoUniversity.Pages.Courses.DeleteModel
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<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>
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>
C#
namespace ContosoUniversity.Models.SchoolViewModels
{
public class AssignedCourseData
{
public int CourseID { get; set; }
public string Title { get; set; }
public bool Assigned { get; set; }
}
}
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;
formateur est affecté au cours. Un HashSet est utilisé pour permettre des recherches
efficaces.
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;
[BindProperty]
public Instructor Instructor { get; set; }
if (Instructor == null)
{
return NotFound();
}
PopulateAssignedCourseData(_context, Instructor);
return Page();
}
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();
}
Le code précédent :
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.
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);
}
}
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;
<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.
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;
[BindProperty]
public Instructor Instructor { get; set; }
if (selectedCourses.Length > 0)
{
newInstructor.Courses = new List<Course>();
// Load collection with one DB call.
_context.Courses.Load();
}
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 :
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#
if (selectedCourses.Length > 0)
{
newInstructor.Courses = new List<Course>();
// Load collection with one DB call.
_context.Courses.Load();
}
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();
}
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;
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
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; }
if (Instructor == null)
{
return NotFound();
}
return Page();
}
if (instructor == null)
{
return RedirectToPage("./Index");
}
_context.Instructors.Remove(instructor);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
Le code précédent apporte les modifications suivantes :
Étapes suivantes
Tutoriel précédent Tutoriel suivant
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.
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.
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.
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 :
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é.
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.
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 :
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.
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; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
[Timestamp]
public byte[] ConcurrencyToken { get; set; }
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 :
Le code suivant montre une partie du T-SQL généré par EF Core quand le nom
Department est mis à jour :
SQL
Le code en surbrillance suivant montre le T-SQL qui vérifie qu’une seule ligne a été
mise à jour :
SQL
Créez le projet.
Visual Studio
PowerShell
Add-Migration RowVersion
Update-Database
C#
b.Property<byte[]>("ConcurrencyToken")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("rowversion");
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 :
Créez le projet.
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;
[BindProperty]
public Department Department { get; set; }
// Replace ViewData["InstructorID"]
public SelectList InstructorNameSL { get; set; }
if (Department == null)
{
return NotFound();
}
return Page();
}
if (departmentToUpdate == null)
{
return HandleDeletedDepartment();
}
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();
}
return Page();
}
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.");
}
}
}
Visual Studio
C#
if (departmentToUpdate == null)
{
return HandleDeletedDepartment();
}
// Set ConcurrencyToken to value read in OnGetAsync
_context.Entry(departmentToUpdate).Property(
d => d.ConcurrencyToken).OriginalValue =
Department.ConcurrencyToken;
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 .
Visual Studio
C#
[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();
}
return Page();
}
if (departmentToUpdate == null)
{
return HandleDeletedDepartment();
}
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();
}
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#
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.");
}
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();
}
diff
+ departmentToUpdate.ConcurrencyToken = Guid.NewGuid();
_context.Entry(departmentToUpdate)
.Property(d => d.ConcurrencyToken).OriginalValue =
Department.ConcurrencyToken;
- Department.ConcurrencyToken = (byte[])dbValues.ConcurrencyToken;
+ Department.ConcurrencyToken = dbValues.ConcurrencyToken;
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 :
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
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.
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;
[BindProperty]
public Department Department { get; set; }
public string ConcurrencyErrorMessage { get; set; }
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();
}
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 :
CSHTML
@page "{id:int}"
@model ContosoUniversity.Pages.Departments.DeleteModel
@{
ViewData["Title"] = "Delete";
}
<h1>Delete</h1>
<p class="text-danger">@Model.ConcurrencyErrorMessage</p>
<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>
Changez le budget sous le premier onglet de navigateur, puis cliquez sur Save.
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
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 :
Voici certains éléments que le tutoriel Razor Pages présente, contrairement à celui-ci :
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
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 :
Voici certains éléments que le tutoriel Razor Pages présente, contrairement à celui-ci :
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.
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 .
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.
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
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>
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 »</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 »</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 .
pour EF.
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.
Dans les sections suivantes, une classe est créée pour chacune de ces entités.
L’entité Student
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; }
plutôt que ID .
Les lignes Enrollment contiennent la valeur PK d’un étudiant dans la colonne de clé
étrangère StudentID (FK).
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.
C#
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
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 .
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
L’entité Course
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; }
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.
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)
{
}
Le code précédent crée une propriété DbSet pour chaque jeu d’entités. Dans la
terminologie EF :
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)
{
}
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;
}
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
);
services.AddControllersWithViews();
}
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;
MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
C#
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
);
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddControllersWithViews();
}
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();
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.");
}
}
}
C#
host.Run();
}
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.
avec le contrôleur.
C#
namespace ContosoUniversity.Controllers
{
public class StudentsController : Controller
{
private readonly SchoolContext _context;
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#
Les éléments de programmation asynchrones dans ce code sont expliqués plus loin
dans ce tutoriel.
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 :
Utilisez l’Explorateur d’objets SQL Server (SSOX) pour afficher la base de données dans
Visual Studio :
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 :
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.
C#
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é.
Voici quelques éléments à connaître lors de l’écriture de code asynchrone qui utilise EF :
qui ne font, par exemple, que changer IQueryable , telles que var students =
context.Students.Where(s => s.LastName == "Davolio") .
Pour plus d’informations sur la programmation asynchrone dans .NET, consultez Vue
d’ensemble du code asynchrone.
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).
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
Prérequis
Bien démarrer avec EF Core et ASP.NET Core MVC
C#
if (student == null)
{
return NotFound();
}
return View(student);
}
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
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
HTML
<a href="/Students/Edit/6">Edit</a>
HTML
HTML
<a href="/Students/Edit?studentID=6">Edit</a>
Pour plus d’informations sur les Tag Helpers, consultez Tag Helpers dans ASP.NET Core.
CSHTML
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.LastName)
</dd>
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.
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é :
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).
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#
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é.
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.
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.
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);
}
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.
C#
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.
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
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
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#
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.
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.
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.
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.)
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.
Obtenir le code
Télécharger ou afficher l’application complète.
Étapes suivantes
Dans ce tutoriel, vous allez :
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).
Prérequis
Implémenter la fonctionnalité CRUD
C#
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#
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.
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.
C#
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.
CSHTML
<p>
<a asp-action="Create">Create New</a>
</p>
<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.
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.
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; }
this.AddRange(items);
}
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.
C#
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
int pageSize = 3;
return View(await
PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1,
pageSize));
}
C#
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.
C#
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
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.
CSHTML
@model PaginatedList<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"]" 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
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>
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.
C#
using System;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }
C#
using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Data;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.Extensions.Logging;
C#
C#
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 .
CSHTML
@model
IEnumerable<ContosoUniversity.Models.SchoolViewModels.EnrollmentDateGroup>
@{
ViewData["Title"] = "Student Body Statistics";
}
<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>
Obtenir le code
Télécharger ou afficher l’application complète.
Étapes suivantes
Dans ce tutoriel, vous allez :
Passez au tutoriel suivant pour découvrir comment gérer les modifications du modèle
de données à l’aide de migrations.
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.
Prérequis
Tri, filtrage et pagination
CLI .NET
7 Notes
CLI .NET
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>.
C#
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.
l’échec.
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
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])
);
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.
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.
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.
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 :
Prérequis
Utilisation des migrations EF Core
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 .
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; }
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
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.
C#
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.).
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; }
C#
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
CLI .NET
CLI .NET
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é.
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; }
L’ajout de l’attribut Column change le modèle sur lequel repose SchoolContext , donc il
ne correspond pas à la base de données.
CLI .NET
CLI .NET
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.
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;
}
}
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#
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).
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.
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; }
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)]
C#
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#
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; }
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.
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; }
[Range(0, 5)]
public int Credits { get; set; }
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.
ci-dessus.
C#
C#
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#
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; }
[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; }
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; }
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 :
C#
C#
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
}
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#
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#
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 ).
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; }
}
}
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.
C#
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}
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.
C#
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.
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();
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 .
CLI .NET
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 :
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 .
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.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 .
JSON
{
"ConnectionStrings": {
"DefaultConnection": "Server=
(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;
MultipleActiveResultSets=true"
},
7 Notes
CLI .NET
CLI .NET
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 :
Passez au tutoriel suivant pour en savoir plus sur l’accès aux données associées.
Les illustrations suivantes montrent les pages que vous allez utiliser.
Dans ce tutoriel, vous allez :
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é.
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.
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#
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>
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.
HTML
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 :
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.
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.
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.
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 :
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#
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);
}
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
C#
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 .
C#
À 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#
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#
À la place de :
C#
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.
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>
CSHTML
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
CSHTML
</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
Ce code lit la propriété Enrollments du modèle de vue pour afficher la liste des
étudiants inscrits dans ce cours.
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#
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 .
Obtenir le code
Télécharger ou afficher l’application complète.
Étapes suivantes
Dans ce tutoriel, vous allez :
Passez au tutoriel suivant pour découvrir comment mettre à jour les données associées.
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 :
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.
C#
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#
C#
[HttpPost, ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditPost(int? id)
{
if (id == null)
{
return NotFound();
}
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);
}
C#
C#
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.
C#
return View(course);
}
C#
return View(course);
}
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>
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>
<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 .
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.
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.
C#
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();
}
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);
}
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
C#
if
(String.IsNullOrWhiteSpace(instructorToUpdate.OfficeAssignment?.Locatio
n))
{
instructorToUpdate.OfficeAssignment = null;
}
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.
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; }
}
}
C#
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();
}
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;
}
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove =
instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID ==
course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
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 .
C#
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#
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#
if (instructorCourses.Contains(course.CourseID))
{
CourseAssignment courseToRemove =
instructorToUpdate.CourseAssignments.FirstOrDefault(i => i.CourseID ==
course.CourseID);
_context.Remove(courseToRemove);
}
}
}
}
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;
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.
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);
_context.Instructors.Remove(instructor);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
// 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).
C#
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#
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;
Obtenir le code
Télécharger ou afficher l’application complète.
Étapes suivantes
Dans ce tutoriel, vous allez :
Passez au tutoriel suivant pour découvrir comment gérer les conflits d’accès
concurrentiel.
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 :
Prérequis
Mettre à jour les données associées
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.
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.
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.
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.
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.
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.
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.
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; }
[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; }
[Timestamp]
public byte[] RowVersion { get; set; }
SQL timestamp avant son remplacement par le type SQL rowversion . Le type .NET pour
rowversion est un tableau d’octets.
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
CLI .NET
C#
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>
C#
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();
}
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}");
}
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.
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
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#
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#
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#
if (databaseValues.Name != clientValues.Name)
{
ModelState.AddModelError("Name", $"Current value:
{databaseValues.Name}");
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");
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");}
}
Changez un champ sous le premier onglet du navigateur, puis cliquez sur Save.
C#
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 .
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#
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#
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.
CSHTML
@model ContosoUniversity.Models.Department
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<p class="text-danger">@ViewData["ConcurrencyErrorMessage"]</p>
<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>
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é.
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>
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 :
Passez au tutoriel suivant pour découvrir comment implémenter l’héritage table par
hiérarchie pour les entités Instructor et Student.
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.
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.
Prérequis
Gérer l’accès concurrentiel
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.
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
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; }
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; }
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; }
C#
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}
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.
CLI .NET
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.
C#
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.
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.)
CLI .NET
7 Notes
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 :
Passez au tutoriel suivant pour découvrir comment gérer divers scénarios Entity
Framework relativement avancés.
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.
Prérequis
Implémenter l’héritage
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.
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.
C#
if (department == null)
{
return NotFound();
}
return View(department);
}
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.
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);
}
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 :
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,
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
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 .
CSHTML
@{
ViewBag.Title = "UpdateCourseCredits";
}
<h2>Update Course Credits</h2>
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.
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 :
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.
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.
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.
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;
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.
C#
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
if (string.IsNullOrEmpty(sortOrder))
{
sortOrder = "LastName";
}
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.
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.
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 :
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
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 :
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 :
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.
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ù :
Razor Pages
Contrôleurs MVC avec vues
API web avec contrôleurs
API web minimales
C#
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
C#
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);
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("RPMovieConte
xt")));
C#
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#
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
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 :
Il existe trois hôtes différents capables d’exécuter une application ASP.NET Core :
C#
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
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.
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.
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.
C#
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
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
C#
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();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Pour plus d’informations, consultez Gérer les erreurs dans ASP.NET Core.
Racine de contenu
La racine du contenu est le chemin de base pour :
Racine web
La racine web est le chemin de base des fichiers de ressources publics statiques, par
exemple :
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.
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.
Ressources supplémentaires
Code source de WebApplicationBuilder
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 .
Razor Pages
Contrôleurs MVC avec vues
API web avec contrôleurs
API minimales
C#
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
Pour plus d’informations sur le démarrage de l’application, consultez ASP.NET Core vue
d’ensemble des principes fondamentaux.
C#
if (!string.IsNullOrWhiteSpace(option))
{
httpContext.Items["option"] = WebUtility.HtmlEncode(option);
}
await _next(httpContext);
}
}
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>
C#
using WebStartup.Middleware;
builder.Services.AddRazorPages();
builder.Services.AddTransient<IStartupFilter,
RequestSetOptionsStartupFilter>();
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>
doivent s’exécuter.
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 .
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.
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.
C#
classe IndexModel :
C#
C#
public interface IMyDependency
{
void WriteMessage(string message);
}
C#
C#
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
C#
C#
C#
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
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.
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.
C#
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.
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 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();
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;
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>();
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;
}
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;
builder.Services
.AddConfig(builder.Configuration)
.AddMyDependencyGroup();
builder.Services.AddRazorPages();
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 :
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.
Il est courant d’utiliser plusieurs implémentations lors d’une simulation de types à des
fins de test.
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
C#
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, 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;
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();
smallCache.Get("date"));
app.MapControllers();
app.Run();
[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big-cache")]
public ActionResult<object> GetOk([FromKeyedServices("big")] ICache
cache)
{
return cache.Get("data-mvc");
}
}
C#
Le code suivant crée plusieurs inscriptions de la classe Operation en fonction des durées
de vie nommées :
C#
builder.Services.AddRazorPages();
builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();
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#
C#
await _next(context);
}
}
Les services délimités et temporaires doivent être résolus dans la méthode InvokeAsync :
C#
await _next(context);
}
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.
JSON
{
"MyKey": "MyKey from appsettings.Developement.json",
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Debug",
"Microsoft": "Error"
}
}
}
C#
builder.Services.AddScoped<IMyDependency, MyDependency>();
app.Run();
Validation de l’étendue
Consultez Comportement d’injection de constructeurs dans Injection de dépendances
dans .NET
7 Notes
É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.
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#
Console.WriteLine("Service1.Dispose");
_disposed = true;
}
}
Console.WriteLine("Service2.Dispose");
_disposed = true;
}
}
Console.WriteLine("Service3.Dispose");
_disposed = true;
}
}
C#
using DIsample2.Services;
builder.Services.AddRazorPages();
builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();
C#
Console
Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose
C#
builder.Services.AddRazorPages();
builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());
Recommandations
Consultez Recommandations dans Injection de dépendances dans .NET
Incorrect :
Correct :
C#
...
}
}
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.
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.
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.
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.
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
ASP.NET Core 8.0 introduit la prise en charge pour l’avance de temps (AOT, ahead-of-
time) native .NET.
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.
ノ Agrandir le tableau
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 ✔️
XML
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
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.
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.
diff
using MyFirstAotWebApi;
+using System.Text.Json.Serialization;
+builder.Services.ConfigureHttpJsonOptions(options =>
+{
+ options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
+});
app.Run();
+[JsonSerializable(typeof(Todo[]))]
+internal partial class AppJsonSerializerContext : JsonSerializerContext
+{
+
+}
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;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
app.Run();
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
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.
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.
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 à :
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;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
app.Run();
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
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#
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
ASP.NET Core 8.0 introduit la prise en charge pour l’avance de temps (AOT, ahead-of-
time) native .NET.
7 Notes
Prérequis
CLI .NET Core
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.
CLI .NET
Sortie
The template "ASP.NET Core Web API (native AOT)" was created
successfully.
CLI .NET
dotnet publish
Sortie
dir bin\Release\net8.0\win-x64\publish
Output
Directory: C:\Code\Demos\MyFirstAotWebApi\bin\Release\net8.0\win-
x64\publish
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
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
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 à :
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
7 Notes
Le RDG :
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.
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>
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;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(
0, AppJsonSerializerContext.Default);
});
app.Run();
public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool
IsComplete = false);
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
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
Value
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.
C#
using System.Text.Json.Serialization;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
class Wrapper
{
public static Func<IResult> GetTodos = () =>
Results.Ok(new Todo(1, "Write test fix"));
}
C#
using System.Text.Json.Serialization;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
app.Run();
Value
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;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
app.Run();
C#
using System.Text.Json.Serialization;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
app.Run();
Value
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;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
app.Run();
abstract class TodoRequest
{
public int Id { get; set; }
public Todo? Todo { get; set; }
}
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
C#
using System.Text.Json.Serialization;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
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; }
}
[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.
Value
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;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
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; }
}
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
C#
using System.Text.Json.Serialization;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
app.MapPut("/v1/todos/{id}",
([AsParameters] TodoRequest todoRequest) =>
Results.Ok(todoRequest.Todo));
app.Run();
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}
Value
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;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
app.Run();
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
C#
using System.Text.Json.Serialization;
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
[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 .
Value
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;
app.Run();
// Additional Constructor
public TodoItemRequest()
{
Id = 1;
Todos = [new Todo(1, "Write tests", DateTime.UtcNow.AddDays(2))];
}
{
Id = id;
Task = task;
DueDate = dueDate;
}
}
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}
C#
using System.Text.Json.Serialization;
app.Run();
Value
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;
builder.Services.AddSingleton(todos);
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0,
AppJsonSerializerContext.Default);
});
app.Run();
struct TodoItemRequest
{
public int Id { get; set; }
[AsParameters]
public Todo[] todos { get; set; }
}
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}
C#
using System.Text.Json.Serialization;
struct TodoItemRequest
{
public int Id { get; set; }
//[AsParameters]
public Todo[] todos { get; set; }
}
[JsonSerializable(typeof(Todo[]))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}
Value
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;
app.Run();
C#
using Microsoft.AspNetCore.Mvc;
app.Run();
Value
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#
C#
record Todo();
record Wrapper<T> { }
Value
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#
record Wrapper<T> { }
C#
record Wrapper<T> { }
Value
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;
builder.Services.AddKeyedSingleton<IService, FizzService>("fizz");
var app = builder.Build();
app.Run();
using Microsoft.AspNetCore.Mvc;
builder.Services.AddKeyedSingleton<IService, FizzService>("fizz");
builder.Services.AddKeyedSingleton<IService, BuzzService>("buzz");
builder.Services.AddSingleton<IService, FizzBuzzService>();
var app = builder.Build();
app.Run();
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 :
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).
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#
app.Run();
C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run();
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#
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é.
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.
C#
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;
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();
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 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();
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 :
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();
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#
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.
C#
app.Map("/map1", HandleMapTest1);
app.Map("/map2", HandleMapTest2);
app.Run();
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.
C#
C#
app.Map("/map1/seg1", HandleMultiSeg);
app.Run();
C#
app.Run();
Requête Response
C#
app.Run();
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.
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 .
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.
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.
ASP.NET Core et de
ses dépendances,
notamment la
disponibilité de la
base de données.
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.
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)
Décompression des Prend en charge la Avant les composants qui lisent le corps des
requêtes décompression des requêtes.
requêtes.
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
redirection des
requêtes.
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
Fenêtre fixe
Fenêtre glissante
Compartiment de jetons
Concurrence
C#
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
builder.Services.AddRateLimiter(_ => _
.AddFixedWindowLimiter(policyName: "fixed", options =>
{
options.PermitLimit = 4;
options.Window = TimeSpan.FromSeconds(12);
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = 2;
}));
app.UseRateLimiter();
app.Run();
Le code précédent :
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;
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;
}));
app.UseRateLimiter();
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 .
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 :
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.
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
C#
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
var builder = WebApplication.CreateBuilder(args);
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;
}));
app.UseRateLimiter();
app.Run();
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
C#
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
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;
}));
app.UseRateLimiter();
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.
C#
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
builder.Services.AddRateLimiter(_ => _
.AddConcurrencyLimiter(policyName: concurrencyPolicy, options =>
{
options.PermitLimit = myOptions.PermitLimit;
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
options.QueueLimit = myOptions.QueueLimit;
}));
app.UseRateLimiter();
}).RequireRateLimiting(concurrencyPolicy);
app.Run();
C#
using System.Globalization;
using System.Threading.RateLimiting;
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 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)
});
}));
});
app.Run();
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.
C#
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using WebRateLimitAuth.Models;
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyRateLimitOptions>(
builder.Configuration.GetSection(MyRateLimitOptions.MyRateLimit));
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;
}));
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;
}));
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.MapRazorPages().RequireRateLimiting(slidingPolicy);
app.MapDefaultControllerRoute().RequireRateLimiting(fixedPolicy);
app.Run();
Program.cs :
C#
[EnableRateLimiting("fixed")]
public class Home2Controller : Controller
{
private readonly ILogger<Home2Controller> _logger;
[EnableRateLimiting("sliding")]
public ActionResult Privacy()
{
return View();
}
[DisableRateLimiting]
public ActionResult NoLimit()
{
return View();
}
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));
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;
}));
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;
}));
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;
[EnableRateLimiting("sliding")]
public ActionResult Privacy()
{
return View();
}
[DisableRateLimiting]
public ActionResult NoLimit()
{
return View();
}
C#
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();
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));
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);
});
});
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();
app.Run();
2 Avertissement
La classe SampleRateLimiterPolicy
C#
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.Extensions.Options;
using WebRateLimitAuth.Models;
namespace WebRateLimitAuth;
C#
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Primitives;
using WebRateLimitAuth.Models;
builder.Services.AddAuthorization();
builder.Services.AddAuthentication("Bearer").AddJwtBearer();
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
});
}
app.UseAuthorization();
app.UseRateLimiter();
app.Run();
C#
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
});
}
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
conditions :
Les intergiciels et les points de terminaison configurés par l’utilisateur sont ajoutés
entre UseRouting et UseEndpoints .
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
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();
C#
app.UseRouting();
app.UseRouting();
app.Run(context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});
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.
Les intergiciels peuvent être testés isolément avec TestServer. Vous pouvez ainsi :
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 :
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 .
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();
...
}
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();
Assert.NotEqual(HttpStatusCode.NotFound, response.StatusCode);
}
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();
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
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();
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
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();
Assert.True(response.IsSuccessStatusCode);
var responseBody = await response.Content.ReadAsStringAsync();
Assert.Equal("Hello Tests", responseBody);
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 .
dotnet/aspnetcore#21677
dotnet/aspnetcore#18463
dotnet/aspnetcore#13273
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.
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();
app.UseHttpsRedirection();
app.UseResponseCaching();
2 Avertissement
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#
builder.Services.AddResponseCaching();
app.UseHttpsRedirection();
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.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 :
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 .
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.
UseCaseSensitivePaths Détermine si les réponses sont mises en cache sur des chemins
respectant la casse. La valeur par défaut est false .
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#
builder.Services.AddResponseCaching(options =>
{
options.MaximumBodySize = 1024;
options.UseCaseSensitivePaths = true;
});
app.UseHttpsRedirection();
app.UseResponseCaching();
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ê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 .
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.
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.
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
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.
C#
using System.Globalization;
app.UseHttpsRedirection();
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
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.
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.
C#
using System.Globalization;
namespace Middleware.Example;
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
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;
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
C#
using Middleware.Example;
using System.Globalization;
app.UseHttpsRedirection();
app.UseRequestCulture();
app.Run();
C#
namespace Middleware.Example;
C#
using Middleware.Example;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IMessageWriter, LoggingMessageWriter>();
app.UseHttpsRedirection();
app.UseMyCustomMiddleware();
app.Run();
C#
namespace Middleware.Example;
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
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#
while (true)
{
var bytesRemaining = await requestBody.ReadAsync(buffer, offset: 0,
buffer.Length);
if (bytesRemaining == 0)
{
break;
}
ArrayPool<byte>.Shared.Return(buffer);
Si vous souhaitez voir les commentaires de code traduits dans une langue autre que
l’anglais, dites-le nous dans cette discussion GitHub .
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#
while (true)
{
var bytesRemaining = await requestBody.ReadAsync(buffer, offset: 0,
buffer.Length);
if (bytesRemaining == 0)
{
results.Add(builder.ToString());
break;
}
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);
}
ArrayPool<byte>.Shared.Return(buffer);
return results;
}
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;
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);
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;
}
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
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é
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.
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 :
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#
builder.Services.AddRequestDecompression();
app.UseRequestDecompression();
app.Run();
C#
C#
builder.Services.AddRequestDecompression(options =>
{
options.DecompressionProviders.Add("custom", new
CustomDecompressionProvider());
});
app.UseRequestDecompression();
app.Run();
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.
HTTP.sys HttpSysOptions.MaxRequestBodySize
IIS IISServerOptions.MaxRequestBodySize
Kestrel KestrelServerLimits.MaxRequestBodySize
2 Avertissement
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
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.
C#
if (!string.IsNullOrWhiteSpace(keyValue))
{
dbContext.Requests.Add(new Request("Conventional", keyValue));
await dbContext.SaveChangesAsync();
}
await _next(context);
}
}
C#
if (!string.IsNullOrWhiteSpace(keyValue))
{
_dbContext.Requests.Add(new Request("Factory", keyValue));
await _dbContext.SaveChangesAsync();
}
await next(context);
}
}
C#
C#
Le middleware activé par fabrique est ajouté au conteneur intégré dans Program.cs :
C#
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#
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é.
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
7 Notes
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;
IMiddleware
IMiddleware définit le middleware pour le pipeline des requêtes de l’application.
C#
if (!string.IsNullOrWhiteSpace(keyValue))
{
_db.Add(new Request()
{
DT = DateTime.UtcNow,
MiddlewareActivation =
"SimpleInjectorActivatedMiddleware",
Value = keyValue
});
await _db.SaveChangesAsync();
}
await next(context);
}
}
C#
C#
_container.Register<AppDbContext>(() =>
{
var optionsBuilder = new DbContextOptionsBuilder<DbContext>();
optionsBuilder.UseInMemoryDatabase("InMemoryDb");
return new AppDbContext(optionsBuilder.Options);
}, Lifestyle.Scoped);
_container.Register<SimpleInjectorActivatedMiddleware>();
_container.Verify();
}
C#
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#
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.
C#
app.Run();
C#
app.Run("http://localhost:3000");
Plusieurs ports
C#
app.Urls.Add("http://localhost:3000");
app.Urls.Add("http://localhost:4000");
app.Run();
CLI .NET
C#
app.Run($"http://localhost:{port}");
ASPNETCORE_URLS=http://localhost:3000
ASPNETCORE_URLS=http://localhost:3000;https://localhost:5000
http://*:3000
C#
app.Urls.Add("http://*:3000");
app.MapGet("/", () => "Hello World");
app.Run();
http://+:3000
C#
app.Urls.Add("http://+:3000");
app.Run();
http://0.0.0.0:3000
C#
app.Urls.Add("http://0.0.0.0:3000");
app.Run();
ASPNETCORE_URLS=http://*:3000;https://+:5000;http://0.0.0.0:5005
Pour plus d’informations, consultez Configurer des points de terminaison pour le serveur
web ASP.NET Core Kestrel
app.Urls.Add("https://localhost:3000");
app.Run();
JSON
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Certificates": {
"Default": {
"Path": "cert.pem",
"KeyPath": "key.pem"
}
}
}
}
C#
app.Urls.Add("https://localhost:3000");
app.Run();
C#
using System.Security.Cryptography.X509Certificates;
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);
});
});
app.Urls.Add("https://localhost:3000");
app.Run();
Lire l’environnement
C#
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/oops");
}
app.Run();
Configuration
Le code suivant est lu à partir du système de configuration :
C#
app.Run();
Journalisation
Le code suivant écrit un message dans le journal au démarrage de l’application :
C#
app.Run();
Pour plus d’informations, consultez Journalisation dans .NET Core et ASP.NET Core
C#
builder.Services.AddControllers();
builder.Services.AddScoped<SampleService>();
app.MapControllers();
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#
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
var app = builder.Build();
app.Run();
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.
C#
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}");
Pour plus d’informations, consultez Vue d’ensemble des principes de base d’ASP.NET
Core
C#
builder.Configuration.AddIniFile("appsettings.ini");
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#
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#
if (builder.Environment.IsDevelopment())
{
Console.WriteLine($"Running in development.");
}
app.Run();
Personnaliser IHostBuilder
Les méthodes d’extension existantes sur IHostBuilder sont accessibles à l’aide de la
propriété Host :
C#
app.Run();
Personnaliser IWebHostBuilder
Les méthodes d’extension sur IWebHostBuilder sont accessibles à l’aide de la propriété
WebApplicationBuilder.WebHost.
C#
app.Run();
C#
app.Run();
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
Ajouter un intergiciel
Tout intergiciel ASP.NET Core existant peut être configuré sur WebApplication :
C#
app.Run();
C#
app.MapGet("/", () =>
{
throw new InvalidOperationException("Oops, the '/' route has thrown an
exception.");
});
app.Run();
Cet article fournit des informations sur l’utilisation de l’hôte générique .NET dans
ASP.NET Core.
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 :
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();
C#
await Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build()
.RunAsync();
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.
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.
C#
public HostApplicationLifetimeEventsHostedService(
IHostApplicationLifetime hostApplicationLifetime)
=> _hostApplicationLifetime = hostApplicationLifetime;
return Task.CompletedTask;
}
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.
IHostEnvironment
Injectez le service IHostEnvironment dans une classe pour obtenir des informations sur
les paramètres suivants :
ApplicationName
EnvironmentName
ContentRootPath
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.
résultats additifs. L’hôte utilise l’option qui définit une valeur en dernier sur une clé
donnée.
C#
Host.CreateDefaultBuilder(args)
.ConfigureHostConfiguration(hostConfig =>
{
hostConfig.SetBasePath(Directory.GetCurrentDirectory());
hostConfig.AddJsonFile("hostsettings.json", optional: true);
hostConfig.AddEnvironmentVariables(prefix: "PREFIX_");
hostConfig.AddCommandLine(args);
});
résultats additifs. L’application utilise l’option qui définit une valeur en dernier sur une
clé donnée.
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
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
C#
Host.CreateDefaultBuilder(args)
.UseContentRoot("/path/to/content/root")
// ...
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
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
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
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
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
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
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
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
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
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
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
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
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
C#
webBuilder.UseUrls("http://*:5000;http://localhost:5001;https://hostname:500
2");
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");
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 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#
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 :
appsettings.{Environment}.json .
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 .
C#
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddXmlFile("appsettings.xml", optional: true,
reloadOnChange: true);
})
...
C#
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Warning);
})
...
C#
WebHost.CreateDefaultBuilder(args)
.ConfigureKestrel((context, options) =>
{
options.Limits.MaxRequestBodySize = 20000000;
});
7 Notes
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.
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é : 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")
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
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>")
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)
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")
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")
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)
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.
C#
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://*:5000;http://localhost:5001;https://hostname:5002")
Clé : shutdownTimeoutSeconds
Type : int
Valeur par défaut : 5
Définition avec : UseShutdownTimeout
La variable d’environnement: ASPNETCORE_SHUTDOWNTIMEOUTSECONDS
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")
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#
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
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
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
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#
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)
C#
C#
Produit le même résultat que Start(RequestDelegate app), sauf que l’application répond
sur http://localhost:8080 .
Start(Action<IRouteBuilder> routeBuilder)
C#
Requête Response
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();
}
StartWith(Action<IApplicationBuilder> app)
C#
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();
}
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#
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;
}
7 Notes
C#
IWebHostEnvironment peut être injecté dans la méthode Invoke lors de la création d’un
C#
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.
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#
C#
Quand ValidateScopes est défini sur true , le fournisseur de services par défaut vérifie
que :
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é.
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
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.
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 :
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 .
C#
public class Index2Model : PageModel
{
private IConfigurationRoot ConfigRoot;
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. À
appsettings.json
JSON
{
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
},
"MyKey": "My appsettings.json Value",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
C#
1. appsettings.json
2. appsettings.{Environment}.json : par exemple les fichiers
appsettings.Production.json et appsettings.Development.json . La version de
C#.
JSON
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
C#
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 :
C#
Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);
C#
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;
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
C#
using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
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>();
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;
}
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;
builder.Services.AddRazorPages();
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 :
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
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.
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.
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 :
CLI .NET
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
Appelez AddEnvironmentVariables avec une chaîne pour spécifier un préfixe pour les
variables d’environnement :
C#
builder.Services.AddRazorPages();
builder.Configuration.AddEnvironmentVariables(prefix: "MyCustomPrefix_");
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.
CLI .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 :
Consultez Préfixes des chaînes de connexion pour plus d’informations sur les chaînes de
connexion de base de données Azure.
variables d’environnement.
appsettings.json
JSON
{
"SmtpServer": "smtp.example.com",
"Logging": [
{
"Name": "ToEmail",
"Level": "Critical",
"Args": {
"FromAddress": "[email protected]",
"ToAddress": "[email protected]"
}
},
{
"Name": "ToConsole",
"Level": "Information"
}
]
}
Variables d’environnement
Console
JSON
"applicationUrl": "https://localhost:5001;http://localhost:5000"
C#
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 :
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.
CLI .NET
CLI .NET
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.
C#
builder.Services.AddRazorPages();
builder.Configuration.AddCommandLine(args, switchMappings);
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#
correspondance de commutateur.
JSON
{
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
},
"MyKey": "My appsettings.json Value",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
C#
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.
Valeurs de configuration :
Fournisseurs de configuration
Le tableau suivant présente les fournisseurs de configuration disponibles pour les
applications ASP.NET Core.
d’environnement
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.
MYSQLCONNSTR_ MySQL
C#
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();
ini
MyKey="MyIniConfig.ini Value"
[Position]
Title="My INI Config title"
Name="My INI Config name"
[Logging:LogLevel]
Default=Information
Microsoft=Warning
C#
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;
builder.Configuration.AddJsonFile("MyConfig.json",
optional: true,
reloadOnChange: true);
builder.Services.AddRazorPages();
var app = builder.Build();
Le code précédent :
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.
C#
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();
XML
C#
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
Le code suivant lit le fichier de configuration précédent et affiche les clés et les valeurs :
C#
XML
key:attribute
section:key:attribute
C#
.ConfigureAppConfiguration((hostingContext, config) =>
{
var path = Path.Combine(
Directory.GetCurrentDirectory(), "path/to/files");
config.AddKeyPerFile(directoryPath: path, optional: true);
})
C#
builder.Configuration.AddInMemoryCollection(Dict);
builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddCommandLine(args);
builder.Services.AddRazorPages();
C#
UseUrls
--urls sur la ligne de commande
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 :
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 .
set Kestrel__Endpoints__Https__Url=https://localhost:8888
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#
Dans le code précédent, si NumberKey est introuvable dans la configuration, la valeur par
défaut de 99 est utilisée.
JSON
{
"section0": {
"key0": "value00",
"key1": "value01"
},
"section1": {
"key0": "value10",
"key1": "value11"
},
"section2": {
"subsection0": {
"key0": "value200",
"key1": "value201"
},
"subsection1": {
"key0": "value210",
"key1": "value211"
}
}
}
C#
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.
C#
C#
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#
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 .
JSON
{
"array": {
"entries": {
"0": "value00",
"1": "value10",
"2": "value20",
"4": "value40",
"5": "value50"
}
}
}
C#
builder.Configuration
.AddJsonFile("MyArray.json",
optional: true,
reloadOnChange: true);
builder.Services.AddRazorPages();
C#
return Content(s);
}
}
C#
text
Models/EFConfigurationValue.cs :
C#
EFConfigurationProvider/EFConfigurationContext.cs :
C#
EFConfigurationProvider/EFConfigurationSource.cs :
C#
public EFConfigurationSource(Action<DbContextOptionsBuilder>
optionsAction) => _optionsAction = optionsAction;
EFConfigurationProvider/EFConfigurationProvider.cs :
C#
OptionsAction(builder);
Data = !dbContext.Values.Any()
? CreateAndSaveDefaultValues(dbContext)
: dbContext.Values.ToDictionary(c => c.Id, c => c.Value);
}
}
dbContext.Values.AddRange(configValues
.Select(kvp => new EFConfigurationValue
{
Id = kvp.Key,
Value = kvp.Value
})
.ToArray());
dbContext.SaveChanges();
return configValues;
}
}
Extensions/EntityFrameworkExtensions.cs :
C#
C#
//using Microsoft.EntityFrameworkCore;
builder.Configuration.AddEFConfiguration(
opt => opt.UseInMemoryDatabase("InMemoryDb"));
app.Run();
C#
// ...
}
}
Pour plus d’informations sur l’accès aux valeurs à l’aide de IConfiguration , consultez
GetValue et GetSection, GetChildren et Exists dans cet article.
@page
@model Test5Model
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
Dans le code suivant, MyOptions est ajouté au conteneur de service avec Configure et lié
à la configuration :
C#
using SampleApp.Models;
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
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>
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
C#
app.Run();
JSON
{
...
"KeyOne": "Key One Value",
"KeyTwo": 1999,
"KeyThree": true
}
C#
using SampleApp.Models;
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(myOptions =>
{
myOptions.Option1 = "Value configured in delegate";
myOptions.Option2 = 500;
});
C#
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é.
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 :
suivantes :
Héberger ASP.NET Core sur Windows avec IIS
Module ASP.NET Core (ANCM) pour IIS
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
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.
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.
JSON
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
C#
Classe d’options :
Le code suivant :
C#
Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);
C#
Bind permet également la création d’une classe abstraite. Prenez le code suivant, qui
utilise la classe abstraite SomethingWithAName :
C#
namespace ConfigSample.Options;
C#
Configuration.GetSection(NameTitleOptions.NameTitle).Bind(nameTitleOptions);
Les appels à Bind sont moins stricts que les appels à Get<> :
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;
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
C#
Interfaces d’options
IOptions<TOptions>:
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 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.
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
C#
public TestSnapModel(IOptionsSnapshot<MyOptions>
snapshotOptionsAccessor)
{
_snapshotOptions = snapshotOptionsAccessor.Value;
}
C#
using SampleApp.Models;
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
IOptionsMonitor
Le code suivant inscrit une instance de configuration à laquelle MyOptions se lie.
C#
using SampleApp.Models;
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
C#
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#
C#
using SampleApp.Models;
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
C#
public TestNOModel(IOptionsSnapshot<TopItemSettings>
namedOptionsAccessor)
{
_monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
_yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
}
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
Pour plus d’informations sur l’ajout d’un dépôt personnalisé, consultez Utiliser
AddOptions pour configurer un dépôt personnalisé.
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));
JSON
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
C#
[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 :
C#
using OptionsValidationSample.Configuration;
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations();
var app = builder.Build();
C#
try
{
var configValue = _config.Value;
}
catch (OptionsValidationException ex)
{
foreach (var failure in ex.Failures)
{
_logger.LogError(failure);
}
}
}
Le code suivant applique une règle de validation plus complexe à l’aide d’un délégué :
C#
using OptionsValidationSample.Configuration;
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.
IValidateOptions<TOptions> et IValidatableObject
C#
if (string.IsNullOrEmpty(match.Value))
{
vor = $"{options.Key1} doesn't match RegEx \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;
}
}
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;
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyConfigOptions>
(builder.Configuration.GetSection(
MyConfigOptions.MyConfig));
builder.Services.AddSingleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>();
var app = builder.Build();
ValidateOnStart
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;
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#
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"));
C#
using OptionsValidationSample.Configuration;
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
C#
Ressources supplémentaires
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
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
IHostEnvironment.EnvironmentName peut être défini sur n’importe quelle valeur, mais les
Staging
Production: La valeur par défaut si DOTNET_ENVIRONMENT et ASPNETCORE_ENVIRONMENT
n’ont pas été définis.
Le code suivant :
C#
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
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>
Les commandes CLI .NET suivantes créent et exécutent une application web nommée
EnvironmentsSample :
Bash
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
CLI .NET
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.
Le fichier launchSettings.json :
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"
}
}
}
}
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é.
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
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.
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"
}
}
}
}
CLI .NET
dotnet run --launch-profile "EnvironmentsSample"
2 Avertissement
launchSettings.json ne doit pas stocker les secrets. Vous pouvez utiliser l’outil
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.
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.
Pour définir l’environnement dans une application Azure App Service à l’aide du
portail :
Console
set ASPNETCORE_ENVIRONMENT=Staging
dotnet run --no-launch-profile
PowerShell
$Env:ASPNETCORE_ENVIRONMENT = "Staging"
dotnet run --no-launch-profile
Pour définir la valeur globalement dans Windows, utilisez l’une des approches suivantes
:
[Environment]::SetEnvironmentVariable :
Console
PowerShell
[Environment]::SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT",
"Staging", "Machine")
XML
<PropertyGroup>
<EnvironmentName>Development</EnvironmentName>
</PropertyGroup>
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
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.
C#
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.
C#
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
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
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.
C#
builder.Services.AddRazorPages();
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.
C#
builder.Services.AddRazorPages();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
C#
L’exemple suivant :
C#
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"
}
}
}
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,
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.
}
}
}
}
Logging:Debug:LogLevel:Default:Information
Le niveau de journalisation minimal peut être spécifié pour tous les éléments suivants :
Passés au fournisseur.
Journalisés ou affichés.
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"
}
}
}
}
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
C#
builder.Logging.AddConsole();
app.Run();
C#
using Microsoft.Extensions.Logging.Console;
app.Run();
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 :
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
Console
JSON
"Logging": {
"Console": {
"LogLevel": {
"Microsoft.Hosting.Lifetime": "Trace"
}
}
}
Console
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 :
L’algorithme suivant est utilisé pour chaque fournisseur quand un objet ILogger est créé
pour une catégorie donnée :
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
C#
C#
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.
ノ Expand table
Critical 5 LogCritical Fournit des informations sur des échecs qui nécessitent
un examen immédiat. Exemples : perte de données,
espace disque insuffisant.
Dans le tableau précédent, LogLevel est listé du plus faible niveau de gravité au plus
élevé.
C#
[HttpGet]
public IActionResult Test1(int id)
{
var routeInfo = ControllerContext.ToCtxString(id);
return ControllerContext.MyDisplayRouteInfo();
}
C#
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
_logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id);
if (todoItem == null)
{
_logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT
FOUND", id);
return NotFound();
}
return ItemToDTO(todoItem);
}
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
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#
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
_logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", 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
C#
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
_logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", 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;
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
C#
Par exemple, lorsque vous journalisez des événements vers le Stockage Table Azure :
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();
}
renommés.
Le code suivant définit le niveau de journalisation par défaut lorsque celui-ci n’est pas
défini dans la configuration :
C#
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#
ノ Expand table
Catégorie Notes
{
"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 :
Console
AzureAppServicesFile et AzureAppServicesBlob
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);
}
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
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 .
/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.
Utilisez les outils dotnet trace pour collecter une trace à partir d’une application :
CLI .NET
dotnet trace ps
CLI .NET
Sur les plateformes autres que Windows, ajoutez l’option -f speedscope pour
remplacer le format du fichier de trace de sortie par speedscope .
ノ Expand table
Mot Description
clé
0 LogAlways
1 Critical
2 Error
3 Warning
4 Informational
5 Verbose
ノ Expand table
Trace 0
Debug 1
Information 2
Warning 3
Error 4
Critical 5
ノ Expand table
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.
CLI .NET
La commande précédente :
CLI .NET
La commande précédente :
CLI .NET
CLI .NET
CLI .NET
4. Arrêtez les outils de trace dotnet en appuyant sur la touche Entrée ou Ctrl + C .
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.
JSON
"Logging": {
"EventLog": {
"LogLevel": {
"Default": "Information"
}
}
}
Le code suivant remplace la valeur par défaut de SourceName ( ".NET Runtime" ) par
MyLogs :
C#
Le fournisseur de package n’est pas inclus dans le framework partagé. Pour utiliser le
fournisseur, ajoutez le package du fournisseur au projet.
C#
using Microsoft.Extensions.Logging.AzureAppServices;
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 .
Serveur d'applications
Serveur web
Suivi des demandes ayant échoué
Accédez à la page Streaming des journaux pour afficher les journaux. Les messages
journalisés le sont avec l’interface ILogger .
L’utilisation d’un framework tiers est semblable à l’utilisation des fournisseurs intégrés :
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 :
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;
de journalisation Debug . Le filtre est appliqué à tous les fournisseurs, car aucun
fournisseur spécifique n’a été configuré.
ActivityTrackingOptions.
C#
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.Run();
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
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 :
2 Avertissement
C#
app.UseHttpLogging();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.Run();
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
C#
using Microsoft.AspNetCore.HttpLogging;
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;
});
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHttpLogging();
app.Run();
7 Notes
LoggingFields
| ResponsePropertiesAndHeaders.
RequestHeaders et ResponseHeaders
C#
using Microsoft.AspNetCore.HttpLogging;
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;
});
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHttpLogging();
await next();
});
app.Run();
MediaTypeOptions
C#
using Microsoft.AspNetCore.HttpLogging;
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;
});
app.UseStaticFiles();
app.UseHttpLogging();
await next();
});
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;
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;
});
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseHttpLogging();
await next();
});
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;
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();
await next();
});
app.Run();
C#
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 :
C#
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.Duration;
});
builder.Services.AddHttpLoggingInterceptor<SampleHttpLoggingInterceptor>();
C#
using Microsoft.AspNetCore.HttpLogging;
namespace HttpLoggingSample;
// 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;
}
if (logContext.TryDisable(HttpLoggingFields.ResponseHeaders))
{
RedactResponseHeaders(logContext);
}
EnrichResponse(logContext);
return default;
}
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
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 :
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
Activer W3CLogger
Activez W3CLogger avec UseW3CLogging, qui ajoute le middleware W3CLogger :
C#
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#
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.
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.
HealthStatus.Unhealthy.
MapHealthChecks.
C#
builder.Services.AddHealthChecks();
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
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 .
C#
// ...
if (isHealthy)
{
return Task.FromResult(
HealthCheckResult.Healthy("A healthy result."));
}
return Task.FromResult(
new HealthCheckResult(
context.Registration.FailureStatus, "An unhealthy
result."));
}
}
retourné.
builder.Services.AddHealthChecks()
.AddCheck<SampleHealthCheck>("Sample");
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."));
C#
C#
builder.Services.AddHealthChecks()
.AddTypeActivatedCheck<SampleHealthCheckWithArgs>(
"Sample",
failureStatus: HealthStatus.Degraded,
tags: new[] { "sample" },
args: new object[] { 1, "Arg" });
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
Pour éviter l’usurpation d’hôte ou de port, utilisez l’une des approches suivantes :
C#
app.MapHealthChecks("/healthz")
.RequireHost("*:5001")
.RequireAuthorization();
Pour plus d’informations, consultez Correspondance de l’hôte dans les itinéraires avec
RequireHost.
C#
app.MapHealthChecks("/healthz")
.RequireAuthorization();
L’exemple suivant filtre les contrôles d’intégrité afin d’exécuter uniquement ceux qui
comportent la balise sample :
C#
C#
C#
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#
C#
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.
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#
7 Notes
AspNetCore.Diagnostics.HealthChecks n’est pas géré ni pris en charge par
Microsoft.
Par défaut :
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 .
C#
builder.Services.AddDbContext<SampleDbContext>(options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddHealthChecks()
.AddDbContextCheck<SampleDbContext>();
C#
_healthCheck.StartupCompleted = true;
}
}
C#
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" });
C#
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.
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.
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
C#
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 ,
C#
public static class SampleHealthCheckBuilderExtensions
{
private const string DefaultName = "Sample";
C#
return Task.CompletedTask;
}
}
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 :
C#
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)
});
app.MapHealthChecks("/healthz");
app.Run();
ê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 .
C#
if (isHealthy)
{
return Task.FromResult(
HealthCheckResult.Healthy("A healthy result."));
}
return Task.FromResult(
new HealthCheckResult(
context.Registration.FailureStatus, "An unhealthy
result."));
}
}
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:
MapHealthChecks autorise :
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 .
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 :
Conseil
Consultez Métriques ASP.NET Core pour obtenir la liste complète de tous les
instruments avec leurs attributs.
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.
CLI .NET
C#
using OpenTelemetry.Metrics;
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.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.
CLI .NET
CLI .NET
CLI .NET
[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
C#
using Microsoft.AspNetCore.Http.Features;
await next.Invoke();
});
app.Run();
L’exemple suivant :
7 Notes
Pour utiliser IMeterFactory dans une application, créez un type qui servira
IMeterFactory à créer les métriques personnalisées de l'application :
C#
C#
C#
metrics.ProductSold(model.ProductName, model.QuantitySold);
});
Pour surveiller le compteur « Contoso.Web », utilisez la commande dotnet-counters
suivante.
CLI .NET
CLI .NET
[Contoso.Web]
contoso.product.sold (Count / 1 sec)
contoso.product.name=Eggs 12
contoso.product.name=Milk 0
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 :
4. Un serveur Prometheus :
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.
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"
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.
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.
C#
[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 :
Ouvrir un problème de
documentation
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
ノ Agrandir le tableau
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.
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 ].
Métrique : http.server.active_requests
ノ Agrandir le tableau
ノ Agrandir le tableau
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
ノ Agrandir le tableau
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
ノ Agrandir le tableau
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
Métrique : aspnetcore.rate_limiting.request_lease.duration
ノ Agrandir le tableau
ノ Agrandir le tableau
Métrique : aspnetcore.rate_limiting.queued_requests
ノ Agrandir le tableau
ノ Agrandir le tableau
Métrique : aspnetcore.rate_limiting.request.time_in_queue
ノ Agrandir le tableau
ノ Agrandir le tableau
Métrique : aspnetcore.rate_limiting.requests
ノ Agrandir le tableau
ノ Agrandir le tableau
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
ノ Agrandir le tableau
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
ノ Agrandir le tableau
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
ノ Agrandir le tableau
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.
Métrique : kestrel.connection.duration
ノ Agrandir le tableau
ノ Agrandir le tableau
É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].
Métrique : kestrel.rejected_connections
ノ Agrandir le tableau
ノ Agrandir le tableau
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.
Les connexions sont rejetées lorsque le nombre actuellement actif dépasse la valeur configurée avec
MaxConcurrentConnections .
ノ Agrandir le tableau
ノ Agrandir le tableau
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.
Métrique : kestrel.queued_requests
ノ Agrandir le tableau
ノ Agrandir le tableau
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.
Métrique : kestrel.upgraded_connections
ノ Agrandir le tableau
ノ Agrandir le tableau
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.
Métrique : kestrel.tls_handshake.duration
ノ Agrandir le tableau
Nom Type Unité Description
d’instrument (UCUM)
ノ Agrandir le tableau
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 ].
Métrique : kestrel.active_tls_handshakes
ノ Agrandir le tableau
ノ Agrandir le tableau
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.
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
ノ Agrandir le tableau
ノ Agrandir le tableau
Valeur Description
ノ Agrandir le tableau
Valeur Protocole
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].
Métrique : signalr.server.active_connections
ノ Agrandir le tableau
ノ Agrandir le tableau
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.
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.
ノ Agrandir le tableau
C#
app.Run();
app.Run();
HttpRequest.Body peut être lu directement ou utilisé avec d’autres API qui acceptent le
flux.
7 Notes
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#
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.
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.
ノ Agrandir le tableau
Propriété Description Exemple
C#
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 :
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.
C#
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.
Pour plus d’informations sur l’écriture de contenu dans BodyWriter , consultez PipeWriter
pour les pipelines d’E/S.
C#
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#
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
7 Notes
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.
C#
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#
app.Run();
7 Notes
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 :
C#
app.Run();
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;
builder.Services.AddHttpContextAccessor();
builder.Services.AddHostedService<PeriodicBranchesLoggerService>();
builder.Services.AddTransient<UserAgentHeaderHandler>();
if (!httpResponseMessage.IsSuccessStatusCode)
return Results.BadRequest();
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;
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>();
if (!httpResponseMessage.IsSuccessStatusCode)
return Results.BadRequest();
return Results.Ok(response);
});
app.Run();
UserAgentHeaderHandler :
C#
using Microsoft.Net.Http.Headers;
namespace HttpContextInBackgroundThread;
if (string.IsNullOrEmpty(userAgentString))
{
userAgentString = "Unknown";
}
request.Headers.Add(HeaderNames.UserAgent, userAgentString);
_logger.LogInformation($"User-Agent: {userAgentString}");
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 :
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.
C#
using System.Text.Json;
namespace HttpContextInBackgroundThread;
public PeriodicBranchesLoggerService(IHttpClientFactory
httpClientFactory,
ILogger<PeriodicBranchesLoggerService> logger)
{
_httpClientFactory = httpClientFactory;
_logger = logger;
_timer = new PeriodicTimer(TimeSpan.FromSeconds(30));
}
stoppingToken);
if (httpResponseMessage.IsSuccessStatusCode)
{
await using var contentStream =
await
httpResponseMessage.Content.ReadAsStreamAsync(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!");
}
}
}
C#
using System.Text.Json;
using HttpContextInBackgroundThread;
using Microsoft.Net.Http.Headers;
builder.Services.AddHttpContextAccessor();
builder.Services.AddHostedService<PeriodicBranchesLoggerService>();
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.
C#
app.Run();
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é.
C#
app.UseRouting();
Points de terminaison
La méthode MapGet est utilisée pour définir un point de terminaison. Un point de
terminaison peut être :
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 :
C#
C#
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
app.MapGet("/", () => "Hello World!");
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.
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.
C#
if (currentEndpoint is null)
{
await next(context);
return;
}
Console.WriteLine($"Endpoint: {currentEndpoint.DisplayName}");
await next(context);
});
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.
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#
app.UseRouting();
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.
txt
1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello
txt
1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)
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 conçu pour être utilisé en tandem avec l’intergiciel
UseRouting . La logique principale pour exécuter un point de terminaison n’est pas
C#
app.UseHttpMethodOverride();
app.UseRouting();
await next(context);
});
C#
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 .
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 :
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.
C#
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 :
Il est appelé intergiciel de terminal, car il met fin à la recherche, exécute certaines
fonctionnalités, puis retourne.
C#
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz").RequireAuthorization();
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.
2 Avertissement
Le type RouteContext sera marqué comme obsolète dans une version ultérieure :
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} :
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} :
2 Avertissement
É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.
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.
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 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
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é.
GetPathByAddress Génère un URI avec un chemin absolu basé sur les valeurs fournies.
2 Avertissement
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#
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 }} .
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 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
/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.
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
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.
C#
[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
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.
présente pendant la
génération de l’URL
2 Avertissement
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.
2 Avertissement
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.
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" });
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.
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$
Pour plus d’informations sur la syntaxe des expressions régulières, consultez Expressions
régulières du .NET Framework.
C#
builder.Services.AddRouting(options =>
options.ConstraintMap.Add("noZeroes", typeof(NoZeroesRouteConstraint)));
C#
[ApiController]
[Route("api/[controller]")]
public class NoZeroesController : ControllerBase
{
[HttpGet("{id:noZeroes}")]
public IActionResult Get(string id) =>
Content(id);
}
C#
public class NoZeroesRouteConstraint : IRouteConstraint
{
private static readonly Regex _regex = new(
@"^[1-9]*$",
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
TimeSpan.FromMilliseconds(100));
if (routeValueString is null)
{
return false;
}
return _regex.IsMatch(routeValueString);
}
}
2 Avertissement
Le code précédent :
C#
[HttpGet("{id}")]
public IActionResult Get(string id)
{
if (id.Contains('0'))
{
return StatusCode(StatusCodes.Status406NotAcceptable);
}
return Content(id);
}
C#
return Regex.Replace(
value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100))
.ToLowerInvariant();
}
}
C#
builder.Services.AddRouting(options =>
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
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.
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.
Les adresses sont un concept extensible qui comprend deux implémentations par défaut
:
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 :
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#
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#
C#
Le code suivant fournit au contrôleur des valeurs ambiantes dans la requête actuelle et
des valeurs explicites :
C#
Le code suivant fournit des valeurs ambiantes à partir de la requête actuelle et des
valeurs explicites :
C#
// ...
}
}
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 .
compatibilité.
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 :
routage. Pour les routages conventionnels dédiés et les routes d’attribut aux contrôleurs
et à Razor Pages :
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.
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.
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?} .
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 ?? ""}");
}
}
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 :
C#
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}", Name = nameof(GetProduct))]
public IActionResult GetProduct(string id)
{
// ...
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"];
// ...
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.
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.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();
}
2 Avertissement
Pour éviter l’usurpation d’hôte ou de port, utilisez l’une des approches suivantes :
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;
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#
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#
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#
C#
CLI .NET
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#
app.UseRouting();
logger.LogInformation("Time 2: {ElapsedMilliseconds}ms",
stopwatch.ElapsedMilliseconds);
});
app.UseAuthorization();
logger.LogInformation("Time 3: {ElapsedMilliseconds}ms",
stopwatch.ElapsedMilliseconds);
});
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#
_logger.LogInformation("{Message}: {ElapsedMilliseconds}ms",
_message, _stopwatch.ElapsedMilliseconds);
_disposed = true;
}
}
C#
app.UseRouting();
app.UseAuthorization();
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.
Plusieurs techniques et optimisations peuvent être appliquées aux routes qui améliorent
en grande partie ce scénario :
C#
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.
JSON
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
app.UseHttpLogging();
app.Run();
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
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.
C#
// Your framework
app.MapMyFramework(...);
app.MapHealthChecks("/healthz");
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");
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#
[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 :
La déclaration d’un type de métadonnées en tant qu’interface ajoute une autre couche
de flexibilité :
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() { }
}
C#
// Your framework
app.MapMyFramework(...).RequireAuthorization();
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)
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.
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.
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 :
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.
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.
C#
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.
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#
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é.
Le code suivant utilise une expression lambda pour la gestion des exceptions :
C#
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler(exceptionHandlerApp =>
{
exceptionHandlerApp.Run(async context =>
{
context.Response.StatusCode =
StatusCodes.Status500InternalServerError;
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.
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);
}
}
}
C#
using ErrorHandlingSample;
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
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#
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStatusCodePages();
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
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.
C#
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
Dans le code précédent, {0} est un espace réservé pour le code d’erreur.
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#
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
await statusCodeContext.HttpContext.Response.WriteAsync(
$"Status Code Page:
{statusCodeContext.HttpContext.Response.StatusCode}");
});
production, car il retourne un message qui n’est pas utile pour les utilisateurs.
UseStatusCodePagesWithRedirects
La méthode d’extension UseStatusCodePagesWithRedirects :
C#
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.
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 :
C#
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.
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#
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#
var statusCodeReExecuteFeature =
HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
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#
En-têtes de réponse
Une fois les en-têtes d’une réponse envoyés :
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.
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.
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
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.
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 ) :
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();
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.
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.
C#
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é
C#
C#
builder.Services.AddTransient<IProblemDetailsWriter,
SampleProblemDetailsWriter>();
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();
}
// /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();
C#
builder.Services.AddControllers();
builder.Services.AddProblemDetails();
app.UseHttpsRedirection();
app.UseStatusCodePages();
MathErrorType.DivisionByZeroError };
context.Features.Set(errorType);
return Results.BadRequest();
}
// /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();
d’erreur.
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();
}
// /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));
}
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
);
}
// /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));
}
C#
app.UseExceptionHandler();
app.UseStatusCodePages();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.MapControllers();
app.Run();
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.
C#
using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;
builder.Services.AddControllers();
builder.Services.AddProblemDetails();
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;
if (context.RequestServices.GetService<IProblemDetailsService>()
is
{ } problemDetailsService)
{
var exceptionHandlerFeature =
context.Features.Get<IExceptionHandlerFeature>();
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.
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 :
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
C#
C#
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
Clients nommés
Les clients nommés sont un bon choix dans les cas suivants :
C#
// 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");
});
CreateClient
Chaque fois que CreateClient est appelé :
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
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é :
// 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");
}
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
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 :
C#
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.
C#
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");
});
C#
POST
PUT
DELETE
PATCH
Pour obtenir la liste complète des verbes HTTP pris en charge, consultez HttpMethod.
C#
httpResponseMessage.EnsureSuccessStatusCode();
}
C#
httpResponseMessage.EnsureSuccessStatusCode();
}
C#
httpResponseMessage.EnsureSuccessStatusCode();
}
Pour en savoir plus sur l’utilisation de différents verbes HTTP avec HttpClient , consultez
HttpClient.
Dérivez de DelegatingHandler.
Remplacez SendAsync. Exécutez du code avant de passer la requête au
gestionnaire suivant dans le pipeline :
C#
C#
builder.Services.AddTransient<ValidateHeaderHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<ValidateHeaderHandler>();
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>();
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#
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>();
C#
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 :
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 .
HttpRequestException
HTTP 5xx
HTTP 408
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.
C#
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é.
C#
builder.Services.AddHttpClient("PollyMultiple")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.RetryAsync(3))
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
C#
policyRegistry.Add("Regular", timeoutPolicy);
policyRegistry.Add("Long", longTimeoutPolicy);
builder.Services.AddHttpClient("PollyRegistryRegular")
.AddPolicyHandlerFromRegistry("Regular");
builder.Services.AddHttpClient("PollyRegistryLong")
.AddPolicyHandlerFromRegistry("Long");
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 :
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.
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.
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 :
C#
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});
Microsoft.Extensions.Hosting
Microsoft.Extensions.Http
IHttpClientFactory .
GitHubService utilise IHttpClientFactory pour créer une instance de HttpClient ,
C#
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
try
{
var gitHubService = host.Services.GetRequiredService<GitHubService>();
var gitHubBranches = await
gitHubService.GetAspNetCoreDocsBranchesAsync();
httpResponseMessage.EnsureSuccessStatusCode();
C#
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddHttpClient("PropagateHeaders")
.AddHeaderPropagation();
builder.Services.AddHeaderPropagation(options =>
{
options.Headers.Add("X-TraceId");
});
app.UseHeaderPropagation();
app.MapControllers();
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
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.
C#
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
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
C#
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
HTML
wwwroot
css
images
js
MyStaticFiles
images
red-rose.jpg
C#
using Microsoft.Extensions.FileProviders;
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
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();
HTML
Pour délivrer des fichiers à partir de plusieurs emplacements, consultez Délivrer des
fichiers à partir de plusieurs emplacements.
C#
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
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).
C#
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileProviders;
using StaticFileAuth.Data;
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();
});
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.
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
Une autre approche pour traiter les fichiers en fonction de l’autorisation consiste à :
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;
C#
app.MapGet("/files/{fileName}", IResult (string fileName) =>
{
var filePath = GetOrCreateFilePath(fileName);
if (File.Exists(filePath))
{
return TypedResults.PhysicalFile(filePath, fileDownloadName: $"
{fileName}");
}
// 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
context.Response.Headers.Append("Location",
linker.GetPathByName(context, "GetFileByName", new { fileName =
fileSaveName}));
return TypedResults.Ok("File Uploaded Successfully!");
})
.RequireAuthorization("AdminsOnly");
app.Run();
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.
C#
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
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 :
applications.
C#
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
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.
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é.
C#
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapDefaultControllerRoute();
app.MapRazorPages();
app.Run();
C#
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
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#
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();
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();
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;
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddDirectoryBrowser();
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();
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
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;
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
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();
Le code suivant permet de délivrer des types inconnus et rend le fichier inconnu en tant
qu’image :
C#
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
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
CSHTML
@page
C#
Le code suivant met à jour WebRootFileProvider , qui permet au Tag Helper d’image de
fournir une version :
C#
app.UseStaticFiles();
2 Avertissement
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 :
2 Avertissement
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.
à 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.
Avec le fichier Program.cs mis à jour suivant qui définit WebRootPath = "wwwroot-
custom" :
C#
app.UseDefaultFiles();
app.UseStaticFiles();
app.Run();
Pour vous assurer que les ressources à partir de wwwroot-custom sont retournées, utilisez
l’une des approches suivantes :
XML
<ItemGroup>
<Content Remove="wwwroot\**" />
</ItemGroup>
C#
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
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.
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.
Inconvénients :
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 :
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.
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
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();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
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
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#";
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 :
/Pages/Index.cshtml / ou /Index
/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml /Store/Contact
Nom et chemin de fichier URL correspondante
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.
C#
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseInMemoryDatabase("name"));
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; }
}
}
C#
using Microsoft.EntityFrameworkCore;
namespace RazorPagesContacts.Data
{
public class CustomerDbContext : DbContext
{
public CustomerDbContext (DbContextOptions<CustomerDbContext>
options)
: base(options)
{
}
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>
C#
[BindProperty]
public Customer? Customer { get; set; }
return RedirectToPage("./Index");
}
}
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
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 :
C#
[BindProperty]
public Customer? Customer { get; set; }
return RedirectToPage("./Index");
}
CSHTML
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>
HTML
<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>
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; }
return RedirectToPage("./Index");
}
C#
[BindProperty]
public Customer? Customer { get; set; }
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)]
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>
La page d’accueil
Index.cshtml est la page d’accueil :
CSHTML
@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
C#
if (contact != null)
{
_context.Customer.Remove(contact);
await _context.SaveChangesAsync();
}
return RedirectToPage();
}
}
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.
CSHTML
Le HTML rendu :
HTML
Quand le bouton Delete est rendu en HTML, son action de formulaire inclut des
paramètres pour :
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#
if (contact != null)
{
_context.Customer.Remove(contact);
await _context.SaveChangesAsync();
}
return RedirectToPage();
}
La méthode OnPostDeleteAsync :
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");}
}
CSHTML
@page "{id:int?}"
Le fichier Edit.cshtml.cs :
C#
[BindProperty]
public Customer? Customer { get; set; }
if (Customer == null)
{
return NotFound();
}
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");
}
Validation
Les règles de validation :
C#
using System.ComponentModel.DataAnnotations;
namespace RazorPagesContacts.Models
{
public class Customer
{
public int Id { get; set; }
[Required, StringLength(10)]
public string? Name { get; set; }
}
}
CSHTML
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<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 :
HTML
<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.
longueur maximale spécifiée. Si un outil comme Fiddler est utilisé pour modifier et
refaire la publication :
C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { 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 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.
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 :
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 .
css
h1 {
color: red;
}
HTML
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
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>
XML
<ItemGroup>
<None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-
identifier" />
</ItemGroup>
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>
XML
<ItemGroup>
<None Update="{Pages|Views}/*.cshtml.css" CssScope="custom-scope-
identifier" />
</ItemGroup>
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>
{STATIC WEB ASSET BASE PATH} : le chemin de base de la ressource web statique.
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.
HTML
Pour plus d’informations sur les bibliothèques de classes Razor, consultez les articles
suivants :
Pour plus d’informations sur l’isolation CSS de Blazor, consultez Isolation CSS Blazor
d’ASP.NET Core.
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.
Nous allons nettoyer un peu cette page en tirant parti de certaines de ces
fonctionnalités.
<!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 :
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.
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.
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.
C#
namespace RazorPagesContacts.Pages
{
public class EditModel : PageModel
{
private readonly AppDbContext _db;
CSHTML
@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
CSHTML
@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<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>
CSHTML
@page
@model CreateModel
<form method="post">
Name:
<input asp-for="Customer!.Name" />
<input type="submit" />
</form>
Pour plus d’informations sur les vues partielles, consultez Vues partielles dans ASP.NET
Core.
C#
[BindProperty]
public Customer? Customer { get; set; }
return RedirectToPage("./Index");
}
}
Pages/
Index.cshtml
Privacy.cshtml
/Customers
Create.cshtml
Edit.cshtml
Index.cshtml
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
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 :
Pour rediriger vers une page située dans une autre Zone, spécifiez la zone :
C#
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.
C#
Dans la page À propos de, accédez à la propriété Title en tant que propriété de
modèle :
CSHTML
<h1>@Model.Title</h1>
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.
C#
[TempData]
public string Message { get; set; }
[BindProperty]
public Customer Customer { get; set; }
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
Message = $"Customer {Customer.Name} added";
return RedirectToPage("./Index");
}
}
CSHTML
<h3>Msg: @Model.Message</h3>
C#
[TempData]
public string Message { get; set; }
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
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;
[BindProperty]
public Customer Customer { get; set; }
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
CSHTML
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>
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 .
razor
@section Scripts {
<script src="~/Pages/Index.cshtml.js"></script>
}
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
razor
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 :
C#
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"));
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.
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"));
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"));
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
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.
À 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
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
Sélectionnez Suivant.
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
Visual Studio affiche la boîte de dialogue suivante lorsqu’un projet n’est pas encore
configuré pour utiliser SSL :
Visual Studio :
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#
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#
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.
C#
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.
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.
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.
C#
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models;
Visual Studio
Visual Studio
C#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Visual Studio
PowerShell
Add-Migration InitialCreate
Update-Database
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()’.
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)
{
}
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.
Tester l’application
1. Exécutez l’application et ajoutez /Movies à l’URL dans le navigateur
( http://localhost:port/movies ).
Console
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 .
Visual Studio
C#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);
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();
Étapes suivantes
Précédent : Bien démarrer
Ouvrir un problème de
documentation
Ce tutoriel décrit les pages Razor créées par génération de modèles automatique dans
le tutoriel précédent.
C#
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies;
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#
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;
C#
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
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>
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
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.
CSHTML
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 .
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";
}
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 .
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" />
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>
CSHTML
CSHTML
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.
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.
CSHTML
@{
Layout = "_Layout";
}
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;
}
[BindProperty]
public Movie Movie { get; set; } = default!;
_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 méthode OnPostAsync est exécutée quand la page publie les données de formulaire :
C#
_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 :
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">
CSHTML
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
Visual Studio
C#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
Visual Studio
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
3. Faites un clic droit sur la table Movie et sélectionnez Afficher les données :
C#
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
namespace RazorPagesMovie.Models;
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;
}
Visual Studio
C#
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
using RazorPagesMovie.Models;
builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
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 :
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 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.
C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models;
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
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.
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 .
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
CSHTML
@page "{id:int?}"
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();
}
if (Movie == null)
{
return NotFound();
}
return Page();
}
C#
_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);
}
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.
C#
[BindProperty]
public Movie Movie { get; set; } = default!;
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
Quand une requête HTTP GET est effectuée sur la page Movies/Edit, par exemple,
https://localhost:5001/Movies/Edit/3 :
sur la page.
Le formulaire d’édition s’affiche avec les valeurs relatives au film.
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.
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
Ouvrir un problème de
documentation
Dans les sections suivantes, la recherche de films par genre ou par nom est ajoutée.
C#
[BindProperty(SupportsGet = true)]
public string? SearchString { get; set; }
[BindProperty(SupportsGet = true)]
public string? MovieGenre { get; set; }
SearchString : Contient le texte que les utilisateurs entrent dans la zone de texte
« 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)]
C#
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?
CSHTML
@page "{searchString?}"
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.
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.*@
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
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;
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#
C#
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
Dans cette section, Migrations Entity Framework Code First est utilisé pour :
Quand vous utilisez EF Code First pour créer et suivre automatiquement une base de
données, Code First :
C#
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>
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 :
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.
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"
},
Générez la solution.
Visual Studio
PowerShell
Add-Migration Rating
Update-Database
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.
4. Sélectionnez OK.
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
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 à :
La prise en charge de la validation fournie par les Pages Razor et Entity Framework est
un bon exemple du principe DRY :
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;
[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;
}
RegularExpression Rating :
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 .
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.
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 :
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.
C#
if (!ModelState.IsValid)
{
return Page();
}
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.
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; }
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.
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.
C#
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.
C#
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.
C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models;
[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.
C#
SQL
Visual Studio
PowerShell
Add-Migration New_DataAnnotations
Update-Database
Examinez la méthode Up :
C#
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)");
}
SQL
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
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é
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.
C#
"SampleAsyncPageFilter.OnPageHandlerSelectionAsync",
value, key.ToString());
return Task.CompletedTask;
}
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.
C#
C#
C#
C#
C#
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();
}
}
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;
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 court-circuitage du pipeline de filtres à partir d’un
filtre, consultez Annulation et court-circuit.
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();
}
}
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.
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#
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
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.
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.
Conventions de modèle
Ajoutez un délégué pour IPageConvention afin d’ajouter des conventions de modèles
qui s’appliquent à Razor Pages.
C#
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace SampleApp.Conventions;
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;
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?}"),
}
});
}
});
});
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
C#
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace SampleApp.Conventions;
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 .
propriété Order .
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 .
CLI .NET
info: SampleApp.Pages.AboutModel[0]
/About/GlobalRouteValue Order = 1 Template =
About/{globalTemplate?}
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.
C#
Program.cs :
C#
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("InMemoryDb"));
builder.Services.AddRazorPages(options =>
{
options.Conventions.Add(new
GlobalTemplatePageRouteModelConvention());
options.Conventions.Add(new
GlobalHeaderPageApplicationModelConvention());
C#
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é.
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é.
C#
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é.
CLI .NET
info: SampleApp.Pages.AboutModel[0]
/About/GlobalRouteValue/AboutRouteValue Order = 2 Template =
About/{globalTemplate?}/{aboutTemplate?}
C#
options.Conventions.AddPageRoute("/Contact", "TheContactPage/{text?}");
La page Contact est également accessible sur / Contact1` via son routage par défaut.
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:[email protected]">[email protected]</a><br>
<strong>Marketing:</strong> <a
href="mailto:[email protected]">[email protected]</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 :
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#
C#
options.Conventions.AddFolderApplicationModelConvention("/OtherPages", model
=>
{
model.Filters.Add(new AddHeaderAttribute(
"OtherPagesHeader", new string[] { "OtherPages Header Value" }));
});
C#
Configurer 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 .
ConfigureFilter configure la fabrique spécifiée pour appliquer des filtres à toutes les
pages Razor.
options.Conventions.ConfigureFilter(new AddHeaderWithFactory());
AddHeaderWithFactory.cs :
C#
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
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 :
7 Notes
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 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#
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#
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; }
Action du contrôleur :
C#
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.
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.
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.
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>
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
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.
Prérequis
Visual Studio
Visual Studio utilise le modèle de projet par défaut pour le projet MVC créé. Le
projet créé :
Exécuter l'application
Visual Studio
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.
Modifiez le code.
Enregistrez le fichier.
Actualiser rapidement le navigateur et voir les modifications de code.
Visual Studio
Dans le prochain didacticiel de la série, vous allez découvrir MVC et commencer à écrire
du code.
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 :
Privacy .
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.
Dans l’Explorateur de solutions, cliquez avec le bouton droit sur Contrôleurs >
Ajouter > Contrôleur.
C#
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers;
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.
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 :
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 :
C#
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
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 :
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#
Le code précédent :
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 .
Cliquez avec le bouton droit sur le dossier Vues/HelloWorld, puis cliquez sur Ajouter
> Nouvel élément.
CSHTML
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>Hello from our View Template!</p>
Accédez à https://localhost:{PORT}/HelloWorld :
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 :
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 .
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>
Remarque : Le contrôleur Movies n’a pas été implémenté. À ce stade, le lien Movie App
ne fonctionne pas.
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.
CSHTML
@{
Layout = "_Layout";
}
CSHTML
@{
ViewData["Title"] = "Movie List";
}
CSHTML
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
Les contrôleurs sont chargés de fournir les données nécessaires pour qu’un modèle de
vue restitue une réponse.
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.
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;
L’objet dictionnaire ViewData contient des données qui seront passées à la vue.
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>
https://localhost:{PORT}/HelloWorld/Welcome?name=Rick&numtimes=4
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 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.
C#
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models;
La classe Movie contient un champ Id , qui est nécessaire à la base de données pour la
clé primaire.
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.
Visual Studio
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.VisualStudio.Web.CodeGeneration.Design
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.
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
PowerShell
Add-Migration InitialCreate
Update-Database
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 .
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 ».
Tester l’application
Visual Studio
Si vous obtenez une exception semblable à ce qui suit, vous avez peut-être manqué
la commande Update-Database à l’étape des migrations :
Console
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.
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)
{
}
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.
Visual Studio
C#
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));
Visual Studio
JSON
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Data Source=MvcMovieContext-ea7a4069-f366-4742-
bd1c-3f753a804ce1.db"
}
}
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);
});
}
migration Up .
C#
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 .
C#
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == 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 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.
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);
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 .
C#
// GET: Movies
public async Task<IActionResult> Index()
{
return View(await _context.Movie.ToListAsync());
}
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
Visual Studio
C#
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));
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 :
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;
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.
}
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;
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));
SeedData.Initialize(services);
}
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.
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.
C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
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.
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>
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
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();
}
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.
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.
C#
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
@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 .
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
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.
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);
}
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
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.
C#
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
La ligne suivante dans la méthode d’action Index crée une requête LINQ pour
sélectionner les films :
C#
La requête est seulement définie à ce stade ; elle n’a pas été exécutée sur la base de
données.
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
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.
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
C#
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
C#
if (!String.IsNullOrEmpty(id))
{
movies = movies.Where(s => s.Title!.Contains(id));
}
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#
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
CSHTML
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
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.
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.
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.
CSHTML
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
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
C#
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace MvcMovie.Models;
SearchString , qui contient le texte que les utilisateurs entrent dans la zone de
texte de recherche.
C#
// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string
searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
if (!string.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
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#
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).
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>
<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>
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
Dans cette section, Migrations Entity Framework Code First est utilisé pour :
Quand EF Code First est utilisé pour créer automatiquement une base de données, Code
First :
C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
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.
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 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 :
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.)
3. Utilisez les migrations Code First pour mettre à jour le schéma de base de
données.
Visual Studio
PowerShell
Add-Migration Rating
Update-Database
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.
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
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.
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;
[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.
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.
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.
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);
}
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.
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>
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.
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.
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#
[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.
C#
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.).
7 Notes
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
C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
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
C#
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
return View(movie);
}
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
pas un film réel). Si vous avez recherché un film null, l’application lève une exception.
C#
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == 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)
Précédent
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.
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.
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>
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.
HomeController.cs :
C#
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 :
C#
return View("Orders");
C#
return View(Orders);
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
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.
ViewBag
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.
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>
C#
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.
ViewBag n’est pas disponible par défaut pour une utilisation dans les pages
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.
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
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#
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#
return View();
}
}
<!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
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#
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
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.
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.">
...
ViewData
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
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.
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 .
css
h1 {
color: red;
}
HTML
HTML
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>
Si d’autres projets sont utilisés, comme des packages NuGet ou des bibliothèques de
classes Razor, le fichier regroupé :
XML
<ItemGroup>
<None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-
identifier" />
</ItemGroup>
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>
XML
<ItemGroup>
<None Update="{Pages|Views}/*.cshtml.css" CssScope="custom-scope-
identifier" />
</ItemGroup>
XML
<PropertyGroup>
<StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>
XML
<PropertyGroup>
<DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>
{STATIC WEB ASSET BASE PATH} : le chemin de base de la ressource web statique.
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.
HTML
Pour plus d’informations sur les bibliothèques de classes Razor, consultez les articles
suivants :
Pour plus d’informations sur l’isolation CSS de Blazor, consultez Isolation CSS Blazor
d’ASP.NET Core.
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.
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
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.
C#
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#
Le Tag Helper Partial effectue un rendu asynchrone et utilise une syntaxe de type HTML :
CSHTML
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
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
MVC
CSHTML
CSHTML
Pour plus d’informations, consultez assistance des balises partielle dans ASP.NET Core.
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");
}
) Important
Si vous devez exécuter du code, utilisez un composant de vue au lieu d’une vue
partielle.
Razor Pages
MVC
1. /Areas/<Area-Name>/Views/<Controller-Name>
2. /Areas/<Area-Name>/Views/Shared
3. /Views/Shared
4. /Pages/Shared
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.
CSHTML
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
Razor Pages
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;
index++;
}
}
CSHTML
@model string
<div>
<h3>@Model</h3>
This partial view from /Pages/Shared/_AuthorPartialRP.cshtml.
</div>
CSHTML
@using PartialViewsSample.ViewModels
@model ArticleSection
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
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;
index++;
}
}
CSHTML
@model string
<div>
<h3>@Model</h3>
This partial view from /Views/Shared/_AuthorPartial.cshtml.
</div>
CSHTML
@using PartialViewsSample.ViewModels
@model ArticleSection
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
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
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.
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.
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,
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.
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 :
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 :
builder.Services.AddControllersWithViews();
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?}");
C#
public class ProductsController : Controller
{
public IActionResult Details(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
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#
/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.
C#
app.MapDefaultControllerRoute();
Remplace :
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
) Important
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 id paramètre à 17.
Ce mappage :
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 :
MapControllerRoute et MapAreaRoute :
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 :
Étant donné que controller et action n’apparaissent pas dans le modèle de route
"blog/{*article}" en tant que paramètres :
L’exemple précédent :
blog la route a une priorité plus élevée pour les correspondances que la
2 Avertissement
Par exemple :
C#
[HttpPost]
public IActionResult Edit(int id, Product product)
{
return ControllerContext.MyDisplayRouteInfo(id, product.name);
}
}
Edit(int) est sélectionné lorsque le verbe HTTP est autre chose. Edit(int) est
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.
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.
Sont interchangeables.
Celui qui est utilisé dans la documentation et le code dépend de l’API décrite.
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#
builder.Services.AddControllers();
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.
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 .
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#
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult MyAbout(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
C#
[Route("[controller]/[action]")]
public IActionResult About()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
C#
[Route("[controller]/[action]")]
public class HomeController : Controller
{
[Route("~/")]
[Route("/Home")]
[Route("~/Home/Index")]
public IActionResult Index()
{
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.
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#
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.
[HttpGet]
[HttpPost]
[HttpPut]
[HttpDelete]
[HttpHead]
[HttpPatch]
Modèles de route
ASP.NET Core a les modèles d’itinéraire suivants :
C#
[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
[HttpGet] // GET /api/test2
public IActionResult ListProducts()
{
return ControllerContext.MyDisplayRouteInfo();
}
C#
C#
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 :
Retourne une requête incorrecte 400 , car la liaison de données n’a pas pu
convertir abc en entier.
C#
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);
}
}
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) :
Consultez Routage pour obtenir une description complète des modèles de routes et des
options associées.
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 :
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.
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);
}
}
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();
}
}
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#
[Route("Home/About")]
[Route("Home/About/{id?}")]
public IActionResult About(int? id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
C#
[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
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();
}
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 .
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);
}
}
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}
C#
[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
{
}
[HttpGet("{id}")] // /api/products11/edit/3
public IActionResult Edit(int id)
{
return ControllerContext.MyDisplayRouteInfo(id);
}
}
C#
using System.Text.RegularExpressions;
return Regex.Replace(value.ToString()!,
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
}
}
C#
C#
using Microsoft.AspNetCore.Mvc.ApplicationModels;
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();
2 Avertissement
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();
}
}
Lorsque plusieurs attributs de route qui implémentent IActionConstraint sont placés sur
une action :
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.
C#
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.
[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
// GET /api/MyTestApi
[HttpGet]
public IActionResult Get()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
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 :
C#
public class NamespaceRoutingConvention : Attribute,
IControllerModelConvention
{
private readonly string _baseNamespace;
C#
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}");
}
La méthode NamespaceRoutingConvention.Apply :
C#
using My.Application.Controllers;
builder.Services.AddControllersWithViews(options =>
{
options.Conventions.Add(
new NamespaceRoutingConvention(typeof(HomeController).Namespace!));
});
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;
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}");
}
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.
C#
[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
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 ;
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#
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 :
text
result: /UrlGeneration/Destination
[HttpGet("custom/url/to/destination")]
public IActionResult Destination()
{
return ControllerContext.MyDisplayRouteInfo();
}
}
LinkGeneratora été ajouté dans ASP.NET Core 3.0 comme alternative à IUrlHelper .
LinkGenerator offre des fonctionnalités similaires, mais plus flexibles. Chaque méthode
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 } :
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#
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 :
C#
CSHTML
<h1>Test Links</h1>
<ul>
<li><a href="@Url.RouteUrl("Destination_Route")">Test
Destination_Route</a></li>
</ul>
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.
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);
}
C#
app.MapControllerRoute(name: "blog",
pattern: "blog/{*article}",
defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
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.
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#
builder.Services.AddControllersWithViews();
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();
AddUser } . La valeur de route area est générée par une valeur par défaut pour area . La
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;
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;
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;
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;
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.
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);
}
}
}
C#
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)
JSON
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
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.
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#
C#
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#
C#
C#
using Microsoft.AspNetCore.Mvc;
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();
app.MapControllers();
app.Run();
[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");
}
}
C#
C#
services.AddControllersWithViews();
}
Configurez l'application pour lire les paramètres à partir d'un JSfichier au format ON :
C#
C#
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.
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.
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"
}
}
}
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>
CSHTML
@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
ViewData["Title"] = "Privacy MVC";
}
<h1>@ViewData["Title"]</h1>
<h2>
MyRoot:MyParent:MyChildName:
@Configuration["MyRoot:MyParent:MyChildName"]
</h2>
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>
C#
using ViewInjectSample.Helpers;
using ViewInjectSample.Infrastructure;
using ViewInjectSample.Interfaces;
using ViewInjectSample.Model.Services;
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddTransient<IToDoItemRepository, ToDoItemRepository>();
builder.Services.AddTransient<StatisticsService>();
builder.Services.AddTransient<ProfileOptionsService>();
builder.Services.AddTransient<MyHtmlHelper>();
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
C#
using System.Linq;
using ViewInjectSample.Interfaces;
namespace ViewInjectSample.Model.Services
{
public class StatisticsService
{
private readonly IToDoItemRepository _toDoItemRepository;
L’exemple affiche les données fournies par le modèle lié à la vue et le service injecté
dans la vue :
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;
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/>
C#
namespace ViewInjectSample.Model.Services;
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
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.
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.
C#
return View(model);
}
[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
});
}
Le contrôleur précédent :
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 :
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#
Les tests de la méthode HTTP POST Index du contrôleur Home s’assurent que :
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.
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
C#
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);
}
return Ok(result);
}
[HttpPost("create")]
public async Task<IActionResult> Create([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
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 :
Le mappage entre des entités de domaine et les types retournés au client peut être
effectué :
Ensuite, l’exemple d’application décrit des tests unitaires pour les méthodes d’API
Create et ForSession du contrôleur Ideas.
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);
}
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);
}
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);
}
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);
// 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.
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);
}
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 :
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);
}
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 :
C#
[HttpPost("createactionresult")]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
public async Task<ActionResult<BrainstormSession>>
CreateActionResult([FromBody]NewIdeaModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (session == null)
{
return NotFound(model.SessionId);
}
await _sessionRepository.UpdateAsync(session);
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);
// Act
var result = await controller.CreateActionResult(newIdea);
// Assert
var actionResult = Assert.IsType<ActionResult<BrainstormSession>>
(result);
Assert.IsType<NotFoundObjectResult>(actionResult.Result);
}
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);
// 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.)
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 :
L’utilisation de .NET dans le développement web côté client offre les avantages
suivants :
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 :
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>
@code {
private int currentCount = 0;
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).
Étapes suivantes
BlazorTutoriel : créer votre première Blazor application
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
Pour Blazor Hybrid les applications, nous testons et prenons en charge les dernières
versions de contrôle de la plateforme Web View :
Ressources supplémentaires
Modèles d’hébergement ASP.NET Core Blazor
Plateformes prises en charge SignalR par ASP.NET Core
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 :
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 :
Des instructions détaillées sur les modes de rendu sont fournies par l’article sur les
modes de rendu Blazor ASP.NET Core.
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 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.
Sélectionnez Créer.
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 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
7 Notes
commandes d’administration :
CLI .NET
XML
<PropertyGroup>
<RunAOTCompilation>true</RunAOTCompilation>
</PropertyGroup>
XML
<PropertyGroup>
<WasmEnableSIMD>false</WasmEnableSIMD>
</PropertyGroup>
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
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.
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.
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.
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.
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.
Pour plus d’informations sur les infrastructures clientes natives Microsoft, consultez les
ressources suivantes :
Les applications Blazor Hybrid incluent .NET MAUI, WPF et les applications
d’infrastructure Windows Forms.
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.
Utilisez le modèle d’hébergement Blazor Server pour éviter de devoir exposer des API
depuis l’environnement serveur.
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.
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.
Les didacticiels suivants fournissent des expériences de travail de base pour la création
d'applications Blazor.
Microsoft Learn
Blazor Parcours d'apprentissage
Blazor Modules d'apprentissage
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 :
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.
CLI .NET
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.
CLI .NET
cd TodoList
CLI .NET
) 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 {
Dans Components/Layout/NavMenu.razor :
razor
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#
7 Notes
Si vous utilisez Visual Studio pour créer le fichier TodoItem.cs et la classe TodoItem ,
utilisez l’une des approches 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>
@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.
@code {
private List<TodoItem> todos = new();
Pour obtenir le titre du nouvel élément todo, ajoutez un champ de chaîne newTodo en
haut du bloc @code :
C#
Modifiez l’élément de texte <input> pour lier newTodo avec l’attribut @bind :
razor
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>
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
razor
@page "/todo"
@rendermode InteractiveServer
<PageTitle>Todo</PageTitle>
<ul>
@foreach (var todo in todos)
{
<li>
<input type="checkbox" @bind="todo.IsDone" />
<input @bind="todo.Title" />
</li>
}
</ul>
@code {
private List<TodoItem> todos = new();
private string? newTodo;
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
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 :
Apprenez à :
Prérequis
Visual Studio
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.
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.
Confirmez que le Framework est .NET 8.0 ou version ultérieure. Sélectionnez Create
(Créer).
Dans la boîte de dialogue Gérer les packages NuGet, confirmez que la source du
package est définie sur nuget.org .
C#
using Microsoft.AspNetCore.SignalR;
namespace BlazorSignalRApp.Hubs;
C#
using Microsoft.AspNetCore.ResponseCompression;
using BlazorSignalRApp.Hubs;
C#
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
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");
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;
await hubConnection.StartAsync();
}
Exécuter l’application
Suivez l’aide relative à vos outils :
Visual Studio
Étapes suivantes
Dans ce didacticiel, vous avez appris à :
Pour obtenir une aide détaillée sur les frameworks SignalR et Blazor, consultez les
ensembles de documentation de référence suivants :
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)
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.
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
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é.
.NET MAUI
BlazorWebViewInitializing
BlazorWebViewInitialized
WPF
BlazorWebViewInitializing
BlazorWebViewInitialized
Windows Forms
BlazorWebViewInitializing
BlazorWebViewInitialized
Globalisation et localisation
Cette section s’applique uniquement aux applications .NET MAUIBlazor Hybrid.
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.
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#
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 :
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.
Ce tutoriel vous montre comment générer et exécuter une application .NET MAUIBlazor
Hybrid. Vous allez apprendre à effectuer les actions suivantes :
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 :
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.
Dépendances restaurées :
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é.
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.
L’indicateur affiche une coche lorsque les tâches en arrière-plan sont terminées :
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
) 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é :
Pendant le déploiement :
Au démarrage de l’application :
Ouvrir un problème de
documentation
Ce tutoriel vous montre comment créer et exécuter une application Windows Forms
Blazor. Vous allez apprendre à effectuer les actions suivantes :
Prérequis
Plateformes prises en charge (documentation Windows Forms)
Visual Studio 2022 avec la charge de travail Développement .NET Desktop
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 ).
XML
<Project Sdk="Microsoft.NET.Sdk.Razor">
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
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;
}
Counter.razor :
razor
<h1>Counter</h1>
@code {
private int currentCount = 0;
à partir de la Boîte à outils dans le concepteur Form1 . Veillez à ne pas faire glisser
accidentellement un contrôle WebView2 dans le formulaire.
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.
C#
using Microsoft.AspNetCore.Components.WebView.WindowsForms;
using Microsoft.Extensions.DependencyInjection;
C#
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;
Exécuter l’application
Sélectionnez le bouton Démarrer dans la barre d’outils de Visual Studio :
Ce tutoriel vous montre comment générer et exécuter une application WPF Blazor. Vous
allez apprendre à effectuer les actions suivantes :
Prérequis
Plateformes prises en charge (documentation WPF)
Visual Studio 2022 avec la charge de travail de développement de bureau .NET
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 ).
XML
<Project Sdk="Microsoft.NET.Sdk.Razor">
XML
<RootNamespace>WpfBlazor</RootNamespace>
7 Notes
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
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>
</html>
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;
}
Counter.razor :
razor
<h1>Counter</h1>
@code {
private int currentCount = 0;
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>
C#
using Microsoft.Extensions.DependencyInjection;
C#
7 Notes
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;
Exécuter l’application
Sélectionnez le bouton Démarrer dans la barre d’outils de Visual Studio :
Cet article explique comment gérer le routage et la navigation des requêtes dans les
applications Blazor Hybrid.
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 :
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.
C#
using Microsoft.AspNetCore.Components.WebView;
C#
blazorWebView.UrlLoading +=
(sender, urlLoadingEventArgs) =>
{
if (urlLoadingEventArgs.Url.Host != "0.0.0.0")
{
urlLoadingEventArgs.UrlLoadingStrategy =
UrlLoadingStrategy.OpenInWebView;
}
};
XAML
C#
blazorWebView.StartPath = "/welcome";
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.
diff
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>
Views/NavigationExample.xaml.cs :
C#
namespace MauiBlazor.Views;
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>
@code {
private async void OpenPage()
{
await App.Current.MainPage.Navigation.PushAsync(new
NavigationExample());
}
}
diff
- await Navigation.PopAsync();
+ await Navigation.PopModalAsync();
diff
- await App.Current.MainPage.Navigation.PushAsync(new
NavigationExample());
+ await App.Current.MainPage.Navigation.PushModalAsync(new
NavigationExample());
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.
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"
]
}
}
]
Vous pouvez utiliser l’outil Générateur de listes d’instructions pour générer et valider
le fichier.
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.
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();
shell
adb shell am start -a android.intent.action.VIEW -c
android.intent.category.BROWSABLE -d "https://redth.dev/items/1234"
-a Action :
-c Catégorie :
-d : URI de données
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.
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.
iOS ou MacCatalyst.
XML
<ItemGroup
Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
== 'ios' Or $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
== 'maccatalyst'">
</ItemGroup>
C#
builder.ConfigureLifecycleEvents(lifecycle =>
{
#if IOS || MACCATALYST
lifecycle.AddiOS(ios =>
{
ios.FinishedLaunching((app, data)
=> HandleAppLink(app.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
});
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.
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 :
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.
Resources/Raw/Data.txt :
text
Pages/StaticAssetExample.razor :
razor
@page "/static-asset-example"
@using System.IO
@using Microsoft.Extensions.Logging
@inject ILogger<StaticAssetExample> Logger
<p>@dataResourceText</p>
@code {
public string dataResourceText = "Loading resource ...";
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
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.
2 Avertissement
StaticAssetExample.razor :
razor
@page "/static-asset-example"
@using System.Resources
<p>@dataResourceText</p>
@code {
public string dataResourceText = "Loading resource ...";
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
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.
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.
StaticAssetExample.razor :
razor
@page "/static-asset-example"
@using System.Resources
<p>@dataResourceText</p>
@code {
public string dataResourceText = "Loading resource ...";
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 .
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/data.txt :
text
This is text from a static text file resource.
wwwroot/scripts.js :
JavaScript
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 :
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
<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>
<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;
Dans les applications .NET MAUI, ajoutez la méthode suivante ReadData au bloc @code
du composant précédent :
C#
Dans les applications WPF et Windows Forms, ajoutez la méthode suivante ReadData au
bloc @code du composant précédent :
C#
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
C#
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)
Cet article explique comment utiliser les outils de développement du navigateur avec
des applications Blazor Hybrid.
Si le projet n’est pas déjà configuré pour les outils de développement du navigateur,
ajoutez la prise en charge en :
C#
using Microsoft.Extensions.Logging;
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.
Ressources supplémentaires
Outils de développement de Chrome
Présentation des outils de développement de Microsoft Edge
Aide au développeur Safari
Cet article explique comment créer et organiser des composants Razor pour le web et
les applications Web Views dans Blazor Hybrid.
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 :
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 .
CameraService.cs :
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
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 :
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)
.NET
ASP.NET Core
Blazor
.NET MAUI
Azure Container Apps
Orleans
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 :
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.
_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 :
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
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 .
C#
namespace SharedLibrary.Data;
C#
using SharedLibrary.Data;
namespace SharedLibrary.Interfaces;
razor
@using SharedLibrary.Data
@using SharedLibrary.Interfaces
WebAssembly :
C#
using System.Net.Http.Json;
using SharedLibrary.Data;
using SharedLibrary.Interfaces;
Dans l’exemple précédent, l’espace réservé {APP NAMESPACE} est l’espace de noms de
l’application.
C#
using SharedLibrary.Data;
using SharedLibrary.Interfaces;
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 .
razor
@page "/fetchdata"
@inject IWeatherForecastService ForecastService
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
@code {
private WeatherForecast[]? forecasts;
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
Cet article explique comment transmettre des paramètres de composant racine dans
une application Blazor Hybrid.
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 :
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 KeypadViewModel()
{
// Command to add the key to the input string
AddCharCommand = new Command<string>((key) => InputString += key);
// 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
formatted = string.Format("({0}) {1}-{2}", str.Substring(0, 3),
str.Substring(3, 3), str.Substring(6));
return formatted;
}
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}" />
C#
public MainPage()
{
InitializeComponent();
rootComponent.Parameters =
new Dictionary<string, object>
{
{ "KeypadViewModel", new KeypadViewModel() }
};
}
razor
@code {
[Parameter]
public KeypadViewModel KeypadViewModel { get; set; }
}
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.
Pages/Keypad.razor :
razor
@page "/keypad"
<h1>Keypad</h1>
<table id="keypad">
<thead>
<tr>
<th colspan="2">@KeypadViewModel.DisplayText</th>
<th><button @onclick="DeleteChar">⇦</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; }
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>
razor
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
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.
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 :
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.
un navigateur qui sont à l’écoute d’un rappel vers une URL spécifique inscrite auprès de
l’application.
7 Notes
ExternalAuthStateProvider.cs :
C#
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
C#
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
diff
- return builder.Build();
C#
builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();
/*
Provide OpenID/MSAL code to authenticate the user. See your identity
provider's
documentation for details.
authenticatedUser.Principal = user;
return host;
7 Notes
ExternalAuthStateProvider.cs :
C#
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
C#
using Microsoft.AspNetCore.Components.Authorization;
C#
builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();
C#
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.
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#
( .TryAddScoped<AuthenticationStateProvider,
CurrentThreadUserAuthenticationStateProvider>() ) sont ajoutés à la collection de
services.
7 Notes
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());
return loginTask;
return Task.FromResult(authenticatedUser);
}
C#
builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
Shared/LoginComponent.razor :
razor
@code
{
public async Task Login()
{
await ((ExternalAuthStateProvider)AuthenticationStateProvider)
.LogInAsync();
}
}
Shared/LogoutComponent.razor :
razor
@code
{
public async Task Logout()
{
await ((ExternalAuthStateProvider)AuthenticationStateProvider)
.Logout();
}
}
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.
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 :
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 :
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.
razor
2 Avertissement
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.
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.
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.
Ressources supplémentaires
Authentification et autorisation avec ASP.NET Core Blazor Hybrid
Authentification et autorisation avec ASP.NET Core Blazor
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.
Cet article explique les étapes à suivre pour utiliser la journalisation BlazorWebView :
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);
});
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#
services.AddLogging(logging =>
{
logging.AddFilter("Microsoft.AspNetCore.Components.WebView",
LogLevel.Trace);
logging.AddDebug();
});
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 .
Projet serveur :
Components Dossier :
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.
7 Notes
Dossier wwwroot : dossier racine web du projet serveur contenant les ressources
statiques publiques de l’application.
serveur.
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.
Blazor WebAssembly
Modèles de projet Blazor WebAssembly : blazorwasm
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 :
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.
composants de l’application ( .razor ), telles que les directives @using pour les
espaces de noms.
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
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>
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)
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.
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.
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.
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 .
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
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>
@code {
private int currentCount = 0;
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
† 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.
7 Notes
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)
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 .
7 Notes
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
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
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 :
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 .
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.
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.
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.
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)]
razor
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.
Le composant Router permet à l’application de spécifier du contenu personnalisé si le contenu relatif à la route
demandée est introuvable.
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.
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
RouteParameter1.razor :
razor
@page "/route-parameter-1/{text}"
<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?}"
<p>Blazor is @Text!</p>
@code {
[Parameter]
public string? Text { get; set; }
C#
Contraintes d'itinéraire
Une contrainte de route applique la correspondance de type d’un segment de route par rapport à un composant.
User.razor :
razor
@page "/user/{Id:int}"
<PageTitle>User</PageTitle>
<h1>User Example</h1>
@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
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; }
}
CatchAll.razor :
razor
@page "/catch-all/{*pageRoute}"
<PageTitle>Catch All</PageTitle>
<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; }
}
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* .
ノ Agrandir le tableau
Membre Description
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>).
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.
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 :
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é :
Navigate.razor :
razor
@page "/navigate"
@implements IDisposable
@inject ILogger<Navigate> Logger
@inject NavigationManager Navigation
<PageTitle>Navigate</PageTitle>
<h1>Navigate Example</h1>
Pour plus d’informations sur la suppression de composants, consultez Cycle de vie des composants ASP.NET Core
Razor.
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.
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.
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 .
HTML
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
HTML
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 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
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).
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 :
Si l’URI de base de l’application ne correspond pas à l’URI de base de inputURI , une ArgumentException est levée.
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#
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 :
La mise en forme appropriée, indépendante de la culture, est appliquée pour le type donné
(CultureInfo.InvariantCulture).
C#
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>
<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; }
}
razor
...
Navigation.GetUriWithQueryParameter("{NAME}", {VALUE})
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
...
Navigation.GetUriWithQueryParameters({PARAMETERS})
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
...
Navigation.GetUriWithQueryParameters("{URI}", {PARAMETERS})
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
ノ 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
ノ Agrandir le tableau
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
C#
ノ Agrandir le tableau
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/
C#
Navigation.GetUriWithQueryParameters(
new Dictionary<string, object?>
{
["name"] = null,
["age"] = (int?)25,
["eye color"] = "green"
})
ノ Agrandir le tableau
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
full name est ajouté ou mis à jour avec Morena Baccarin , une valeur unique.
C#
Navigation.GetUriWithQueryParameters(
new Dictionary<string, object?>
{
["full name"] = "Morena Baccarin",
["ping"] = new int?[] { 35, 16, null, 87, 240 }
})
ノ Agrandir le tableau
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.
C#
Navigation.NavigateTo(
Navigation.GetUriWithQueryParameter("name", "Morena Baccarin"));
Les exemples de chacune des approches suivantes illustrent la navigation vers un élément avec un id de
targetElement dans le composant Counter :
razor
<a href="/counter#targetElement">
razor
<NavLink href="/counter#targetElement">
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
HashedRouting.razor :
razor
@page "/hashed-routing"
@inject NavigationManager Navigation
<PageTitle>Hashed routing</PageTitle>
<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>
@code {
private void NavigateToElement()
{
Navigation.NavigateTo("/counter#targetElement");
}
}
razor
@using Microsoft.AspNetCore.Components.Routing
Fournissez du contenu au paramètre Navigating à afficher lors des événements de transition de page.
razor
<Navigating>
<p>Loading the requested page…</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.
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.
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.
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
<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 };
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.
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;
return ValueTask.CompletedTask;
}
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.
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.
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("/");
}
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> :
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
HTML
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
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 .
razor
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>();
Cet article explique comment configurer des applications Blazor, notamment les
paramètres de l’application, l’authentification et la configuration de la journalisation.
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.
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>.
Sur le client, la configuration est chargée par défaut à partir des fichiers de configuration
d’application suivants :
wwwroot/appsettings.json .
7 Notes
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 .
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.
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.
wwwroot/cars.json :
JSON
{
"size": "tiny"
}
C#
using Microsoft.Extensions.Configuration;
Modifiez l’inscription de service HttpClient existante pour utiliser le client afin de lire le
fichier :
C#
builder.Configuration.AddJsonStream(stream);
C#
using Microsoft.Extensions.Configuration.Memory;
C#
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>
<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>
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}"
}
}
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 .
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 .
wwwroot/appsettings.json :
JSON
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
C#
builder.Logging.AddConfiguration(
builder.Configuration.GetSection("Logging"));
C#
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.
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.
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
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 :
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>.
ノ Agrandir le tableau
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.
IExampleDependency :
C#
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#
await host.RunAsync();
C#
await host.RunAsync();
C#
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
C#
builder.Services.AddSingleton<IDataAccess, DataAccess>();
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#
Pour le fichier Program côté client, appelez ConfigureCommonServices pour inscrire les
services communs :
C#
...
ConfigureCommonServices(builder.Services);
Dans le fichier Program côté serveur, appelez ConfigureCommonServices pour inscrire les
services communs :
C#
...
Client.Program.ConfigureCommonServices(builder.Services);
Pour plus d’informations, consultez Les services côté client ne peuvent pas être résolus
lors du prérendu.
ノ 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 :
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.
Pour plus d’informations, consultez Injection de dépendances dans des vues dans
ASP.NET Core.
razor
@page "/the-sunmakers"
@inject IDataAccess DataRepository
<PageTitle>The Sunmakers</PageTitle>
@code {
private IReadOnlyList<Actor>? actors;
/*
* Register the service in Program.cs:
* using static BlazorSample.Components.Pages.TheSunmakers;
* builder.Services.AddScoped<IDataAccess, DataAccess>();
*/
C#
using Microsoft.AspNetCore.Components;
...
}
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>
C#
using System.Net.Http;
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.
C#
[Inject(Key = "my-service")]
public IMyService MyService { get; set; }
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.
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.
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#
TimeTravel.cs :
C#
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.
C#
builder.Services.AddScoped<ITimeTravel, TimeTravel>();
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!;
TimeTravel1 reçoit la même instance de service que celle créée lors du premier
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é.
OwningComponentBase<TService>
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>
Dans : Program.cs
L’espace de noms Services de l’application est fourni en haut du fichier ( using
BlazorSample.Services; ).
( builder.Services.AddTransient<TransientDisposableService>(); ).
EnableTransientDisposableDetection est appelé sur l’hôte généré dans le
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; ).
( builder.DetectIncorrectUsageOfTransients(); ).
Le service TransientDependency est inscrit
( builder.Services.AddTransient<TransientDependency>(); ).
TransitiveTransientDisposableDependency est inscrit pour
ITransitiveTransientDisposableDependency
( builder.Services.AddTransient<ITransitiveTransientDisposableDependency,
TransitiveTransientDisposableDependency>(); ).
BaseAddressAuthorizationMessageHandler
AuthorizationMessageHandler
diff
- else if (name != "TransientService")
+ else
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#
return services;
}
}
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
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 :
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.
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>.
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
Blazor Server:
HTML
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 :
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
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
HTML
<script>
Blazor.start({ enableClassicInitializers: true });
</script>
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 .
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
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
7 Notes
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
JavaScript
additionalModule.js :
JavaScript
JavaScript
logMessage();
}
HTML
HTML
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
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.
ノ 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 .
dotnet.*.js
dotnet.wasm
HTML
HTML
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 .
HTML
HTML
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.
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.
C#
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é.
C#
...
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.
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é,
À 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.
de l’application.
totalResources est le nombre total de ressources à charger.
JavaScript
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
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.
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
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 .
HTML
HTML
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.
HTML
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
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 :
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.
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>.
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 .
HTML
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.
HTML
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.
C#
Console.WriteLine(
$"Client Hosting Environment: {builder.HostEnvironment.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
...
<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.
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 .
paramètre.
ReadEnvironment.razor :
razor
@page "/read-environment"
@using Microsoft.AspNetCore.Components.WebAssembly.Hosting
@inject IWebAssemblyHostEnvironment Env
<h1>Environment example</h1>
<p>Environment: @HostEnvironment.Environment</p>
ServerHostEnvironment.cs :
C#
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Components;
C#
builder.Services.TryAddScoped<IWebAssemblyHostEnvironment,
ServerHostEnvironment>();
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
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.
C#
if (builder.HostEnvironment.Environment == "Custom")
{
...
};
IsDevelopment
IsProduction
IsStaging
IsEnvironment
if (builder.HostEnvironment.IsStaging())
{
...
};
if (builder.HostEnvironment.IsEnvironment("Custom"))
{
...
};
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)
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 :
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.
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>.
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.
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.
Counter1.razor :
razor
@page "/counter-1"
@inject ILogger<Counter1> Logger
<PageTitle>Counter 1</PageTitle>
<h1>Counter 1</h1>
@code {
private int currentCount = 0;
currentCount++;
}
}
Counter2.razor :
razor
@page "/counter-2"
@inject ILoggerFactory LoggerFactory
<PageTitle>Counter 2</PageTitle>
<h1>Counter 2</h1>
@code {
private int currentCount = 0;
C#
builder.Logging.SetMinimumLevel(LogLevel.Warning);
C#
await host.RunAsync();
info: Program[0]
Logged after the app is built in the Program file.
7 Notes
C#
warn: CustomCategory[0]
Someone has clicked me!
LogEvent.cs :
C#
C#
info: BlazorSample.Pages.Counter[1000]
Someone has clicked me!
warn: BlazorSample.Pages.Counter[1001]
Someone has clicked me!
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#
info: BlazorSample.Pages.Counter[0]
Someone clicked me at 04/21/2022 12:15:57!
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.
C#
currentCount++;
try
{
if (currentCount == 3)
{
currentCount = 4;
throw new OperationCanceledException("Skip 3");
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "Exception (currentCount: {Count})!",
currentCount);
}
warn: BlazorSample.Pages.Counter[0]
Exception (currentCount: 4)!
System.OperationCanceledException: Skip 3
at BlazorSample.Pages.Counter.IncrementCount() in
C:UsersAlabaDesktopBlazorSamplePagesCounter.razor:line 28
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.
C#
C#
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);
dbug: Microsoft.AspNetCore.Components.RenderTree.Renderer[3]
Rendering component 14 of type
Microsoft.AspNetCore.Components.Web.HeadOutlet
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.
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 .
( LogFormat.Long ).
CustomLoggerConfiguration.cs :
C#
using Microsoft.Extensions.Logging;
C#
using Microsoft.Extensions.Logging;
using static CustomLoggerConfiguration;
public CustomLogger(
string name,
Func<CustomLoggerConfiguration> getCurrentConfig) =>
(this.name, this.getCurrentConfig) = (name, getCurrentConfig);
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);
}
CustomLoggerExtensions.cs :
C#
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<ILoggerProvider,
CustomLoggerProvider>());
LoggerProviderOptions.RegisterProviderOptions
<CustomLoggerConfiguration, CustomLoggerProvider>
(builder.Services);
return builder;
}
}
C#
builder.Logging.ClearProviders().AddCustomLogger();
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.");
}
}
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"
}
}
}
}
C#
builder.Logging.AddConfiguration(
builder.Configuration.GetSection("Logging"));
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 :
ノ Agrandir le tableau
Trace trace 0
Debug debug 1
Information information 2
Warning warning 3
Error error 4
Critical critical 5
None none 6
Application webBlazor :
HTML
Blazor Server :
HTML
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.
Application webBlazor :
HTML
Blazor Server :
HTML
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
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
7 Notes
WebAssemblyConsoleLogger est interne et n’est pas pris en charge pour une
C#
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.
C#
await hubConnection.StartAsync();
}
7 Notes
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.
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)
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 :
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.
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>.
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.
En haut de MainLayout.razor :
razor
razor
HTML
7 Notes
C#
try
{
...
}
catch (Exception ex)
{
await DispatchExceptionAsync(ex);
}
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.
razor
@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
@code {
private void SendReport()
{
_ = SendReportAsync();
}
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!");
}
Dans MainLayout.razor :
razor
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
...
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 ) :
C#
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.
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é.
C#
builder.Services.AddRazorComponents(options =>
options.DetailedErrors = builder.Environment.IsDevelopment());
2 Avertissement
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 :
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.
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 :
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
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
...
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.
Dans Counter.razor :
C#
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 :
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.
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 :
razor
...
<ErrorBoundary @ref="errorBoundary">
@Body
</ErrorBoundary>
...
@code {
private ErrorBoundary? errorBoundary;
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
<CascadingValue Value="this">
@ChildContent
</CascadingValue>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
7 Notes
Dans Routes.razor :
razor
<Error>
<Router ...>
...
</Router>
</Error>
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; }
if (currentCount > 5)
{
throw new InvalidOperationException("Current count is
over five!");
}
}
catch (Exception ex)
{
Error?.ProcessError(ex);
}
}
}
Console
É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.
7 Notes
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.
‡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 :
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.
à l’utilisateur.
L’erreur est journalisée.
razor
@page "/product-details/{ProductId:int?}"
@inject ILogger<ProductDetails> Logger
@inject IProductRepository Product
<PageTitle>Product Details</PageTitle>
}
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; }
/*
* Register the service in Program.cs:
* using static BlazorSample.Components.Pages.ProductDetails;
* builder.Services.AddScoped<IProductRepository, ProductRepository>();
*/
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.
razor
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
Le code du gestionnaire d’événements peut lever une exception non prise en charge
dans ces scénarios.
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.
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.
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.
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 :
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 :
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.
2 Avertissement
Si du code RenderTreeBuilder est écrit, le développeur doit garantir son exactitude. Par
exemple, le développeur doit s’assurer que :
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.
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 :
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.
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>.
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 :
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.
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.
C#
if (!app.Environment.IsDevelopment())
{
app.UseResponseCompression();
}
IncludeRequestCredentialsMessageHandler.cs :
C#
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Http;
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
return base.SendAsync(request, cancellationToken);
}
}
C#
...
NavigationManager injecté.
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.
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.
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.
ノ Agrandir le tableau
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.
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;
});
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
AllowStatefulReconnects
ApplicationMaxBufferSize
AuthorizationData (Lecture seule)
CloseOnAuthenticationExpiration
LongPolling (Lecture seule)
MinimumProtocolVersion
TransportMaxBufferSize
Transports
TransportSendTimeout
WebSockets (Lecture seule)
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.
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 :
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 :
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.
7 Notes
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
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.
CSHTML
<div id="components-reconnect-modal">
There was a problem with the connection!
</div>
7 Notes
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;
}
ノ Agrandir le tableau
components- Une connexion active est rétablie avec le serveur. Masque le modal.
reconnect-hide
wwwroot/app.css :
css
#components-reconnect-modal {
transition: visibility 0s linear 1000ms;
}
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>
HTML
There was a problem with the connection! (Current reconnect attempt: 3 / 8)
Par exemple, vous pouvez utiliser un gestionnaire d’activités de circuit pour détecter si le
client est inactif :
C#
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 :
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 .
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é.
(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
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
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.
C#
await hubConnection.StartAsync();
}
Pour modifier les événements de connexion, inscrivez des rappels pour les modifications
de connexion suivantes :
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
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.
App.razor :
HTML
Application webBlazor :
JavaScript
(() => {
const maximumRetryCount = 3;
const retryIntervalMilliseconds = 5000;
const reconnectModal = document.getElementById('reconnect-modal');
(async () => {
for (let i = 0; i < maximumRetryCount; i++) {
reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of
${maximumRetryCount}`;
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;
}
return {
cancel: () => {
isCanceled = true;
reconnectModal.style.display = 'none';
},
};
};
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');
(async () => {
for (let i = 0; i < maximumRetryCount; i++) {
reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of
${maximumRetryCount}`;
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;
}
return {
cancel: () => {
isCanceled = true;
reconnectModal.style.display = 'none';
},
};
};
Blazor.start({
reconnectionHandler: {
onConnectionDown: () => currentReconnectionProcess ??=
startReconnectionProcess(),
onConnectionUp: () => {
currentReconnectionProcess?.cancel();
currentReconnectionProcess = null;
}
}
});
})();
Application webBlazor :
HTML
Blazor Server :
HTML
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.
suivant, le circuit est déconnecté lorsque la page est masquée (événement pagehide ):
JavaScript
window.addEventListener('pagehide', () => {
Blazor.disconnect();
});
TrackingCircuitHandler.cs :
C#
using Microsoft.AspNetCore.Components.Server.Circuits;
return Task.CompletedTask;
}
return Task.CompletedTask;
}
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.
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.
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.
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 :
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.
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>.
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.
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
C#
if (builder.Environment.IsStaging())
{
builder.WebHost.UseStaticWebAssets();
}
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.
XML
<PropertyGroup>
<StaticWebAssetBasePath>{PATH}</StaticWebAssetBasePath>
</PropertyGroup>
Dans l’exemple précédent, l’espace réservé {TFM} est le Moniker du framework cible
(TFM) (par exemple, net6.0 ).
XML
<PropertyGroup>
<StaticWebAssetBasePath>app1</StaticWebAssetBasePath>
</PropertyGroup>
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.
Configurez les options via l’injection de dépendances (DI) dans le fichier Program à
l’aide de StaticFileOptions :
C#
using Microsoft.AspNetCore.StaticFiles;
...
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") .
C#
using Microsoft.AspNetCore.StaticFiles;
...
var provider = new FileExtensionContentTypeProvider();
provider.Mappings["{EXTENSION}"] = "{CONTENT TYPE}";
C#
Ressources supplémentaires
Chemin de base de l’application
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 :
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.
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>.
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.
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.
7 Notes
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 :
@using
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
...
Exemple :
razor
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.
❌ productDetail.razor
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
†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 :
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 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
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é) :
<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
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
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>
@code {
private int currentCount = 0;
CounterPartialClass.razor :
razor
@page "/counter-partial-class"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
CounterPartialClass.razor.cs :
C#
namespace BlazorSample.Components.Pages;
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;
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.
BlazorRocks1.razor :
razor
@page "/blazor-rocks-1"
@inherits BlazorRocksBase1
<PageTitle>Blazor Rocks!</PageTitle>
<p>
@BlazorRocksText
</p>
BlazorRocksBase1.cs :
C#
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
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.
HelloWorld.razor :
razor
@page "/hello-world"
<PageTitle>Hello World!</PageTitle>
<h1>Hello World!</h1>
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# :
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 :
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
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
@code {
private string headingFontStyle = "italic";
}
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;
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
Parameter1.razor :
razor
@page "/parameter-1"
<PageTitle>Parameter 1</PageTitle>
<h1>Parameter Example 1</h1>
<ParameterChild />
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
<div>
<div>Set By Child</div>
<div>Set by child.</div>
</div>
<div>
<div>Set by Parent</div>
<div>Set by parent.</div>
</div>
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.
Parameter2.razor :
razor
@page "/parameter-2"
<PageTitle>Parameter 2</PageTitle>
@code {
private string title = "From Parent field";
private PanelData panelData = new();
7 Notes
razor
razor
Incorrect :
razor
razor
razor
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
@code {
private string? title;
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
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>
@code {
private PanelData panelData = new();
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.
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.
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;
C#
[Parameter]
[EditorRequired]
public string? Title { get; set; }
C#
[Parameter, EditorRequired]
public string? Title { get; set; }
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
@code {
[Parameter]
public (int, string, bool)? Data { get; set; }
}
RenderTupleParent.razor :
razor
@page "/render-tuple-parent"
@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
@code {
[Parameter]
public (int TheInteger, string TheString, bool TheBoolean)? Data { get;
set; }
}
NamedTuples.razor :
razor
@page "/named-tuples"
<PageTitle>Named Tuples</PageTitle>
@code {
private (int TheInteger, string TheString, bool TheBoolean) data =
new(999, "I aim to misbehave.", true);
}
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; }
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 .
RenderFragmentChild.razor :
razor
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
) Important
RenderFragments.razor :
razor
@page "/render-fragments"
<PageTitle>Render Fragments</PageTitle>
<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
<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
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
razor
@RenderWelcomeInfo
@RenderWelcomeInfo
@code {
private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!
</p>;
}
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.
ReferenceChild.razor :
razor
@code {
private int value;
this.value = value;
StateHasChanged();
}
}
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.
ReferenceParent1.razor :
razor
@page "/reference-parent-1"
@code {
private ReferenceChild? childComponent;
}
ReferenceParent2.razor :
razor
@page "/reference-parent-2"
@code {
private ReferenceChild? childComponent;
private Action? callChildMethod;
) Important
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]
ConditionalAttribute.razor :
razor
@page "/conditional-attribute"
<PageTitle>Conditional Attribute</PageTitle>
<label>
<input type="checkbox" checked="@IsCompleted" />
Is Completed?
</label>
@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
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>
@((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
RazorTemplate.razor :
razor
@page "/razor-template"
<PageTitle>Razor Template</PageTitle>
@timeTemplate
HTML
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
HTML
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");
}
razor
<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.";
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 :
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
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.
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
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
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
† 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.
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.
Les services pour les composants Razor sont ajoutés en appelant AddRazorComponents.
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();
Dans l’exemple suivant, le rendu côté serveur interactif (SSR interactif) est appliqué à
l’instance de composant Dialog :
razor
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
razor
razor
@page "..."
@rendermode InteractiveServer
rendu référencée doit être statique. L'attribut directive @rendermode peut prendre
n'importe quelle instance de mode de rendu.
7 Notes
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
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 :
Layout . S’il n’existe pas déjà, créez un dossier Layout dans le projet .Client .
Pour activer l’interactivité globale lors de la création d’une Web App Blazor :
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.
razor
@rendermode renderModeForPage
...
@code {
private static IComponentRenderMode renderModeForPage =
InteractiveServer;
}
razor
...
@code {
private IComponentRenderMode? RenderModeForPage => InteractiveServer;
}
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
razor
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 .
RenderMode1.razor :
razor
@page "/render-mode-1"
@code {
private string message = "Not clicked yet.";
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).
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
RenderMode2.razor :
razor
@page "/render-mode-2"
@rendermode InteractiveServer
@code {
private string message = "Not clicked yet.";
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.
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
@code {
private string message = "Not clicked yet.";
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.
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
@code {
private string message = "Not clicked yet.";
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.
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>
<p>@ChildContent</p>
@code {
private string message = "Not clicked yet.";
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public string Greeting { get; set; } = "Hello!";
RenderMode5.razor :
razor
@page "/render-mode-5"
<SharedMessage />
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 />
RenderMode7.razor :
razor
@page "/render-mode-7"
RenderMode8.razor :
razor
@page "/render-mode-8"
RenderMode9.razor :
razor
@page "/render-mode-9"
<SharedMessage @rendermode="InteractiveServer">
Child content
</SharedMessage>
❌Erreur :
WrapperComponent.razor :
razor
<SharedMessage>
Child content
</SharedMessage>
RenderMode10.razor :
razor
@page "/render-mode-10"
Le composant suivant génère une erreur de runtime lorsque le composant est rendu :
RenderMode11.razor :
razor
@page "/render-mode-11"
@rendermode InteractiveServer
❌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.
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.
razor
@using BlazorSample.Components.Account.Shared
@layout AccountLayout
Components/Account/Shared/AccountLayout.razor :
razor
@inherits LayoutComponentBase
@layout BlazorSample.Components.Layout.MainLayout
@inject NavigationManager NavigationManager
@code {
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
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.
Components/App.razor :
razor
...
@code {
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;
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.
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
@code {
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
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
...
@code {
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;
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.
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
personnalisés :
razor
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.
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>
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;
Toutefois, l’approche précédente n’est pas utile si votre logique nécessite une valeur
pendant le prérendu.
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é :
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.
razor
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
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)
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.
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.
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>.
PrerenderedCounter1.razor :
razor
@page "/prerendered-counter-1"
@rendermode @(new InteractiveServerRenderMode(prerender: true))
@inject ILogger<PrerenderedCounter1> Logger
@code {
private int currentCount;
private Random r = new Random();
7 Notes
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).
razor
@implements IDisposable
@inject PersistentComponentState ApplicationState
...
@code {
private {TYPE} data;
private PersistingComponentStateSubscription persistingSubscription;
if (!ApplicationState.TryTakeFromJson<{TYPE}>(
"{TOKEN}", out var restored))
{
data = await ...;
}
else
{
data = restored!;
}
}
return Task.CompletedTask;
}
void IDisposable.Dispose()
{
persistingSubscription.Dispose();
}
}
razor
@page "/prerendered-counter-2"
@implements IDisposable
@inject ILogger<PrerenderedCounter2> Logger
@inject PersistentComponentState ApplicationState
@code {
private int currentCount;
private Random r = new Random();
private PersistingComponentStateSubscription persistingSubscription;
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);
}
}
return Task.CompletedTask;
}
void IDisposable.Dispose()
{
persistingSubscription.Dispose();
}
private void IncrementCount()
{
currentCount++;
}
}
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.
Pages/Shared/_Layout.cshtml :
CSHTML
<body>
...
<persist-component-state />
</body>
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
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
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 :
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.
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>.
razor
@typeparam TItem
razor
ListGenericTypeItems1.razor :
razor
@typeparam TExample
@code {
[Parameter]
public IEnumerable<TExample>? ExampleList{ get; set; }
}
Le composant suivant génère le rendu de deux ListGenericTypeItems1 composants :
GenericType1.razor :
razor
@page "/generic-type-1"
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.
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 :
ListDisplay1.razor :
razor
@typeparam TExample
@code {
[Parameter]
public IEnumerable<TExample>? ExampleList { get; set; }
}
ListDisplay2.razor :
razor
@typeparam TExample
@code {
[Parameter]
public IEnumerable<TExample>? ExampleList { get; set; }
}
7 Notes
Cette section utilise les deux composants ListDisplay de la section Prise en charge
des types génériques en cascade.
ListGenericTypeItems2.razor :
razor
@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample
@ChildContent
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
( 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.
composant ListGenericTypeItems2 .
GenericType2.razor :
razor
@page "/generic-type-2"
<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>
ListDisplay3.razor :
razor
@typeparam TExample
@code {
[CascadingParameter]
protected IEnumerable<TExample>? ExampleList { get; set; }
}
ListDisplay4.razor :
razor
@typeparam TExample
@code {
[CascadingParameter]
protected IEnumerable<TExample>? ExampleList { get; set; }
}
ListGenericTypeItems3.razor :
razor
@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample
@ChildContent
<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"
<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
types :
razor
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
@ChildContent
<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"
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"
<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 };
}
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 :
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.
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>.
Result
Wait
WaitAny
WaitAll
Sleep
GetResult
7 Notes
TimerService.cs :
C#
namespace BlazorSample;
using (timer)
{
while (await timer.WaitForNextTickAsync())
{
elapsedCount += 1;
await notifier.Update("elapsedCount", elapsedCount);
logger.LogInformation("ElapsedCount {Count}",
elapsedCount);
}
}
}
}
NotifierService.cs :
C#
namespace BlazorSample;
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>();
Notifications.razor :
razor
@page "/notifications"
@implements IDisposable
@inject NotifierService Notifier
@inject TimerService Timer
<PageTitle>Notifications</PageTitle>
<h1>Notifications Example</h1>
<h2>Timer Service</h2>
<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;
) Important
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 :
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.
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 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> .
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>
@code {
private Timer timer = new Timer(3000);
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.
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
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é :
) Important
Les clés sont locales à chaque composant ou élément conteneur. Les clés ne sont
pas comparées globalement dans le document.
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>
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>
razor
<div>
@foreach (var person in people)
{
<Details @key="person" Data="@person.Data" />
}
</div>
razor
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
razor
razor
<ol>
@foreach (var person in people)
{
<li>
<Details @key="person" Data="@person.Data" />
</li>
}
</ol>
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.
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.
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 :
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.
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>.
Paramètres remplacés
Le framework Blazor impose généralement une affectation de paramètres parent-enfant
sans échec :
) 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.
Expander.razor :
@if (Expanded)
{
<p class="card-text">@ChildContent</p>
}
</div>
</div>
@code {
[Parameter]
public bool Expanded { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
ExpanderExample.razor :
@page "/expander-example"
<PageTitle>Expander</PageTitle>
<h1>Expander Example</h1>
<Expander Expanded="true">
Expander 1 content
</Expander>
<button @onclick="StateHasChanged">
Call StateHasChanged
</button>
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 :
7 Notes
Expander.razor :
@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; }
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.
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 :
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.
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>.
Projection d’attributs
Dans le composant Splat suivant :
Splat.razor :
razor
@page "/splat"
<PageTitle>SPLAT!</PageTitle>
<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" }
};
}
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; }
}
AttributeOrderChild1.razor :
razor
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}
AttributeOrder1.razor :
razor
@page "/attribute-order-1"
<p>
View the HTML markup in your browser to inspect the attributes on
the AttributeOrderChild1 component.
</p>
HTML
Dans l’exemple suivant, l’ordre de extra et de @attributes est inversé dans le <div> du
composant enfant :
AttributeOrderChild2.razor :
razor
@code {
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object>? AdditionalAttributes { get; set; }
}
AttributeOrder2.razor :
razor
@page "/attribute-order-2"
<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
Ouvrir un problème de
documentation
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 :
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.
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>.
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é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.
DoctorWhoLayout.razor :
razor
@inherits LayoutComponentBase
<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
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
7 Notes
razor
@using BlazorSample.Components.Layout
razor
@using BlazorSample.Components.Layout
@layout DoctorWhoLayout
razor
@layout BlazorSample.Components.Layout.DoctorWhoLayout
Episodes.razor :
razor
@page "/episodes"
@layout DoctorWhoLayout
<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
HTML
<header>
<h1 ...>...</h1>
</header>
<nav>
...
</nav>
<h2>...</h2>
<ul>
<li>...</li>
<li>...</li>
<li>...</li>
</ul>
<footer>
...
</footer>
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.
_Imports.razor :
razor
@layout DoctorWhoLayout
...
2 Avertissement
7 Notes
razor
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
7 Notes
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.
DoctorWhoLayout.razor :
razor
@inherits LayoutComponentBase
@layout ProductionsLayout
<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";
}
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 :
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>
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)
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.
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.
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 :
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 ).
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
razor
<SectionContent SectionName="top-bar">
<button class="btn btn-primary" @onclick="IncrementCount">Click
me</button>
</SectionContent>
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.
@code {
internal static object TopbarSection = new();
}
razor
Dans Counter.razor :
razor
<SectionContent SectionId="MainLayout.TopbarSection">
<button class="btn btn-primary" @onclick="IncrementCount">Click
me</button>
</SectionContent>
7 Notes
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 :
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.
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.
ControlHeadContent.razor :
razor
@page "/control-head-content"
<PageTitle>@title</PageTitle>
<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>
C#
builder.RootComponents.Add<HeadOutlet>("head::after");
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)
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 :
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.
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>.
Daleks.cs :
C#
namespace BlazorSample;
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#
Daleks.razor :
razor
@page "/daleks"
<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.
7 Notes
ThemeInfo.cs :
C#
namespace BlazorSample;
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>
@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 :
razor
<CascadingValue Value="@theme">
<Router ...>
<Found ...>
...
</Found>
</Router>
</CascadingValue>
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
razor
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.
Program.cs
C#
builder.Services.AddCascadingValue(sp =>
new ThemeInfo() { ButtonClass = "btn-primary" });
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.
ThemedCounter.razor :
razor
@page "/themed-counter"
<PageTitle>Themed Counter</PageTitle>
<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; }
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" };
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.
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 .
razor
@code {
private CascadingType? parentCascadeParameter1;
[Parameter]
public CascadingType? ParentCascadeParameter2 { get; set; }
}
razor
@code {
[CascadingParameter(Name = "CascadeParam1")]
protected CascadingType? ChildCascadeParameter1 { get; set; }
[CascadingParameter(Name = "CascadeParam2")]
protected CascadingType? ChildCascadeParameter2 { get; set; }
}
7 Notes
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;
7 Notes
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
razor
@using BlazorSample.UIInterfaces
<CascadingValue Value="this">
<ul class="nav nav-tabs">
@ChildContent
</ul>
</CascadingValue>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
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; }
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;
}
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 :
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.
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.
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#.
EventHandler1.razor :
razor
@page "/event-handler-1"
<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";
EventHandler2.razor :
razor
@page "/event-handler-2"
<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);
EventHandler3.razor :
razor
@page "/event-handler-3"
<p>@mousePointerMessage</p>
@code {
private string? mousePointerMessage;
ノ Agrandir le tableau
Presse-papiers ClipboardEventArgs
Error ErrorEventArgs
Entrée ChangeEventArgs
Clavier KeyboardEventArgs
Souris MouseEventArgs
Pointeur de PointerEventArgs
souris
Roulette de la WheelEventArgs
souris
Progression ProgressEventArgs
7 Notes
EventHandlers contient des attributs pour configurer les mappages entre les noms
d’événements et les types d’arguments d’événement.
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.
JavaScript
function eventArgsCreator(event) {
return {
customProperty1: 'any value for property 1',
customProperty2: event.srcElement.id
};
}
JavaScript
7 Notes
C#
namespace BlazorSample.CustomEvents;
C#
using Microsoft.AspNetCore.Components;
namespace BlazorSample.CustomEvents;
[EventHandler("oncustomevent", typeof(CustomEventArgs),
enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}
razor
@using BlazorSample.CustomEvents
@code
{
private string? propVal1;
private string? propVal2;
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.
CustomEvents.cs :
C#
using Microsoft.AspNetCore.Components;
namespace BlazorSample.CustomEvents;
[EventHandler("oncustompaste", typeof(CustomPasteEventArgs),
enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}
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.
JavaScript
JavaScript
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.
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;
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"
<h2>@heading</h2>
<p>
<button @onclick="@(e => heading = "New heading!!!")">
Update heading
</button>
</p>
@code {
private string heading = "Initial heading";
}
EventHandler5.razor :
razor
@page "/event-handler-5"
<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
</p>
}
@code {
private string heading = "Select a button to learn its position";
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 :
Vous pouvez également utiliser une boucle foreach avec Enumerable.Range, qui ne
souffre pas du problème précédent :
razor
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
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; }
}
ParentChild.razor :
razor
@page "/parent-child"
<PageTitle>Parent Child</PageTitle>
<p>@message</p>
@code {
private string? message;
C#
await OnClickCallback.InvokeAsync();
Child2.razor :
razor
<h3>Child2 Component</h3>
@code {
[Parameter]
public EventCallback<string> OnClickCallback { get; set; }
ParentChild2.razor :
razor
@page "/parent-child-2"
<p>
@messageText
</p>
@code {
private string messageText = string.Empty;
}
Document: keydown .
EventHandler6.razor :
razor
@page "/event-handler-6"
<p>
<label>
Count of '+' key presses:
<input value="@count" @onkeydown="KeyHandler"
@onkeydown:preventDefault />
</label>
</p>
@code {
private int count = 0;
razor
...
@code {
private bool shouldPreventDefault = true;
}
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
EventHandler7.razor :
razor
@page "/event-handler-7"
<div>
<b>stopPropagation</b>: @stopPropagation
</div>
<div>
<button @onclick="StopPropagation">
Stop Propagation (stopPropagation = true)
</button>
<button @onclick="EnablePropagation">
Enable Propagation (stopPropagation = false)
</button>
</div>
<p>
@message
</p>
@code {
private bool stopPropagation = false;
private string? message;
razor
@page "/event-handler-8"
<p>
<label>
Input:
<input @ref="exampleInput" />
</label>
</p>
<button @onclick="ChangeFocus">
Focus the Input Element
</button>
@code {
private ElementReference exampleInput;
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 :
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.
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>.
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.
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.
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>
<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
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>
<p>
<label>
InputValue:
<input @bind="InputValue" @bind:event="oninput" />
</label>
</p>
<p>
<code>InputValue</code>: @InputValue
</p>
@code {
private string? InputValue { get; set; }
}
razor
@code {
private string? searchText;
private string[]? searchResult;
Exemples supplémentaires
BindAfter.razor :
razor
@page "/bind-after"
@using Microsoft.AspNetCore.Components.Forms
<h2>Elements</h2>
<h2>Components</h2>
<InputText @bind-Value="text" @bind-Value:after="() => { }" />
@code {
private string text = "";
Exemples
BindGetSet.razor :
razor
@page "/bind-get-set"
@using Microsoft.AspNetCore.Components.Forms
<h2>Elements</h2>
<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 = "";
razor
<p>
<input value="@inputValue" @oninput="OnInput" />
</p>
<p>
<code>inputValue</code>: @inputValue
</p>
@code {
private string? inputValue;
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.
razor
<p>
<input @bind:event="oninput" @bind:get="inputValue" @bind:set="OnInput"
/>
</p>
<p>
<code>inputValue</code>: @inputValue
</p>
@code {
private string? inputValue;
DecimalBinding.razor :
razor
@page "/decimal-binding"
@using System.Globalization
<PageTitle>Decimal Binding</PageTitle>
<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");
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.
BindMultipleInput.razor :
razor
@page "/bind-multiple-input"
<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" };
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> .
Le framework Blazor ne tente pas de supprimer le comportement par défaut, car cela
impliquerait les effets suivants :
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> .
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>
<p>
<label>
inputValue:
<input @bind="inputValue" />
</label>
</p>
<p>
<code>inputValue</code>: @inputValue
</p>
@code {
private int inputValue = 123;
}
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>
<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);
}
C#
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
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.
ChildBind.razor :
razor
@code {
private Random r = new();
[Parameter]
public int Year { get; set; }
[Parameter]
public EventCallback<int> YearChanged { get; set; }
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>
@code {
private Random r = new();
private int year = 1979;
razor
@code {
...
razor
PasswordEntry.razor :
razor
@code {
private bool showPassword;
private string? password;
[Parameter]
public string? Password { get; set; }
[Parameter]
public EventCallback<string> PasswordChanged { get; set; }
await PasswordChanged.InvokeAsync(password);
}
PasswordBinding.razor :
razor
@page "/password-binding"
<PageTitle>Password Binding</PageTitle>
<p>
<code>password</code>: @password
</p>
@code {
private string password = "Not set";
}
7 Notes
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.
PasswordEntry.razor :
razor
@code {
private bool showPassword;
private string? password;
private string? validationMessage;
[Parameter]
public string? Password { get; set; }
[Parameter]
public EventCallback<string> PasswordChanged { get; set; }
return Task.CompletedTask;
}
else
{
validationMessage = string.Empty;
return PasswordChanged.InvokeAsync(password);
}
}
Parent2.razor :
razor
@page "/parent-2"
<PageTitle>Parent 2</PageTitle>
<p>
<button @onclick="ChangeValue">Change from Parent</button>
</p>
@code {
private string parentMessage = "Initial value set in Parent";
NestedChild.razor :
razor
<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; }
NestedGrandchild.razor :
razor
<p>
<button @onclick="ChangeValue">Change from Grandchild</button>
</p>
</div>
@code {
[Parameter]
public string? GrandchildMessage { get; set; }
[Parameter]
public EventCallback<string> GrandchildMessageChanged { get; set; }
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)
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 :
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.
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>.
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
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 :
SetParamsAsync.razor :
razor
@page "/set-params-async/{Param?}"
<p>@message</p>
@code {
private string message = "Not set";
[Parameter]
public string? Param { get; set; }
await base.SetParametersAsync(parameters);
}
}
OnInit.razor :
razor
@page "/on-init"
<PageTitle>On Initialized</PageTitle>
<p>@message</p>
@code {
private string? message;
C#
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#
await base.OnInitializedAsync();
}
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.
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.
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.
7 Notes
OnParamsSet.razor :
razor
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<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; }
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#
await base.OnParametersSetAsync();
}
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>
<p>
<button @onclick="LogInformation">Log information (and trigger a render)
</button>
</p>
@code {
private string message = "Initial assigned message.";
if (firstRender)
{
message = "Executed for the first render.";
}
else
{
message = "Executed after the first render.";
}
C#
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#
await base.OnAfterRenderAsync(firstRender);
}
BlazorRocks2.razor :
razor
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<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;
razor
@code {
private Movies[]? movies;
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.
méthode GetForecastAsync .
C#
builder.Services.AddMemoryCache();
WeatherForecastService.cs :
C#
using Microsoft.Extensions.Caching.Memory;
namespace BlazorSample;
await Task.Delay(TimeSpan.FromSeconds(10));
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.
7 Notes
HTML
<script>
window.setElementText1 = (element, text) => element.innerText = text;
</script>
2 Avertissement
PrerenderedInterop1.razor :
razor
@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS
<PageTitle>Prerendered Interop 1</PageTitle>
@code {
private ElementReference divElement;
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
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
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">@(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;
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
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.
IDisposable synchrone
Le composant suivant :
razor
@implements IDisposable
...
@code {
...
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
@code {
private int currentCount = 0;
private Timer timer = new(1000);
7 Notes
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
IAsyncDisposable asynchrone
Le composant suivant :
razor
@implements IAsyncDisposable
...
@code {
...
public async ValueTask DisposeAsync()
{
if (obj is not null)
{
await obj.DisposeAsync();
}
}
}
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
razor
@implements IDisposable
<EditForm EditContext="editContext">
...
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>
@code {
...
editContext.OnFieldChanged += fieldChanged;
}
razor
@implements IDisposable
<EditForm EditContext="editContext">
...
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>
@code {
...
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.
C#
C#
[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }
messageStore = new(CurrentEditContext);
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 .
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.
BackgroundWork.razor :
razor
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<PageTitle>Background Work</PageTitle>
<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 = [];
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
}
if (!cts.IsCancellationRequested)
{
cts.Cancel();
}
cts?.Dispose();
resource?.Dispose();
}
if (disposed)
{
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
}
logger.LogInformation("BackgroundResourceMethod: Action on
Resource");
}
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 :
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.
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>.
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é.
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 :
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> :
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> :
razor
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);
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
@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));
}
}
...
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 :
razor
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"
<Virtualize Items="@stringList">
<ItemContent>
<p>
@context
</p>
</ItemContent>
<EmptyContent>
<p>
There are no strings to display.
</p>
</EmptyContent>
</Virtualize>
@code {
private List<string>? stringList;
C#
razor
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
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.
Par exemple, vous pouvez utiliser un attribut tabindex sur le conteneur de défilement :
razor
l’emplacement approprié.
razor
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
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
flex avec flex-direction défini sur column . Assurez-vous que les enfants
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>.
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>
<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();
}
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 :
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.
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.
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 :
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.
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) .
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.
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)]
...
@code {
...
...
forecasts = ...
}
}
ControlRender.razor :
razor
@page "/control-render"
<PageTitle>Control Render</PageTitle>
<label>
<input type="checkbox" @bind="shouldRender" />
Should Render?
</label>
<p>
<button @onclick="IncrementCount">Click me</button>
</p>
@code {
private int currentCount = 0;
private bool shouldRender = true;
Toutefois, il peut être judicieux d’appeler StateHasChanged dans les cas décrits dans les
sections suivantes de cet article :
Considérez le composant CounterState1 suivant, qui met à jour le compte quatre fois
chaque fois que la méthode IncrementCount s’exécute :
CounterState1.razor :
razor
@page "/counter-state-1"
<p>
Current count: @currentCount
</p>
<p>
<button class="btn btn-primary" @onclick="IncrementCount">Click
me</button>
</p>
@code {
private int currentCount = 0;
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
StateHasChanged();
await Task.Delay(1000);
currentCount++;
// Renders here automatically
}
}
CounterState2.razor :
razor
@page "/counter-state-2"
@using System.Timers
@implements IDisposable
<p>
This counter demonstrates <code>Timer</code> disposal.
</p>
<p>
Current count: @currentCount
</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
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 :
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 :
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.
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.
7 Notes
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>
@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" }
};
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>
<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" }
};
Pets3.razor :
razor
@page "/pets-3"
<PageTitle>Pets 3</PageTitle>
<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" }
};
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>
@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" }
};
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)
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é.
Example.razor :
razor
@page "/example"
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
HTML
HTML
<h1 b-3xxtam6d07>
css
/* /Components/Pages/Counter.razor.rz.scp.css */
h1[b-3xxtam6d07] {
color: brown;
}
Release ).
BlazorSample ).
Parent.razor :
razor
@page "/parent"
<div>
<h1>Parent component</h1>
<Child />
</div>
Child.razor :
razor
<h1>Child Component</h1>
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.
Parent.razor :
razor
<div>
<h1>Parent</h1>
<Child />
</div>
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 ).
) 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="..." /> .
XML
<ItemGroup>
<None Update="Components/Pages/Example.razor.css" CssScope="custom-scope-
identifier" />
</ItemGroup>
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
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>
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>
XML
<PropertyGroup>
<DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>
XML
<ScopedCssEnabled>false</ScopedCssEnabled>
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 :
Ressources supplémentaires
Razor Isolation CSS des pages
Isolation CSS MVC
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 :
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.
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.
razor
@code {
private Type componentType = ...;
private IDictionary<string, object> parameters = ...;
}
razor
<button @onclick="Refresh">Refresh</button>
@code {
private DynamicComponent? dc;
private Task Refresh()
{
return (dc?.Instance as IRefreshable)?.Refresh();
}
}
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
SpaceX® SpaceX.razor
ULA® UnitedLaunchAlliance.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
<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"
<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;
ComponentMetadata.cs :
C#
namespace BlazorSample;
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; }
}
DynamicComponent2.razor :
razor
@page "/dynamic-component-2"
<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>
@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;
components[nameof(RocketLabWithWindowSeat)].Parameters["WindowSeat"] =
windowSeat;
}
}
ComponentMetadata.cs :
C#
namespace BlazorSample;
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
<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; }
}
) Important
DynamicComponent3.razor :
razor
@page "/dynamic-component-3"
<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>
<p>
@message
</p>
@code {
private Type? selectedType;
private string? message;
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)
}
}
}
}
};
}
}
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
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 :
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.
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>.
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 :
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
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é.
modèle pour afficher les cellules dont les données n’ont pas été chargées.
PromotionGrid.razor :
razor
@page "/promotion-grid"
@using Microsoft.AspNetCore.Components.QuickGrid
<PageTitle>Promotion Grid</PageTitle>
<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);
7 Notes
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.AddQuickGridEntityFrameworkAdapter();
razor
<QuickGrid Items="..." custom-attribute="somevalue" class="custom-class">
) Important
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.
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.
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.
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>.
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.
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
razor
@using BlazorSample
@using BlazorSample.Components
Components/Routes.razor :
razor
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>
razor
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
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!";
}
Ajoutez une instruction using en haut du fichier pour les composants du projet :
C#
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;
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();
C#
app.MapRazorComponents<App>();
C#
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
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>
@code {
private int currentCount = 0;
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 :
À 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
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.
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.
CLI .NET
Pour plus d’informations sur la commande dotnet add reference , consultez dotnet
add reference (documentation .NET).
C#
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
C#
builder.Services.AddRazorComponents()
.AddInteractiveWebAssemblyComponents();
C#
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(BlazorSample.Client._Imports).Assembly)
;
C#
app.MapRazorComponents<App>()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(BlazorSample.Client._Imports).Assembly)
;
razor
@page "/counter"
@rendermode InteractiveAuto
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
@code {
private int currentCount = 0;
diff
- @rendermode InteractiveAuto
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.
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
CSHTML
HTML
<script src="_framework/blazor.web.js"></script>
7 Notes
Components/App.razor :
razor
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#
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;
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>
@code {
private int currentCount = 0;
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";
}
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
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
Components/Layout/Footer.razor :
razor
Dans le balisage précédent, définissez l’espace réservé {APP TITLE} sur le titre de
l’application. Par exemple :
HTML
Components/Layout/Footer.razor.css :
css
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
}
Components/Layout/NavMenu.razor :
razor
Dans le balisage précédent, définissez l’espace réservé {APP TITLE} sur le titre de
l’application. Par exemple :
HTML
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;
}
.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;
}
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;
}
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>
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
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#
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;
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.
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>
@code {
private int currentCount = 0;
Pour plus d’informations sur les espaces de noms, consultez la section Espaces de noms
de composant.
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#
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.
CSHTML
Par exemple :
CSHTML
@using BlazorSample.Components
Ressources supplémentaires
Prérendu des composants Razor ASP.NET Core
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 :
Tout comme les composants sont des types .NET standard, les composants fournis par
un RCL sont des assemblys .NET normaux.
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 :
razor
@using Microsoft.AspNetCore.Components.Web
XML
<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>
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.
razor
<div class="my-component">
This component is defined in the <strong>ComponentLibrary</strong>
package.
</div>
ConsumeComponent1.razor :
razor
@page "/consume-component-1"
<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
<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';
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.
) Important
Ajoutez une nouvelle feuille de style à la RCL avec une classe extra-style .
css
.extra-style {
border: 2px dashed blue;
padding: 1em;
margin: 1em 0;
background-image: url('extra-background.png');
}
<div class="extra-style">
<p>
This component is defined in the <strong>ComponentLibrary</strong>
package.
</p>
</div>
ConsumeComponent3.razor :
razor
@page "/consume-component-3"
@using ComponentLibrary
<ExtraStyles />
HTML
razor
AdditionalAssemblies="new[] { typeof(ComponentLibrary.Component1).Assembly
}"
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.
razor
<h3>ComponentLibrary.JeepYJ</h3>
<p>
<img alt="Jeep YJ®" src="_content/ComponentLibrary/jeep-yj.png" />
</p>
Jeep.razor :
razor
@page "/jeep"
@using ComponentLibrary
<div style="float:left;margin-right:10px">
<h3>Direct use</h3>
<p>
<img alt="Jeep YJ®" 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>
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
sont :
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 .
) 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.
HTML
<script src="{PATH}/JsCollocation1.razor.js"></script>
razor
@page "/js-collocation-1"
@inject IJSRuntime JS
@if (!string.IsNullOrEmpty(result))
{
<p>
Hello @result!
</p>
}
@code {
private string? result;
{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.
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.
razor
@page "/js-collocation-2"
@implements IAsyncDisposable
@inject IJSRuntime JS
@if (!string.IsNullOrEmpty(result))
{
<p>
Hello @result!
</p>
}
@code {
private IJSObjectReference? module;
private string? result;
{PATH}/JsCollocation2.razor.js :
JavaScript
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
C#
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 .
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
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).
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 :
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.
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.
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.
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 .
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.
implémentés pour fonctionner de la même manière dans les modes de rendu statiques
et interactifs.
razor
<button type="submit">Submit</button>
</EditForm>
@code {
[SupplyParameterFromForm]
public Product? Model { get; set; }
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.
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.
L’exemple de cette section affiche le composant Razor suivant dans une page via JS.
Quote.razor :
razor
@code {
[Parameter]
public string? Text { get; set; }
}
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
C#
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(options =>
{
options.RootComponents.RegisterForJavaScript<Quote>(identifier:
"quote",
javaScriptInitializer: "initializeComponent");
});
C#
builder.RootComponents.RegisterForJavaScript<Quote>(identifier:
"quote",
javaScriptInitializer: "initializeComponent");
wwwroot/jsComponentInitializers.js :
JavaScript
window.initializeComponent = (name, parameters) => {
console.log({ name: name, parameters: parameters });
}
wwwroot/scripts.js :
JavaScript
Une fois le script Blazor chargé, chargez les scripts précédents dans l’application JS :
HTML
Dans l’exemple précédent, l’espace réservé {BLAZOR SCRIPT} est le script Blazor.
HTML
<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
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
JavaScript
...
rootComponent.dispose();
pas utilisés.
En HTML, placez l’élément conteneur cible, quoteContainer2 pour cet exemple :
HTML
<div id="quoteContainer2"></div>
JavaScript
JavaScript
7 Notes
JavaScriptRootComponents.razor
wwwroot/js/jsRootComponentInitializers.js
wwwroot/index.html
7 Notes
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.
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>
@code {
private int currentCount = 0;
C#
using Microsoft.AspNetCore.Components.Web;
C#
using BlazorSample.Components.Pages;
C#
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(options =>
{
options.RootComponents.RegisterCustomElement<Counter>("my-counter");
});
C#
using Microsoft.AspNetCore.Components.Web;
C#
using BlazorSample.Pages;
C#
builder.RootComponents.RegisterCustomElement<Counter>("my-counter");
HTML
<my-counter></my-counter>
7 Notes
Counter.razor :
razor
@page "/counter"
<h1>Counter</h1>
@code {
private int currentCount = 0;
[Parameter]
public int IncrementAmount { get; set; } = 1;
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é.
2 Avertissement
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 :
CLI .NET
CLI .NET
diff
- <Project Sdk="Microsoft.NET.Sdk">
+ <Project Sdk="Microsoft.NET.Sdk.Razor">
RenderMessage.razor :
razor
<h1>Render Message</h1>
<p>@Message</p>
@code {
[Parameter]
public string Message { get; set; }
}
C#
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ConsoleApp1;
Console.WriteLine(html);
7 Notes
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
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 :
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.
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>.
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.
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.
CultureInfo.InvariantCulture est utilisé pour les types de champs suivants ( <input type="
{TYPE}" /> , où l’espace réservé {TYPE} est le type) :
date
number
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 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>
Dans runtimeconfig.json :
JSON
{
"runtimeOptions": {
"configProperties": {
"System.Globalization.Invariant": true
}
}
}
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
<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>
<p>
The following <code><input></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>
<p>
The following <code><input></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;
}
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
XML
<PropertyGroup>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlo
balizationData>
</PropertyGroup>
7 Notes
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" }));
7 Notes
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 ) :
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 ) :
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 ).
HTML
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>
HTML
<script>
Blazor.start({
applicationCulture: 'en-US'
});
</script>
C#
using System.Globalization;
C#
) Important
C#
builder.Services.AddLocalization();
C#
app.UseRequestLocalization("en-US");
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 .
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.
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;
diff
- await builder.Build().RunAsync();
C#
builder.Services.AddLocalization();
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>.
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"),
};
7 Notes
razor
7 Notes
razor
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.
C#
builder.Services.AddLocalization();
C#
app.UseRequestLocalization(localizationOptions);
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
razor
@using System.Globalization
@using Microsoft.AspNetCore.Localization
razor
@code {
[CascadingParameter]
public HttpContext? HttpContext { get; set; }
C#
builder.Services.AddControllers();
C#
app.MapControllers();
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
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"),
};
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 :
razor
razor
razor
@rendermode InteractiveServer
razor
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 .
XML
<PropertyGroup>
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlo
balizationData>
</PropertyGroup>
C#
using System.Globalization;
C#
builder.Services.AddLocalization();
C#
builder.Services.AddLocalization();
C#
app.UseRequestLocalization(localizationOptions);
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
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.
Pages/CultureExample2.resx :
XML
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
Pages/CultureExample2.es.resx :
XML
razor
@using Microsoft.Extensions.Localization
CultureExample2.razor :
razor
@page "/culture-example-2"
@using System.Globalization
@inject IStringLocalizer<CultureExample2> Loc
<p>
<b>CurrentCulture</b>: @CultureInfo.CurrentCulture
</p>
<h2>Greeting</h2>
<p>
@Loc["Greeting"]
</p>
<p>
@greeting
</p>
@code {
private string? greeting;
7 Notes
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;
Localization/SharedResource.resx
Localization/SharedResource.es.resx
7 Notes
LocalizationOptions.
razor
@using Localization
@inject IStringLocalizer<SharedResource> Loc
razor
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)
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 :
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.
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>.
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
@code {
[SupplyParameterFromForm]
public Starship? Model { get; set; }
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) .
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
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
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.
7 Notes
Dans l’exemple suivant, le composant précédent est modifié pour créer le formulaire
dans le composant Starship2 :
Starship2.razor :
razor
@page "/starship-2"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship2> Logger
@code {
[SupplyParameterFromForm]
public Starship? Model { get; set; }
Pour les formulaires basés sur l’élément <form> HTML, ajoutez manuellement le
composant AntiforgeryToken au formulaire :
razor
@if (submitted)
{
<p>Form submitted!</p>
}
@code{
private bool submitted = false;
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)]
razor
<EditForm Enhance ...>
...
</EditForm>
HTML
❌ 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>
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 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)
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 :
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.
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>.
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
InputSelect<TValue> <select>
InputText <input>
InputTextArea <textarea>
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.
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
é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;
[Required]
public string? Classification { get; set; }
[Required]
[Range(typeof(bool), "true", "true", ErrorMessage = "Approval
required.")]
public bool IsValidatedDesign { get; set; }
[Required]
public DateTime ProductionDate { get; set; }
}
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
@code {
[SupplyParameterFromForm]
private Starship? Model { get; set; }
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
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
@code {
private EditContext? editContext;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
// await ...
}
else
{
Logger.LogInformation("Submit called: Form is INVALID");
}
}
}
7 Notes
Starship5.razor :
razor
@page "/starship-5"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship5> Logger
@code {
private EditContext? editContext;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
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 :
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 :
InputDate<TValue>.ParsingErrorMessage
InputNumber<TValue>.ParsingErrorMessage
suivant :
css
razor
<label>
Production Date:
<InputDate @bind-Value="Model!.ProductionDate"
DisplayName="Production Date" />
</label>
razor
<label>
Production Date:
<InputDate @bind-Value="Model!.ProductionDate"
DisplayName="Production Date"
ParsingErrorMessage="The {0} field has an incorrect date value." />
</label>
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 :
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.
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>.
Liaison de données
Affectation à EditForm.Model :
razor
@code {
[SupplyParameterFromForm]
public Starship? Model { get; set; }
Liaison du contexte
Affectation à EditForm.EditContext :
razor
@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 primitifs
Collections
Types complexes
Types récursifs
Types avec constructeurs
Enums
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
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.
HelloFormFromLibrary.razor :
razor
@if (submitted)
{
<p>Hello @Name from the library's form!</p>
}
@code {
bool submitted = false;
[SupplyParameterFromForm]
public string? Name { get; set; }
NamedFormsWithScope.razor :
razor
@page "/named-forms-with-scope"
<FormMappingScope Name="ParentContext">
<HelloFormFromLibrary />
</FormMappingScope>
<div>Hello form using the same form name</div>
@if (submitted)
{
<p>Hello @Name from the app form!</p>
}
@code {
bool submitted = false;
[SupplyParameterFromForm]
public string? Name { get; set; }
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
@code {
[SupplyParameterFromForm(FormName = "Holodeck1")]
public Holodeck? Model1 { get; set; }
[SupplyParameterFromForm(FormName = "Holodeck2")]
public Holodeck? Model2 { get; set; }
ShipDetails.cs :
C#
namespace BlazorSample;
Ship.cs :
C#
namespace BlazorSample
{
public class Ship
{
public string? Id { get; set; }
public ShipDetails Details { get; set; } = new();
}
}
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
@code {
[SupplyParameterFromForm]
public Ship? Model { get; set; }
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#
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;
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
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#
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 :
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.
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>.
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.
Starship8.razor :
razor
@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger
<h2>Holodeck Configuration</h2>
@code {
private EditContext? editContext;
[SupplyParameterFromForm]
public Holodeck? Model { get; set; }
DataAnnotationsValidator
AddDataAnnotationsValidation .
7 Notes
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.
7 Notes
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;
[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }
messageStore = new(CurrentEditContext);
) Important
7 Notes
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.
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
@code {
private CustomValidation? customValidation;
[SupplyParameterFromForm]
public Starship? Model { get; set; }
if (errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else
{
Logger.LogInformation("Submit called: Processing the form");
}
}
}
7 Notes
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.
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
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;
}
[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);
}
}
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.
C#
using Microsoft.AspNetCore.Mvc;
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
Pour plus d’informations sur les réponses d’erreur d’échec de routage et de validation
de contrôleur, consultez les ressources suivantes :
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
@code {
private CustomValidation? customValidation;
private bool disabled;
private string? message;
private string messageStyles = "visibility:hidden";
[SupplyParameterFromForm]
public Starship? Model { get; set; }
try
{
var response = await Http.PostAsJsonAsync<Starship>(
"StarshipValidation", (Starship)editContext.Model);
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) });
7 Notes
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
<div>
CurrentValue: @Model?.Id
</div>
@code {
[SupplyParameterFromForm]
public Starship? Model { get; set; }
razor
<ValidationSummary />
razor
razor
css
.validation-message {
color: red;
}
C#
✔️recommandé :
C#
CustomValidator.cs :
C#
using System;
using System.ComponentModel.DataAnnotations;
La classe SaladChef indique la liste des navires approuvés pour une salade Ten Forward.
SaladChef.cs :
C#
namespace BlazorSample;
C#
builder.Services.AddTransient<SaladChef>();
SaladChefValidatorAttribute.cs :
C#
using System.ComponentModel.DataAnnotations;
namespace BlazorSample;
if (saladChef.SaladToppers.Contains(value?.ToString()))
{
return ValidationResult.Success;
}
Starship12.razor :
razor
@page "/starship-12"
@inject SaladChef SaladChef
@code {
private string? saladToppers;
[SaladChefValidator]
public string? SaladIngredient { get; set; }
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;
Starship13.razor :
razor
@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger
@code {
private EditContext? editContext;
[SupplyParameterFromForm]
public Starship? Model { get; set; }
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;
return string.Empty;
}
}
7 Notes
✔️fieldId.FieldName == "Name"
❌ fieldId.FieldName == "name"
❌ fieldId.FieldName == "NAME"
❌ fieldId.FieldName == "nAmE"
C#
razor
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 :
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
css
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid red;
}
CustomFieldClassProvider3.cs :
C#
using Microsoft.AspNetCore.Components.Forms;
if (fieldIdentifier.FieldName == "Name")
{
return isValid ? "validField" : "invalidField";
}
else
{
if (editContext.IsModified(fieldIdentifier))
{
return isValid ? "modified valid" : "modified invalid";
}
else
{
return isValid ? "valid" : "invalid";
}
}
}
}
C#
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());
À l’aide de CustomFieldClassProvider3 :
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.
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>
Starship.cs :
C#
using System;
using System.ComponentModel.DataAnnotations;
[ValidateComplexType]
public ShipDescription ShipDescription { get; set; } = new();
...
}
ShipDescription.cs :
C#
using System;
using System.ComponentModel.DataAnnotations;
[Required]
[StringLength(240, ErrorMessage = "Description too long (240 char).")]
public string? LongDescription { get; set; }
}
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
@code {
private bool formInvalid = false;
private EditContext? editContext;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
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 .
razor
...
@code {
private string displaySummary = "display:none";
...
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 :
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.
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>.
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.
JavaScript
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
<div>
Length: @TextAreaValue?.Length
</div>
@code {
private ElementReference largeTextArea;
throw;
}
}
}
Vérifiez que le EditForm affecte un Modelou un EditContext. N’utilisez pas les deux pour
le même formulaire.
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. ».
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>.
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.
razor
@code {
private void LoadFiles(InputFileChangeEventArgs e)
{
...
}
}
HTML rendu :
HTML
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.
❌ 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#
C#
✔ 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());
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.
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
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;
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;
}
}
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;
isLoading = false;
}
}
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.
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.
C#
builder.Services.AddHttpClient();
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#
7 Notes
Ne faites pas confiance aux noms de fichiers fournis par les clients pour :
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>
<p>
@message
</p>
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
private string? message;
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,
};
shouldRender = true;
}
return result.Uploaded;
}
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.
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;
[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();
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);
}
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.
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#
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
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 :
C#
builder.Services.AddHttpClient();
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) });
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>
<p>
@message
</p>
@code {
private List<File> files = new();
private List<UploadResult> uploadResults = new();
private int maxAllowedFiles = 3;
private bool shouldRender;
private string? message;
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);
shouldRender = true;
}
return result.Uploaded;
}
Le contrôleur suivant du projet côté serveur enregistre les fichiers chargés depuis le
client.
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;
[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();
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);
}
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.
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;
StateHasChanged();
}
loadedFiles.Add(file);
}
catch (Exception ex)
{
Logger.LogError("File: {FileName} Error: {Error}",
file.Name, ex.Message);
}
}
isLoading = false;
}
}
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.
razor
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
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 :
JavaScript
Enfin, utilisez un IJSRuntime injecté pour ajouter le gestionnaire OnChange qui appelle la
fonction JavaScript :
razor
@inject IJSRuntime JS
...
@code {
...
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.
FileUpload4.razor :
razor
@page "/file-upload-4"
@rendermode InteractiveServer
@inject IJSRuntime JS
@code {
private InputFile? inputFile;
private ElementReference previewImageElem;
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.
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.
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.
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 :
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.
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>.
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 étapes de sécurité qui réduisent la probabilité d’une attaque réussie sont les
suivantes :
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
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
Le composant suivant :
FileDownload1.razor :
razor
@page "/file-download-1"
@using System.IO
@inject IJSRuntime JS
<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;
}
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#
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 :
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!"
<script>
window.triggerFileDownload = (fileName, url) => {
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? '';
anchorElement.click();
anchorElement.remove();
}
</script>
7 Notes
FileDownload2.razor :
razor
@page "/file-download-2"
@inject IJSRuntime JS
<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);
}
}
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 :
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)
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).
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
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 :
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>.
7 Notes
TypeScript
Tutoriel : Créer une application ASP.NET Core avec TypeScript dans Visual Studio
Gérer les packages npm dans Visual Studio
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.
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.
DOMCleanup.razor :
razor
@page "/dom-cleanup"
@implements IAsyncDisposable
@inject IJSRuntime JS
<div id="cleanupDiv"></div>
@code {
private IJSObjectReference? module;
await module.InvokeVoidAsync("DOMCleanup.createObserver");
}
}
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.
JavaScript
static createObserver() {
const target = document.querySelector('#cleanupDiv');
if (targetRemoved) {
// Cleanup resources here
// ...
window.DOMCleanup = DOMCleanup;
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 :
C#
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.
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 :
2 Avertissement
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.
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 :
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.
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
sont :
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 .
) 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.
HTML
<script src="{PATH}/JsCollocation1.razor.js"></script>
razor
@page "/js-collocation-1"
@inject IJSRuntime JS
@if (!string.IsNullOrEmpty(result))
{
<p>
Hello @result!
</p>
}
@code {
private string? result;
{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.
) 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.
razor
@page "/js-collocation-2"
@implements IAsyncDisposable
@inject IJSRuntime JS
@if (!string.IsNullOrEmpty(result))
{
<p>
Hello @result!
</p>
}
@code {
private IJSObjectReference? module;
private string? result;
{PATH}/JsCollocation2.razor.js :
JavaScript
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
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.
HTML
<body>
...
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)} :
HTML
<body>
...
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.
JavaScript
if ({CONDITION}) import("/additionalModule.js");
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 :
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.
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 :
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.
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>.
IJSRuntime.InvokeAsync
JSRuntimeExtensions.InvokeAsync
JSRuntimeExtensions.InvokeVoidAsync
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
Le composant suivant :
CallJs1.razor :
razor
@page "/call-js-1"
@inject IJSRuntime JS
<PageTitle>Call JS 1</PageTitle>
<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;
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.
.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 .
HTML
<script>
window.displayTickerAlert1 = (symbol, price) => {
alert(`${symbol}: $${price}!`);
};
</script>
7 Notes
CallJs2.razor :
razor
@page "/call-js-2"
@inject IJSRuntime JS
<PageTitle>Call JS 2</PageTitle>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
C#
using Microsoft.JSInterop;
namespace BlazorSample;
CallJs3.razor :
razor
@page "/call-js-3"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 3</PageTitle>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
private JsInteropClasses1? jsClass;
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
CallJs4.razor :
razor
@page "/call-js-4"
@inject IJSRuntime JS
<PageTitle>Call JS 4</PageTitle>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
C#
using Microsoft.JSInterop;
namespace BlazorSample;
razor
@page "/call-js-5"
@implements IDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 5</PageTitle>
<p>
<button @onclick="SetStock">Set Stock</button>
</p>
@code {
private Random r = new();
private string? stockSymbol;
private decimal price;
private JsInteropClasses2? jsClass;
private string? result;
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.
7 Notes
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).
PrerenderedInterop1.razor :
razor
@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JS
@code {
private ElementReference divElement;
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
7 Notes
HTML
<script>
window.setElementText2 = (element, text) => {
element.innerText = text;
return text;
};
</script>
2 Avertissement
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>
@code {
private string? data;
private ElementReference divElement;
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
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;
...
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) :
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.
JavaScript
if ({CONDITION}) import("/additionalModule.js");
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
CallJs6.razor :
razor
@page "/call-js-6"
@implements IAsyncDisposable
@inject IJSRuntime JS
<PageTitle>Call JS 6</PageTitle>
<p>
<button @onclick="TriggerPrompt">Trigger browser window prompt</button>
</p>
<p>
@result
</p>
@code {
private IJSObjectReference? module;
private string? result;
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.
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)} :
C#
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) .
Capturez des références à des éléments HTML dans un composant à l’aide de l’approche
suivante :
razor
@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>
JavaScript
window.interopFunctions = {
clickElement : function (element) {
element.click();
}
}
razor
@inject IJSRuntime JS
<button @onclick="TriggerClick">
Trigger click event on <code>Example Button</code>
</button>
@code {
private ElementReference exampleButton;
Pour utiliser une méthode d’extension, créez une méthode d’extension statique qui
reçoit l’instance IJSRuntime :
C#
razor
@inject IJSRuntime JS
@using JsInteropClasses
<button @onclick="TriggerClick">
Trigger click event on <code>Example Button</code>
</button>
@code {
private ElementReference exampleButton;
) Important
Lorsque vous utilisez des types génériques et retournez une valeur, utilisez
ValueTask<TResult> :
C#
GenericMethod est appelé directement sur l’objet avec un type. L’exemple suivant
razor
@inject IJSRuntime JS
@using JsInteropClasses
<p>
returnValue: @returnValue
</p>
@code {
private ElementReference username;
private string? returnValue;
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 :
HTML
<style>
.red { color: red }
</style>
HTML
<script>
function setElementClass(element, className) {
var myElement = element;
myElement.classList.add(className);
}
</script>
7 Notes
razor
@page "/call-js-7"
<PageTitle>Call JS 7</PageTitle>
CallJs7.razor.cs :
C#
using Microsoft.AspNetCore.Components;
namespace BlazorSample.Pages;
subscriptions.Clear();
subscriptions.Add(observer);
razor
<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;
[Parameter]
public IObservable<ElementReference>? Parent { get; set; }
[Inject]
public IJSRuntime? JS {get; set;}
subscription?.Dispose();
subscription = Parent?.Subscribe(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.
C#
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(options =>
options.JSInteropDefaultCallTimeout = {TIMEOUT});
C#
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.
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
<div @ref="unmanagedElement"></div>
@code {
private ElementReference unmanagedElement;
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';
Pour produire un style correct, ajoutez la balise de feuille de style suivante à la page
HTML hôte.
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>
@code
{
private ElementReference mapElement;
private IJSObjectReference? mapModule;
private IJSObjectReference? mapInstance;
<div> avec @ref="mapElement" est laissé vide en ce qui concerne Blazor. Le script
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
<p>
<button @onclick="SendByteArray">Send Bytes</button>
</p>
<p>
@result
</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 {
private string? result;
En JavaScript, utilisez une mémoire tampon de tableau ou un flux lisible pour recevoir
les données :
JavaScript
JavaScript
Dans le code C# :
C#
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.
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 :
CallJs11.razor :
razor
@page "/call-js-11"
@inject IJSRuntime JS
<PageTitle>Call JS 11</PageTitle>
<p>
<button @onclick="CatchUndefinedJSFunction">Catch Exception</button>
</p>
<p>
@result
</p>
<p>
@errorMessage
</p>
@code {
private string? errorMessage;
private string? result;
HTML
<script>
class Helpers {
static #controller = new AbortController();
static stopFn() {
this.#controller.abort();
console.log('longRunningFn aborted!');
}
}
window.Helpers = Helpers;
</script>
7 Notes
Le composant suivant :
CallJs12.razor :
razor
@page "/call-js-12"
@inject IJSRuntime JS
<p>
<button @onclick="StartTask">Start Task</button>
<button @onclick="CancelTask">Cancel Task</button>
</p>
@code {
private CancellationTokenSource? cts;
await JS.InvokeVoidAsync("Helpers.longRunningFn");
}
Console
longRunningFn: 1
longRunningFn: 2
longRunningFn: 3
longRunningFn aborted!
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.
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.
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
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 :
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.
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>.
uniquement.
JavaScript
) Important
Pour les composants côté serveur, nous recommandons la fonction asynchrone
( invokeMethodAsync ) plutôt que la version synchrone ( invokeMethod ).
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.
CallDotnet1.razor :
razor
@page "/call-dotnet-1"
<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 });
}
}
HTML
<script>
window.returnArrayAsync = () => {
DotNet.invokeMethodAsync('BlazorSample', 'ReturnArrayAsync')
.then(data => {
console.log(data);
});
};
</script>
7 Notes
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 ]
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>
C#
[JSInvokable]
public static Task<int[]> ReturnArrayAsync(int startPosition)
{
return Task.FromResult(Enumerable.Range(startPosition, 3).ToArray());
}
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")]
DotNet.invokeMethodAsync('BlazorSample', 'DifferentMethodName');
7 Notes
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#.
JavaScript
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
JavaScript
var jsObjectReference = DotNet.createJSObjectReference(window);
DotNet.disposeJSObjectReference(jsObjectReference);
Dans l’exemple précédent, l’espace réservé {ASSEMBLY NAME} est l’espace de noms de
l’application.
JavaScript
7 Notes
invokeMethodAsync et invokeMethod n’acceptent pas de paramètre de nom
) Important
Supprimez le DotNetObjectReference.
Les sections suivantes de cet article illustrent différentes approches pour appeler une
méthode .NET d’instance :
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;
[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.
HTML
<script>
window.sayHello1 = (dotNetHelper) => {
return dotNetHelper.invokeMethodAsync('GetHelloMessage');
};
</script>
7 Notes
CallDotnet2.razor :
razor
@page "/call-dotnet-2"
@implements IDisposable
@inject IJSRuntime JS
<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);
}
[JSInvokable]
public string GetHelloMessage() => $"Hello, {name}!";
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.
CallDotnet3.razor :
razor
@page "/call-dotnet-3"
@implements IDisposable
@inject IJSRuntime JS
<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;
[JSInvokable]
public string GetHelloMessage(string passedName) => $"Hello,
{passedName}!";
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.
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
<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;
[JSInvokable]
public string GetHelloMessage() => $"Hello, {name}!";
[JSInvokable]
public string GetWelcomeMessage() => $"Welcome, {name}!";
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;
}
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).
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.
GenericType.cs :
C#
using Microsoft.JSInterop;
[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}");
}
}
HTML
<script>
const randomInt = () => Math.floor(Math.random() * 99999);
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
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;
await JS.InvokeVoidAsync(
"invokeMethodsAsync", syncInterop, objRef1, objRef2);
}
Dans l’exemple précédent, JS est une instance IJSRuntime injectée. IJSRuntime est
inscrit par le framework Blazor.
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 ).
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.
HTML
<script>
window.sayHello1 = (dotNetHelper) => {
return dotNetHelper.invokeMethodAsync('GetHelloMessage');
};
</script>
7 Notes
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.
HelloHelper.cs :
C#
using Microsoft.JSInterop;
namespace BlazorSample;
[JSInvokable]
public string GetHelloMessage() => $"Hello, {Name}!";
}
JsInteropClasses3.cs :
C#
using Microsoft.JSInterop;
namespace BlazorSample;
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>
<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;
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
<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;
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
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.
bouton.
MessageUpdateInvokeHelper.cs :
C#
using Microsoft.JSInterop;
namespace BlazorSample;
[JSInvokable]
public void UpdateMessageCaller()
{
action.Invoke();
}
}
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 .
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;
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"
<ul>
<ListItem1 />
<ListItem1 />
<ListItem1 />
<ListItem1 />
</ul>
L’image suivante montre le composant parent rendu une fois le deuxième bouton
InteropCall sélectionné :
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 ).
HTML
<script>
window.assignDotNetHelper = (element, dotNetHelper) => {
element.dotNetHelper = dotNetHelper;
}
</script>
HTML
<script>
window.interopCall = async (element) => {
await element.dotNetHelper.invokeMethodAsync('UpdateMessage');
}
</script>
7 Notes
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> ).
Blazor n’a aucun moyen de savoir que l’interface utilisateur doit être mise à jour lorsque
la méthode est appelée.
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";
[JSInvokable]
public void UpdateMessage()
{
message = "UpdateMessage Called!";
display = "none";
color = "MediumSeaGreen";
StateHasChanged();
}
Le composant parent suivant comprend quatre éléments de liste, chacun étant une
instance du composant ListItem2 .
CallDotnet7.razor :
razor
@page "/call-dotnet-7"
<ul>
<ListItem2 />
<ListItem2 />
<ListItem2 />
<ListItem2 />
</ul>
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 .
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é :
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.
JavaScript
if ({CONDITION}) import("/additionalModule.js");
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
CallDotnet8.razor :
razor
@page "/call-dotnet-8"
@using System.Text
<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.
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);
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
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.
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.
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
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).
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
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.
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.
XML
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
2 Avertissement
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>
@code {
private string? message;
message = GetWelcomeMessage();
}
}
7 Notes
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();
}
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.
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
Func<TResult> Function
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
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
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
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
Le composant CallDotNet1 suivant appelle JS, qui interagit directement avec le DOM
pour afficher la chaîne de message d’accueil :
) Important
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].
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!";
}
}
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 :
CallDotNet1.razor.js :
JavaScript
document.getElementById("result").innerText =
exports.BlazorSample.Components.Pages.CallDotNet1.GetMessageFromDotnet();
}
7 Notes
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
wwwroot/js/interop.js .
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!";
}
}
JavaScript
document.getElementById("result").innerText =
exports.BlazorSample.JavaScriptInterop.Interop.GetMessageFromDotnet();
}
C#
using System.Runtime.InteropServices.JavaScript;
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>
@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
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.
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.
Components/Pages/PageWithScript.razor :
razor
@page "/page-with-script"
@using BlazorPageScript
Welcome to my page.
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
Dans une bibliothèque de classes Razor (RCL) (l’exemple RCL est nommé
BlazorPageScript ), ajoutez le module suivant.
wwwroot/BlazorPageScript.lib.module.js :
JavaScript
function registerPageScriptElement(src) {
if (!src) {
throw new Error('Must provide a non-empty value for the "src"
attribute.');
}
if (pageScriptInfo) {
pageScriptInfo.referenceCount++;
} else {
pageScriptInfo = { referenceCount: 1, module: null };
pageScriptInfoBySrc.set(src, pageScriptInfo);
initializePageScriptModule(src, pageScriptInfo);
}
}
function unregisterPageScriptElement(src) {
if (!src) {
return;
}
pageScriptInfo.referenceCount--;
}
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);
}
}
this.src = newValue;
unregisterPageScriptElement(oldValue);
registerPageScriptElement(newValue);
}
disconnectedCallback() {
unregisterPageScriptElement(this.src);
}
});
blazor.addEventListener('enhancedload', onEnhancedLoad);
}
PageScript.razor :
razor
<page-script src="@Src"></page-script>
@code {
[Parameter]
[EditorRequired]
public string Src { get; set; } = default!;
}
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
razor
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 :
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.
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>.
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 ).
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.
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
@code {
private IEnumerable<GitHubBranch>? branches = Array.Empty<GitHubBranch>
();
private bool getBranchesError;
private bool shouldRender;
if (response.IsSuccessStatusCode)
{
using var responseStream = await
response.Content.ReadAsStreamAsync();
branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
getBranchesError = true;
}
shouldRender = true;
}
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
razor
C#
7 Notes
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
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 :
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.
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>.
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 .
ShowImage1.razor :
razor
@page "/show-image-1"
@code {
private string? imageSource;
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 :
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>
}
suivantes :
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
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
<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");
}
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)
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 :
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.
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>.
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 :
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.
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.
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.
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 .
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.
7 Notes
ClaimsPrincipalData.razor :
razor
@page "/claims-principle-data"
@using System.Security.Claims
@inject AuthenticationStateProvider AuthenticationStateProvider
<h1>ClaimsPrincipal Data</h1>
<p>@authMessage</p>
<p>@surname</p>
@code {
private string? authMessage;
private string? surname;
private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();
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.
CascadeAuthState.razor :
razor
@page "/cascade-auth-state"
<p>@authMessage</p>
@code {
private string authMessage = "The user is NOT authenticated.";
[CascadingParameter]
private Task<AuthenticationState>? authenticationState { get; set; }
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>
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.
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.
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() { ... }
}
2 Avertissement
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.
razor
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>
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.
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.
É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.
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]
) 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>
razor
@page "/"
@attribute [Authorize(Policy = "Over21")]
<p>You can only see this if you satisfy the 'Over21' policy.</p>
HTML
Not authorized.
razor
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
C#
using Microsoft.AspNetCore.Components;
using System.Linq;
Ajouter la stratégie :
C#
if (!string.IsNullOrEmpty(id))
{
return id.StartsWith("EMP",
StringComparison.InvariantCulture);
}
}
return false;
})
);
EditUser.razor :
razor
@page "/users/{id}/edit"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "EditUser")]
<h1>Edit User</h1>
@code {
[Parameter]
public string? Id { get; set; }
}
razor
<Router ...>
<Found ...>
<AuthorizeRouteView ...>
<NotAuthorized>
...
</NotAuthorized>
<Authorizing>
...
</Authorizing>
</AuthorizeRouteView>
</Found>
</Router>
7 Notes
C#
builder.Services.AddCascadingAuthenticationState();
HTML
Not authorized.
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.
« é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
if (user.IsInRole("Admin"))
{
// ...
}
if ((await AuthorizationService.AuthorizeAsync(user,
"content-editor"))
.Succeeded)
{
// ...
}
}
}
}
}
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.
razor
<CascadingAuthenticationState>
<Router ...>
...
</Router>
</CascadingAuthenticationState>
diff
- <CascadingAuthenticationState>
<Router ...>
...
</Router>
- </CascadingAuthenticationState>
C#
builder.Services.AddCascadingAuthenticationState();
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
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 :
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.
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>.
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.
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
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
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.
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) .
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
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.
7 Notes
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;
C#
using Microsoft.AspNetCore.Components.Authorization;
...
builder.Services.AddScoped<AuthenticationStateProvider,
CustomAuthenticationStateProvider>();
razor
<Router ...>
<Found ...>
<AuthorizeRouteView RouteData="@routeData"
DefaultLayout="@typeof(Layout.MainLayout)" />
...
</Found>
</Router>
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.
razor
<AuthorizeView>
<Authorized>
<p>Hello, @context.User.Identity?.Name!</p>
</Authorized>
<NotAuthorized>
<p>You're not authorized.</p>
</NotAuthorized>
</AuthorizeView>
CustomAuthenticationStateProvider.cs :
C#
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
NotifyAuthenticationStateChanged(
Task.FromResult(new AuthenticationState(user)));
}
}
Dans un composant :
razor
<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;
C#
using System.Security.Claims;
C#
builder.Services.AddScoped<AuthenticationService>();
C#
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
NotifyAuthenticationStateChanged(
Task.FromResult(new AuthenticationState(newUser)));
};
}
razor
<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;
AuthenticationService.CurrentUser = newUser;
}
}
ExampleService.cs :
C#
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.
C#
builder.Services.AddScoped<ExampleService>();
InjectAuthStateProvider.razor :
razor
@page "/inject-auth-state-provider"
@inherits OwningComponentBase
@inject AuthenticationStateProvider AuthenticationStateProvider
<p>@message</p>
@code {
private string? message;
private ExampleService? ExampleService { get; set; }
message = await
ExampleService.ExampleMethod(AuthenticationStateProvider);
}
}
7 Notes
7 Notes
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
razor
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.
7 Notes
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.
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.
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.
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.
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.
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.
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.
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.
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 :
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 :
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.
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 :
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.
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.
É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 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.
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é.
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.
7 Notes
Blazor prend en charge la virtualisation intégrée. Pour plus d’informations,
consultez Virtualisation des composants ASP.NET Core Razor.
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 :
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.
7 Notes
CircuitOptions.DisconnectedCircuitMaxRetained
CircuitOptions.DisconnectedCircuitRetentionPeriod
CircuitOptions.JSInteropDefaultCallTimeout
CircuitOptions.MaxBufferedUnacknowledgedRenderBatches
HubConnectionContextOptions.MaximumReceiveMessageSize
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 :
É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 :
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.
razor
<p>Count: @count</p>
@code
{
private int count = 0;
razor
<p>Count: @count</p>
@code
{
private int count = 0;
razor
@code {
private bool isLoading;
private Data[] data = Array.Empty<Data>();
razor
@implements IDisposable
...
@code {
private readonly CancellationTokenSource TokenSource =
new CancellationTokenSource();
if (TokenSource.Token.IsCancellationRequested)
{
return;
}
...
}
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é.
Le framework Blazor prend des mesures pour se protéger contre certaines des menaces
précédentes :
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 :
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 .
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.
Ce conseil s’applique également lors du rendu des liens dans le cadre de l’application :
Pour plus d’informations, consultez Empêcher les attaques par redirection ouverte dans
ASP.NET Core.
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 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:
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.
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
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 )
C#
oidcOptions.Authority = "https://login.microsoftonline.com/a3942615-
d115-4eb7-bc84-9974abcf5064/v2.0/";
oidcOptions.ClientId = "4ba4de56-9cef-45d9-83fa-a4c18f9f5f0f";
C#
oidcOptions.Authority =
"https://login.microsoftonline.com/common/v2.0/";
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.
.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:
C#
#if DEBUG
oidcOptions.ClientSecret = "463471c8c4...137f90d674bc9";
#endif
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;
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
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
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) .
https://localhost/signout-oidc
7 Note
C#
oidcOptions.CallbackPath = new PathString("{PATH}");
oidcOptions.SignedOutCallbackPath = new PathString("{PATH}");
oidcOptions.RemoteSignOutPath = new PathString("{PATH}");
C#
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;
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.
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.
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
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.
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;
C#
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);
CLI .NET
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;
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.
diff
- builder.Services.AddSingleton<IEmailSender<ApplicationUser>,
IdentityNoOpEmailSender>();
+ builder.Services.AddSingleton<IEmailSender<ApplicationUser>, EmailSender>
();
Supprimez le IdentityNoOpEmailSender
( Components/Account/IdentityNoOpEmailSender.cs ) de l’application.
diff
diff
@code {
- private string? emailConfirmationLink;
...
}
C#
builder.Services.ConfigureApplicationCookie(options => {
options.ExpireTimeSpan = TimeSpan.FromDays(5);
options.SlidingExpiration = true;
});
C#
builder.Services.Configure<DataProtectionTokenProviderOptions>(options =>
options.TokenLifespan = TimeSpan.FromHours(3));
7 Notes
7 Notes
CustomTokenProvider.cs :
C#
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
namespace BlazorSample;
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>>();
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
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
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>.
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
Authentifiez l’application comme vous le feriez avec une application Pages ou MVC
Razor standard. Provisionnez et enregistrez les jetons dans l’authentification cookie.
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
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é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#
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
C#
builder.Services.AddHttpClient();
builder.Services.AddScoped<TokenProvider>();
razor
...
@code {
[CascadingParameter]
public HttpContext? HttpContext { get; set; }
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;
C#
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
...
app.MapRazorComponents<App>().RequireAuthorization(
new AuthorizeAttribute
{
AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
})
.AddInteractiveServerRenderMode();
UserService.cs :
C#
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;
C#
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;
...
builder.Services.AddScoped<UserService>();
builder.Services.TryAddEnumerable(
ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());
razor
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#
C#
app.UseMiddleware<UserServiceMiddleware>();
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 :
AuthenticationStateHandler.cs :
C#
public AuthenticationStateHandler(
CircuitServicesAccessor circuitServicesAccessor)
{
this.circuitServicesAccessor = circuitServicesAccessor;
}
C#
builder.Services.AddTransient<AuthenticationStateHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<AuthenticationStateHandler>();
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) .
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).
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.
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.
L’état stocké par l’API Historique offre les avantages suivants pour l’authentification à
distance :
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 :
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]
Authentication.razor :
razor
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@attribute [AllowAnonymous]
razor
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
7 Notes
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 Autres scénarios de sécurité ASP.NET Core Blazor
WebAssembly.
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).
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
...
...
}
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.
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.
Vous trouverez une aide supplémentaire pour la configuration dans les articles suivants :
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é
Les applications autonomes Blazor WebAssembly peuvent être sécurisées avec ASP.NET
Core Identity en suivant les instructions de cet article.
Sur le client, appelez le point de terminaison /register pour inscrire un utilisateur avec
son adresse e-mail et son mot de passe :
C#
C#
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’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.
builder.Services
.AddAuthentication()
.AddBearerToken(IdentityConstants.BearerScheme);
diff
- /login?useCookies=true
+ /login
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
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
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 .
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 :
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>() .
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
L’application utilise les packages NuGet suivants :
Microsoft.AspNetCore.Components.WebAssembly.Authentication
Microsoft.Extensions.Http
Microsoft.AspNetCore.Components.WebAssembly
Microsoft.AspNetCore.Components.WebAssembly.DevServer
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 .
Les composants suivants gèrent les tâches courantes d’authentification des utilisateurs,
utilisant des IAccountManagement services :
CookieAuthenticationStateProviderclasse (Identity/CookieAuthenticationStateProvider.cs
) .
2 Avertissement
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) .
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
Microsoft Edge
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 :
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) .
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
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
à 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>
@code {
[CascadingParameter]
private Task<AuthenticationState>? AuthenticationState { get; set; }
Ressources supplémentaires
Nouveautés de l’identité dans .NET 8
AuthenticationStateProvider service
Négociation entre origines SignalR côté client pour l’authentification
Cet article explique comment sécuriser une application autonome ASP.NET Core Blazor
WebAssembly avec la bibliothèque d’authentification Blazor WebAssembly.
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.
7 Notes
Visual Studio
Une fois que vous avez choisi le modèle Application Blazor WebAssembly, affectez
à Type d’authentification la valeur Comptes individuels.
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"
}
}
7 Notes
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.
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 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
le code et la configuration suivants à l’application avec des valeurs pour les espaces
réservés et autres valeurs de configuration.
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}"
}
}
C#
builder.Services.AddOidcAuthentication(options =>
{
...
options.ProviderOptions.DefaultScopes.Add("{SCOPE URI}");
});
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 :
7 Notes
Composant RedirectToLogin
Le composant RedirectToLogin ( Shared/RedirectToLogin.razor ) :
7 Notes
Composant LoginDisplay
Le composant LoginDisplay ( Shared/LoginDisplay.razor ) est affiché dans le composant
MainLayout ( Shared/MainLayout.razor ) et gère les comportements suivants :
7 Notes
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 :
razor
@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
[Parameter]
public string? Action { get; set; }
}
7 Notes
Journalisation
Cette section s’applique à ASP.NET Core 7.0 ou version ultérieure.
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é.
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).
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
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
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>
<h2>Access token</h2>
<p id="access-token">@AccessToken?.Value</p>
@code {
[CascadingParameter]
private Task<AuthenticationState> AuthenticationState { get; set; }
AccessToken = token;
AuthenticatedUser = state.User;
}
// header.payload.signature
var payload = AccessToken.Value.Split(".")[1];
var base64Payload = payload.Replace('-', '+').Replace('_', '/')
.PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');
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.
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.
7 Notes
CLI .NET
dotnet new blazorwasm -au SingleOrg --client-id "{CLIENT ID}" --tenant-id
"common" -o {PROJECT NAME}
ノ Agrandir le tableau
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.
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.
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.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
});
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
}
}
builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});
C#
options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE
URI}");
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
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 :
7 Notes
Composant RedirectToLogin
Le composant RedirectToLogin ( Shared/RedirectToLogin.razor ) :
7 Notes
7 Notes
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 :
razor
@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@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 ( ? ).
Journalisation
Cette section s’applique à ASP.NET Core dans .NET 7 ou version ultérieure.
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é.
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).
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
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
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>
<h2>Access token</h2>
<p id="access-token">@AccessToken?.Value</p>
@code {
[CascadingParameter]
private Task<AuthenticationState> AuthenticationState { get; set; }
AccessToken = token;
AuthenticatedUser = state.User;
}
// header.payload.signature
var payload = AccessToken.Value.Split(".")[1];
var base64Payload = payload.Replace('-', '+').Replace('_', '/')
.PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');
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
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.
7 Notes
ノ Agrandir le tableau
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.
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.
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.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
});
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
}
}
C#
builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});
C#
options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE
URI}");
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
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 :
7 Notes
Composant RedirectToLogin
Le composant RedirectToLogin ( Shared/RedirectToLogin.razor ) :
7 Notes
Composant LoginDisplay
Le composant LoginDisplay ( Shared/LoginDisplay.razor ) est affiché dans le composant
MainLayout ( Shared/MainLayout.razor ) et gère les comportements suivants :
7 Notes
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 :
razor
@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
[Parameter]
public string? Action { get; set; }
}
7 Notes
Journalisation
Cette section s’applique à ASP.NET Core 7.0 ou version ultérieure.
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é.
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).
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
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
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>
<h2>Access token</h2>
<p id="access-token">@AccessToken?.Value</p>
@code {
[CascadingParameter]
private Task<AuthenticationState> AuthenticationState { get; set; }
AuthenticatedUser = state.User;
}
// header.payload.signature
var payload = AccessToken.Value.Split(".")[1];
var base64Payload = payload.Replace('-', '+').Replace('_', '/')
.PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');
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
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.
Avant de suivre les instructions de cet article, vérifiez que vous avez sélectionné le
répertoire approprié pour le locataire AAD B2C.
7 Notes
Notez le nom du flux utilisateur créé pour les étapes d’inscription et de connexion de
l’application (par exemple B2C_1_signupsignin ).
CLI .NET
ノ Agrandir le tableau
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 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.
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.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAdB2C",
options.ProviderOptions.Authentication);
});
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
}
}
C#
builder.Services.AddMsalAuthentication(options =>
{
...
options.ProviderOptions.DefaultAccessTokenScopes.Add("{SCOPE URI}");
});
C#
options.ProviderOptions.AdditionalScopesToConsent.Add("{ADDITIONAL SCOPE
URI}");
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
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 :
7 Notes
Composant RedirectToLogin
Le composant RedirectToLogin ( Shared/RedirectToLogin.razor ) :
7 Notes
7 Notes
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 :
razor
@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
[Parameter]
public string? Action { get; set; }
}
7 Notes
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.
Journalisation
Cette section s’applique à ASP.NET Core, dans .NET 7 ou une version ultérieure.
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é.
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).
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
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
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>
<h2>Access token</h2>
<p id="access-token">@AccessToken?.Value</p>
@code {
[CascadingParameter]
private Task<AuthenticationState> AuthenticationState { get; set; }
AccessToken = token;
AuthenticatedUser = state.User;
}
// header.payload.signature
var payload = AccessToken.Value.Split(".")[1];
var base64Payload = payload.Replace('-', '+').Replace('_', '/')
.PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');
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
Cet article décrit d’autres scénarios de sécurité pour les applications Blazor
WebAssembly.
7 Notes
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>();
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
...
...
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
TryAddAdditionalParameter
TryRemoveAdditionalParameter
TryGetAdditionalParameter
prompt est défini sur login : force l’utilisateur à entrer ses informations
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");
}
requestOptions.TryAddAdditionalParameter("prompt", "login");
requestOptions.TryAddAdditionalParameter("loginHint",
"[email protected]");
Navigation.NavigateToLogin("authentication/login", requestOptions);
}
}
InteractiveRequestOptions
Liste de paramètres de requête contextuelle
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
C#
try
{
var examples = await Http.GetFromJsonAsync<ExampleType[]>
("ExampleAPIMethod");
...
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect(requestOptions => {
requestOptions.TryAddAdditionalParameter("prompt", "login");
requestOptions.TryAddAdditionalParameter("loginHint",
"[email protected]");
});
}
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 .
InteractiveRequestOptions
Liste de paramètres de requête contextuelle
Personnaliser les options lors de l’utilisation d’un
IAccessTokenProvider
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
C#
Navigation.NavigateToLogin(accessTokenResult.InteractiveRequestUrl,
accessTokenResult.InteractionOptions);
}
InteractiveRequestOptions
Liste de paramètres de requête contextuelle
C#
Navigation.NavigateToLogout("authentication/logout", "goodbye");
C#
var loginPath =
RemoteAuthOptions.Get(Options.DefaultName).AuthenticationPaths.LogInPath;
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 .
C#
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
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.
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
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
...
@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.
C#
using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
...
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;
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; ).
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 .
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>();
razor
...
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.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.
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.AddHttpClient("WebAPI.NoAuthenticationClient",
client => client.BaseAddress = new Uri("https://www.example.com/base"));
razor
...
@code {
protected override async Task OnInitializedAsync()
{
var client =
ClientFactory.CreateClient("WebAPI.NoAuthenticationClient");
...
}
}
7 Notes
Une autre approche pour utiliser le IHttpClientFactory consiste à créer un client typé
pour un accès non authentifié aux points de terminaison anonymes.
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.
razor
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
...
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é.
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 ).
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.
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 :
razor
...
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject IAccessTokenProvider TokenProvider
@inject IJSRuntime JS
@inject NavigationManager Navigation
@code {
public class Profile
{
public string? Name { get; set; }
public string? LastName { get; set; }
}
if (currentQuery.Contains("state=resumeSavingProfile"))
{
User = await JS.InvokeAsync<Profile>("sessionStorage.getItem",
"resumeSavingProfile");
}
}
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;
CounterValue = deserializedState.CounterValue;
}
}
razor
@page "/counter"
@inject StateContainer State
<h1>Counter</h1>
@code {
private int currentCount = 0;
ApplicationAuthenticationState.cs :
C#
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
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; }
RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogOut,
Action))
{
AuthenticationState.Id = Guid.NewGuid().ToString();
await JS.InvokeVoidAsync("sessionStorage.setItem",
AuthenticationState.Id, State.GetStateForLocalStorage());
}
}
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 :
C#
builder.Services.AddMsalAuthentication<ApplicationAuthenticationState>
(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
});
builder.Services.AddSingleton<StateContainer>();
Route Objectif
Dans l’exemple suivant, tous les chemins d’accès sont précédés de /security .
razor
@page "/security/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code{
[Parameter]
public string? Action { get; set; }
}
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";
});
razor
@page "/register"
Vous êtes autorisé à diviser l’interface utilisateur en différentes pages si vous choisissez
de le faire.
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; }
}
ノ 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.
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;
C#
using System.Security.Claims;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
return initialUser;
}
}
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>();
C#
Dans ce scénario :
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 :
C#
using Microsoft.AspNetCore.Authentication.JwtBearer;
...
builder.Services.Configure<JwtBearerOptions>(
JwtBearerDefaults.AuthenticationScheme,
options =>
{
options.Authority += "/v2.0";
});
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 :
2 Avertissement
TypeScript
diff
- <script
src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationServic
e.js"></script>
+ <script src="js/CustomAuthenticationService.js"></script>
7 Notes
) Important
C#
[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; }
}
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 :
C#
Uri.TryCreate(
$"{builder.HostEnvironment.BaseAddress}authentication/login-
callback",
UriKind.Absolute, out var redirectUri);
options.RedirectUri = redirectUri;
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
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 l’article fournissent des instructions pour les applications clients et
serveurs :
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 :
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).
7 Notes
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
CustomUserAccount.cs :
C#
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
namespace BlazorSample;
[JsonPropertyName("wids")]
public List<string>? Wids { get; set; }
[JsonPropertyName("oid")]
public string? Oid { get; set; }
}
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 .
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;
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();
var requestMemberOf =
client.Users[account?.Oid].MemberOf;
var memberships = await
requestMemberOf.Request().GetAsync();
return initialUser;
}
}
TransitiveMemberOf ( IUserTransitiveMemberOfCollectionWithReferencesRequestBuilder ).
C#
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
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#
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.
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>
@code {
private string policyMessage = "Check hasn't been made yet.";
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
if ((await AuthorizationService.AuthorizeAsync(user,
"BillingAdministrator")).Succeeded)
{
policyMessage = "Yes! The 'BillingAdministrator' policy is
met.";
}
else
{
policyMessage = "No! 'BillingAdministrator' policy is NOT met.";
}
}
}
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).
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
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.
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
Pour attribuer un rôle à un utilisateur (ou à un groupe si vous disposez d’un compte
Azure de niveau Premium) :
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).
C#
builder.Services.AddMsalAuthentication(options =>
{
...
options.UserOptions.RoleClaim = "appRole";
});
7 Notes
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
7 Notes
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">
razor
Logique procédurale
C#
if (user.IsInRole("Admin")) { ... }
Exiger que l’utilisateur soit dans le rôle Admin ou Developer avec le composant
AuthorizeView :
razor
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
Exiger que l’utilisateur soit à la fois dans les rôles Admin et Developer avec
l’attribut [Authorize] :
razor
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#
C#
[Authorize(Roles = "Admin")]
Logique procédurale
C#
if (User.IsInRole("Admin")) { ... }
Exiger que l’utilisateur soit dans le rôle Admin ou Developer avec l’attribut
[Authorize] :
C#
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#
É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
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.
) 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 ).
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;
services.Configure<RemoteAuthenticationOptions<MsalProviderOptions>>
(
options =>
{
scopes?.ForEach((scope) =>
{
options.ProviderOptions.DefaultAccessTokenScopes.Add(scope);
});
});
services.AddScoped<IAuthenticationProvider,
GraphAuthenticationProvider>();
services.AddScoped(sp =>
{
return new GraphServiceClient(
baseUrl,
sp.GetRequiredService<IAuthenticationProvider>(),
sp.GetRequiredService<IHttpProvider>());
});
return services;
}
Dans le fichier Program , ajoutez les services clients et la configuration Graph avec la
méthode d’extension AddGraphClient :
C#
builder.Services.AddGraphClient(baseUrl, scopes);
GraphExample.razor :
razor
@page "/graph-example"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Graph
@attribute [Authorize]
@inject GraphServiceClient Client
@if (!string.IsNullOrEmpty(user?.MobilePhone))
{
<p>Mobile Phone: @user.MobilePhone</p>
}
@code {
private Microsoft.Graph.User? user;
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.
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;
return initialUser;
}
}
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#
builder.Services.AddGraphClient(baseUrl, scopes);
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>();
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)
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 :
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.
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.
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.
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 :
Application de la stratégie
Utilisez une balise <meta> pour appliquer la stratégie :
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.
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;">
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;">
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.
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.
razor
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
razor
@if (!Env.IsDevelopment())
{
<HeadContent>
<meta ...>
</HeadContent>
}
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.
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 .
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
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 :
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.
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>.
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.
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.
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 :
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 :
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 :
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é :
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 :
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.
7 Notes
ProtectedLocalStorage
ProtectedSessionStorage
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.
C#
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.
C#
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.
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#
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 :
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.
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
razor
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;
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; }
7 Notes
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.
razor
razor
<CounterStateProvider>
<Router ...>
...
</Router>
</CounterStateProvider>
razor
@page "/counter"
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
StateContainer.cs :
C#
C#
builder.Services.AddSingleton<StateContainer>();
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>
<button @onclick="ChangePropertyValue">
Change the Property from the Nested component
</button>
</p>
@code {
protected override void OnInitialized()
{
StateContainer.OnChange += StateHasChanged;
}
StateContainerExample.razor :
razor
@page "/state-container-example"
@implements IDisposable
@inject StateContainer StateContainer
<h1>State Container Example component</h1>
<p>
<button @onclick="ChangePropertyValue">
Change the Property from the State Container Example component
</button>
</p>
<Nested />
@code {
protected override void OnInitialized()
{
StateContainer.OnChange += StateHasChanged;
}
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 :
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
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.
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.
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
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.
JSON
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-
proxy?browser={browserInspectUri}"
La propriété inspectUri :
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.
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’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.
Components/Pages/Counter2.razor :
razor
@page "/counter-2"
@rendermode InteractiveServer
<PageTitle>Counter 2</PageTitle>
<h1>Counter 2</h1>
@code {
private int currentCount = 0;
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
en cours d’exécution :
JSON
{
"name": "Attach and Debug",
"type": "blazorwasm",
"request": "attach",
"url": "{URL}"
}
ノ 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 .
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.
webRoot Spécifie le chemin absolu du serveur web. Doit être défini si une application est servie à
partir d’un sous-itinéraire.
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 :
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.
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 :
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
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.
OnInitialized:
C#
protected override void OnInitialized()
{
#if DEBUG
Thread.Sleep(10000);
#endif
...
}
OnInitializedAsync :
C#
...
}
Console
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.
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.
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.
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.
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
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();
7 Notes
Exemple complet
La démonstration de cette section effectue les opérations suivantes :
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
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;
return true;
}
}
Robot.razor :
razor
@page "/robot"
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.Extensions.Logging
@inject ILogger<Robot> Logger
<h1>Robot</h1>
<p>
@message
</p>
@code {
private RobotModel robotModel = new() { AxisSelection = Axis.Left };
private string? message;
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>
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>.
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…</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();
rendu. Vous pouvez inspecter le chargement de l’assembly dans l’onglet Réseau des
outils pour développeurs du navigateur.
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
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.
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 )
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).
Test.c :
int fact(int n)
{
if (n == 0) return 1;
return n * fact(n - 1);
}
XML
<ItemGroup>
<NativeFileReference Include="Test.c" />
</ItemGroup>
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.
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
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 à
CLI .NET
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 .
Pages/NativeDependencyExample.razor :
razor
@page "/native-dependency-example"
@using SkiaSharp
@using SkiaSharp.Views.Blazor
<PageTitle>Native dependency</PageTitle>
@code {
private void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.White);
Ressources supplémentaires
.NET WebAssembly Build Tools
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.
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
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; }
prevInboundFlightId = InboundFlight?.FlightId ?? 0;
prevOutboundFlightId = OutboundFlight?.FlightId ?? 0;
}
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.
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.
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 :
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.
À 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.
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>
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
@code {
private RenderFragment RenderWelcomeInfo = @<p>Welcome to your new app!
</p>;
}
razor
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.
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>;
}
C#
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
Pour plus d’informations sur les paramètres de type générique ( @typeparam ), consultez
les ressources suivantes :
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
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.
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; }
return base.SetParametersAsync(ParameterView.Empty);
}
}
à 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>
@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();
}
await JS.InvokeVoidAsync("onThrottledMouseMove",
mouseMoveElement, selfReference, minInterval);
}
}
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>
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.
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;
Task IHandleEvent.HandleEventAsync(
EventCallbackWorkItem callback, object? arg) =>
callback.InvokeAsync(arg);
}
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;
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;
Logger.LogInformation(
"This event handler doesn't trigger a rerender. " +
"Mouse coordinates: {ScreenX}:{ScreenY}",
args.ScreenX, args.ScreenY);
}
}
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>
<p>
<button @onclick="@(e => UpdateHeading(e, buttonNumber))">
Button #@i
</button>
</p>
}
@code {
private string heading = "Select a button to learn its position";
LambdaEventPerformance.razor :
razor
@page "/lambda-event-performance"
@rendermode InteractiveServer
<h1>@heading</h1>
@code {
private string heading = "Select a button to learn its position";
button.Id = Guid.NewGuid().ToString();
Buttons.Add(button);
}
}
En outre, pour les applications Blazor côté serveur, ces appels sont passés sur le réseau.
C#
C#
JavaScript
function storeAllInLocalStorage(items) {
items.forEach(item => {
localStorage.setItem(item.id, JSON.stringify(item));
});
}
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;
...
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 .
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.
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.
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.
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>
Pour tester un composant Razor, le composant en cours de test (CUT, Component Under
Test) est :
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.
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.
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.
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 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.
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 :
7 Notes
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>
@code {
private int currentCount = 0;
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");
}
}
C#
// Act
cut.Find("button").Click();
// Assert
var paraElmText = paraElm.TextContent;
paraElmText.MarkupMatches("Current count: 1");
}
}
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
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.
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 :
XML
...
<ServiceWorkerAssetsManifest>service-worker-
assets.js</ServiceWorkerAssetsManifest>
</PropertyGroup>
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 :
CLI .NET
version 5.0 :
CLI .NET
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.
7 Notes
À 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
HTML
HTML
...
<script>navigator.serviceWorker.register('service-worker.js');
</script>
</body>
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 .
) Important
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.
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 .
Pour partager la logique entre les deux fichiers de service worker, envisagez l’approche
suivante :
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.
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.
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.
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
JavaScript
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 .
JavaScript
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.
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
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.
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.
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.
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.
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.
É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.
Quand une application conçue pour une utilisation en ligne et hors connexion est de
nouveau en ligne :
Pour créer une application PWA exécutable hors connexion qui interagit avec
l’authentification :
OfflineAccountClaimsPrincipalFactory
( Client/Data/OfflineAccountClaimsPrincipalFactory.cs )
LocalVehiclesStore ( Client/Data/LocalVehiclesStore.cs )
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
Publier l’application
Les applications sont publiées pour le déploiement dans la configuration Release.
Visual Studio
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
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 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.
Arrière-plan
La destination d’une balise d’ancrage (href ) peut être composée d’un des deux points
de terminaison suivants :
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
Il existe trois sources de liens qui se rapportent à Blazor dans les applications ASP.NET
Core :
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
Il existe deux approches pour résoudre correctement le problème des liens relatifs :
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 :
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.
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#
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 :
HTML
<base href="/CoolApp/">
C#
app.UsePathBase("/CoolApp");
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"
}
HTML
<base href="/CoolApp/">
7 Notes
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 ( ./ ) :
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 ( / ) :
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");
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
Pour une application Blazor WebAssembly avec le chemin URL relatif /CoolApp/ ( <base
href="/CoolApp/"> ), la commande est :
CLI .NET
JSON
JSON
"commandLineArgs": "--pathbase=/CoolApp",
"launchUrl": "CoolApp",
JSON
{
"AppBasePath": "staging/"
}
Dans une application Blazor côté serveur, chargez le chemin de base à partir de la
configuration dans le contenu <head> :
razor
...
<head>
...
<base href="/@(Config.GetValue<string>("AppBasePath"))" />
...
</head>
C#
app.UsePathBase($"/{app.Configuration.GetValue<string>("AppBasePath")}");
diff
razor
...
<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 / .
Dans les scénarios où une application nécessite une zone distincte avec des ressources
et des composants Razor personnalisés :
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
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 .
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 :
Cet article explique comment héberger et déployer une application Blazor côté serveur
avec ASP.NET Core.
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 :
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.
Serveur
) Important
C#
builder.Services.AddRazorComponents().AddInteractiveServerComponents()
.AddHubOptions(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});
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
Dans l’exemple suivant, une valeur personnalisée de 60 secondes est utilisée pour
le délai d’expiration du serveur.
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#
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);
await hubConnection.StartAsync();
}
) 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.
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;
});
Dans appsettings.json :
JSON
"Azure:SignalR:ServerStickyMode": "Required"
7 Notes
L’erreur suivante est générée par une application qui n’a pas activé les sessions
persistantes Azure SignalR Service :
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.
7 Notes
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 :
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 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();
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"
Pour plus d’informations et pour obtenir de l’aide sur la configuration, consultez les
ressources suivantes :
ProxyPreserveHost On
ProxyPassMatch ^/_blazor/(.*) http://localhost:5000/_blazor/$1
ProxyPass /_blazor ws://localhost:5000/_blazor
ProxyPass / http://localhost:5000/
ProxyPassReverse / http://localhost:5000/
a2enmod proxy
a2enmod proxy_wstunnel
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
MeasureLatency.razor :
razor
@inject IJSRuntime JS
<h2>Measure Latency</h2>
@code {
private DateTime startTime;
private TimeSpan? latency;
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).
(Circuits actifs × mémoire par circuit) + (Circuits déconnectés × mémoire par circuit)
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.
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.
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.
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.
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.
Pour obtenir des conseils sur l’installation des outils de génération de WebAssembly de
.NET, consultez Outils pour ASP.NET Core Blazor.
WebAssembly :
XML
<PropertyGroup>
<RunAOTCompilation>true</RunAOTCompilation>
</PropertyGroup>
CLI .NET
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 :
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
XML
<PropertyGroup>
<RunAOTCompilation>true</RunAOTCompilation>
<WasmStripILAfterAOT>true</WasmStripILAfterAOT>
</PropertyGroup>
XML
<WasmStripILAfterAOT>false</WasmStripILAfterAOT>
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
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 :
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 :
7 Notes
HTML
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>
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>
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
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
É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.
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.
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.
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.
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.
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
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
XML
<PropertyGroup>
<PublishIISAssets>true</PublishIISAssets>
</PropertyGroup>
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
XML
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
XML
<handlers>
<remove name="aspNetCore" />
</handlers>
XML
7 Notes
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 .
Pour plus d’informations sur les fichiers personnalisés web.config , consultez la section
Utiliser un web.config personnalisé.
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 :
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.
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;
}
}
}
http {
server {
...
location / {
...
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
<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>
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
à 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
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
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.
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.
CLI .NET
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).
) Important
CLI .NET
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.
CLI .NET
JSON
"commandLineArgs": "--urls=http://127.0.0.1:0"
Console
--urls=http://127.0.0.1:0
7 Notes
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.
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.
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
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-
PowerShell
Dans la commande précédente, l’espace réservé {PATH} est le chemin d’accès au fichier
service-worker-assets.js publié.
Console
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-
Console
Dans la commande précédente, l’espace réservé {PATH} est le chemin d’accès au fichier
service-worker-assets.js publié.
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
7 Notes
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" />
diff
Supprimez les entrées des fichiers .dll compressés si la compression est en cours
d’utilisation :
diff
Pour plus d’informations sur les fichiers personnalisés web.config , consultez la section
Utiliser un web.config personnalisé.
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.
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.
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 :
7 Notes
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.
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 :
Toutefois, le serveur web ne peut pas modifier les données non compressées.
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.
PowerShell
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
7 Notes
C:\Users\
{USER}\Documents\GitHub\AspNetCore.Docs\aspnetcore\blazor\host-and-
deploy\webassembly\_samples\integrity.ps1
SHA256 :
32c24cb667d79a701135cb72f6bae490d81703323f61b8af2c7e5e5dc0f0c2bb
MD5 : 9cee7d7ec86ee809a329b5406fbf21a8
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.
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>
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é :
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é.
JavaScript
JavaScript
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.
) Important
C#
builder.Services.AddSignalR(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});
Client
) 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.
C#
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);
await hubConnection.StartAsync();
}
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 :
7 Notes
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 :
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 :
2 Avertissement
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 :
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 Avertissement
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 :
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 :
Blazor.
BundlePath : chemin où le bundle est écrit.
Extension : nouvelles extensions de publication à inclure dans la build.
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; }
bundle.CopyToAsync(output).ConfigureAwait(false).GetAwaiter()
.GetResult();
output.Flush(true);
}
return true;
}
}
}
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
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 :
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
Ajoutez un fichier .targets pour connecter la tâche MSBuild au pipeline de build. Dans
ce fichier, les objectifs suivants sont atteints :
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
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>
Les initialiseurs JS :
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
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);
}
}
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#
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 ).
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.
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>.
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.
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"
}
}
}
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 .
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#
C#
if (Loading)
{
return;
}
try
{
Loading = true;
...
}
finally
{
Loading = false;
}
Placez les opérations après la ligne Loading = true; dans le bloc try .
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.
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 .
razor
razor
Filters.Loading = false;
await ReloadAsync();
}
7 Notes
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#
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#
try
{
Context = DbFactory.CreateDbContext();
await base.OnInitializedAsync();
}
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 :
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.
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.
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>
2 Avertissement
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 :
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.
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>.
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; }
}
BuiltContent.razor :
razor
@page "/built-content"
<PageTitle>Built Content</PageTitle>
<div>
@CustomRender
</div>
<button @onclick="RenderComponent">
Create three Pet Details components
</button>
@code {
private RenderFragment? CustomRender { get; set; }
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.
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.
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
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
C#
var seq = 0;
if (someFlag)
{
builder.AddContent(seq++, "First");
}
builder.AddContent(seq++, "Second");
suivant.
ノ Agrandir le tableau
Cette fois, l’algorithme différentiel constate que deux modifications se sont produites.
L’algorithme génère le script de modification suivant :
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 .
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 :
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 :
aspnetcore-https.js
proxy.conf.js
package.json (modifié)
angular.json (modifié)
app.components.ts
app.module.ts
7 Notes
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 :
7 Notes
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.
2. Choisissez OK.
3. Cliquez de nouveau avec le bouton droit sur le projet ASP.NET Core et sélectionnez
Modifier le fichier projet.
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.
C#
app.UseDefaultFiles();
app.UseStaticFiles();
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.
Dépannage
Erreur proxy
L'erreur suivante peut apparaître :
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.
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 :
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 )
vite.config.js
App.js (modifié)
App.test.js (modifié)
7 Notes
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 :
Interface CLI Vite affichant un message de type VITE v4.4.9 ready in 780 ms
7 Notes
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.
2. Choisissez OK.
3. Cliquez de nouveau avec le bouton droit sur le projet ASP.NET Core et sélectionnez
Modifier le fichier projet.
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.
C#
app.UseDefaultFiles();
app.UseStaticFiles();
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.
Dépannage
Erreur proxy
L'erreur suivante peut apparaître :
JavaScript
target: 'https://localhost:7183/',
Erreur de confidentialité
Vous pouvez voir l’erreur de certificat suivante :
É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 :
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 :
vite.config.json (modifié)
HelloWorld.vue (modifié)
package.json (modifié)
7 Notes
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 :
7 Notes
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.
3. Cliquez de nouveau avec le bouton droit sur le projet ASP.NET Core et sélectionnez
Modifier le fichier projet.
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.
C#
app.UseDefaultFiles();
app.UseStaticFiles();
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.
Dépannage
Erreur proxy
L'erreur suivante peut apparaître :
Erreur de confidentialité
Vous pouvez voir l’erreur de certificat suivante :
JavaScript
target: 'https://localhost:7142/',
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 :
JSON
"httpPort": 5175,
"sslPort": 5173
É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.
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
Les modèles Visual Studio de création d’applications ASP.NET Core avec un front-end
JavaScript ou TypeScript offrent les avantages suivants :
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
Les modèles Visual Studio de création d’applications ASP.NET Core avec un front-end
JavaScript ou TypeScript offrent les avantages suivants :
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
Les modèles Visual Studio de création d’applications ASP.NET Core avec un front-end
JavaScript ou TypeScript offrent 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.
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
Prérequis
Dernier Kit de développement logiciel (SDK) .NET
Installation
La commande suivante permet d’installer LibMan :
CLI .NET
7 Notes
Utilisation
Console
libman
Console
libman --version
Console
libman --help
Console
1.0.163+g45474d37ed
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
Synopsis
Console
Options
Les options suivantes sont disponibles pour la commande libman init :
-d|--default-destination <PATH>
-p|--default-provider <PROVIDER>
cdnjs
filesystem
jsdelivr
unpkg
-h|--help
--verbosity <LEVEL>
Exemples
Pour créer un fichier libman.json dans un projet ASP.NET Core :
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
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
Arguments
LIBRARY
Options
Les options suivantes sont disponibles pour la commande libman install :
-d|--destination <PATH>
--files <FILE>
-p|--provider <PROVIDER>
cdnjs
filesystem
jsdelivr
unpkg
-h|--help
--verbosity <LEVEL>
detailed
Exemples
Examinons le fichier libman.json suivant :
JSON
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": []
}
JSON
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "[email protected]",
"destination": "wwwroot/scripts/jquery",
"files": [
"jquery.min.js"
]
}
]
}
Console
JSON
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "[email protected]",
"destination": "wwwroot/scripts/jquery",
"files": [
"jquery.min.js"
]
},
{
"library": "C:\\temp\\contosoCalendar\\",
"provider": "filesystem",
"destination": "wwwroot/lib/contosoCalendar",
"files": [
"calendar.js",
"calendar.css"
]
}
]
}
Synopsis
Console
Options
Les options suivantes sont disponibles pour la commande libman restore :
-h|--help
--verbosity <LEVEL>
detailed
Exemples
Pour restaurer les fichiers de bibliothèque définis dans libman.json :
Console
libman restore
Synopsis
Console
Options
Les options suivantes sont disponibles pour la commande libman clean :
-h|--help
--verbosity <LEVEL>
normal
detailed
Exemples
Pour supprimer les fichiers de bibliothèque installés via LibMan :
Console
libman clean
Si plusieurs bibliothèques portant le même nom sont installées, vous êtes invité à en
choisir une.
Synopsis
Console
Arguments
LIBRARY
Options
Les options suivantes sont disponibles pour la commande libman uninstall :
-h|--help
--verbosity <LEVEL>
normal
detailed
Exemples
Examinons le fichier libman.json suivant :
JSON
{
"version": "1.0",
"defaultProvider": "cdnjs",
"libraries": [
{
"library": "[email protected]",
"files": [
"jquery.min.js",
"jquery.js",
"jquery.min.map"
],
"destination": "wwwroot/lib/jquery/"
},
{
"provider": "unpkg",
"library": "[email protected]",
"destination": "wwwroot/lib/bootstrap/"
},
{
"provider": "filesystem",
"library": "C:\\temp\\lodash\\",
"files": [
"lodash.js",
"lodash.min.js"
],
"destination": "wwwroot/lib/lodash/"
}
]
}
Console
Console
Console
libman uninstall C:\temp\lodash\
Si plusieurs bibliothèques portant le même nom sont installées, vous êtes invité à en
choisir une.
Synopsis
Console
Arguments
LIBRARY
Options
Les options suivantes sont disponibles pour la commande libman update :
-pre
--to <VERSION>
-h|--help
Afficher les informations d’aide.
--verbosity <LEVEL>
normal
detailed
Exemples
Pour mettre à jour jQuery vers la dernière version :
Console
Console
Console
Synopsis
Console
Arguments
PROVIDER
cdnjs
filesystem
jsdelivr
unpkg
Options
Les options suivantes sont disponibles pour la commande libman cache :
--files
--libraries
-h|--help
--verbosity <LEVEL>
detailed
Exemples
Pour afficher les noms des bibliothèques mises en cache par fournisseur, utilisez
l’une des commandes suivantes :
Console
Console
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
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.
Console
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
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
Visual Studio prend en charge LibMan dans les projets ASP.NET Core, avec notamment :
Prérequis
Visual Studio 2019 avec la charge de travail ASP.NET et développement web
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é.
Console
† 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 :
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": "[email protected]",
"files": [
"jquery.min.js",
"jquery.js",
"jquery.min.map"
],
"destination": "wwwroot/lib/jquery/"
},
{
"provider": "unpkg",
"library": "[email protected]",
"destination": "wwwroot/lib/bootstrap/"
},
{
"provider": "filesystem",
"library": "C:\\temp\\lodash\\",
"files": [
"lodash.js",
"lodash.min.js"
],
"destination": "wwwroot/lib/lodash/"
}
]
}
7 Notes
Les fichiers de bibliothèque peuvent être restaurés dans un projet ASP.NET Core de
deux manières :
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" />
Passez en revue le flux Build de la fenêtre Sortie pour un journal d’activité LibMan :
Console
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.
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.
Console
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
Ouvrez libman.json .
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> :