Vue d’ensemble des applications monopages (SPA) dans ASP.NET Core
Remarque
Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.
Avertissement
Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la Stratégie de prise en charge de .NET et .NET Core. Pour la version actuelle, consultez la version .NET 8 de cet article.
Important
Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.
Pour la version actuelle, consultez la version .NET 8 de cet article.
Visual Studio fournit des modèles de projet pour la création d’applications monopages (SPA) basées sur des infrastructures JavaScript telles que Angular, React et Vue qui ont un back-end ASP.NET Core. Ces modèles :
- Créent une solution Visual Studio avec un projet front-end et un projet back-end.
- Utilisent le type de projet Visual Studio pour JavaScript et TypeScript (.esproj) pour le front-end.
- Utilisent un projet ASP.NET Core pour le back-end.
Les projets créés à l’aide des modèles Visual Studio peuvent être exécutés à partir de la ligne de commande sur Windows, Linux et macOS. Pour exécuter l’application, utilisez dotnet run --launch-profile https
pour exécuter le projet de serveur. L’exécution du projet de serveur démarre automatiquement le serveur de développement JavaScript frontal. Le profil de lancement https
est actuellement requis.
Tutoriels Visual Studio
Pour commencer, suivez l’un des tutoriels de la documentation Visual Studio :
- Créer une application ASP.NET Core avec Angular
- Créer une application ASP.NET Core avec React
- Créer une application ASP.NET Core avec Vue
Pour plus d’informations, consultez JavaScript et TypeScript dans Visual Studio
Modèles SPA ASP.NET Core
Visual Studio inclut des modèles de création d’applications ASP.NET Core avec un front-end JavaScript ou TypeScript. Ces modèles sont disponibles dans Visual Studio 2022 version 17.8 ou ultérieure sur laquelle est installée la charge de travail ASP.NET et développement web.
Les modèles Visual Studio de création d’applications ASP.NET Core avec un front-end JavaScript ou TypeScript offrent les avantages suivants :
- Nettoyer la séparation de projet du front-end et du back-end.
- Rester à jour des dernières versions de l’infrastructure front-end.
- Intégrer le dernier outillage de commande en ligne de l’infrastructure front-end, tels que Vite.
- Modèles pour JavaScript et TypeScript (uniquement TypeScript pour Angular).
- Expérience de modification de code JavaScript et TypeScript enrichie.
- Intégrer des outils de génération JavaScript au build .NET.
- IU de gestion des dépendances npm.
- Compatible avec le débogage et la configuration de lancement de Visual Studio Code.
- Exécuter des tests unitaires front-end dans l’Explorateur de tests à l’aide d’infrastructures de tests JavaScript.
Modèles SPA ASP.NET Core hérités
Les versions antérieures du kit de développement logiciel (SDK) .NET incluaient ce que sont désormais des modèles hérités de création d’applications SPA avec ASP.NET Core. Pour obtenir une documentation sur ces modèles plus anciens, consultez la version ASP.NET Core 7.0 de l’Aperçu SPA et les articles Angular et React.
Architecture des modèles d’application monopage
Les modèles d’application monopage (SPA) pour Angular et React offrent la possibilité de développer des applications Angular et React hébergées dans un serveur principal .NET.
Au moment de la publication, les fichiers de l’application Angular et React sont copiés dans le dossier wwwroot
et sont servis via l’intergiciel de fichiers statiques.
Au lieu de renvoyer HTTP 404 (introuvable), un itinéraire de secours gère les requêtes inconnues au back-end et sert le index.html
pour la SPA.
Pendant le développement, l’application est configurée pour utiliser le proxy frontal. React et Angular utilisent le même proxy frontal.
Lorsque l’application est lancée, la page index.html
est ouverte dans le navigateur. Intergiciel spécial uniquement activé dans le développement :
- Intercepte les requêtes entrantes.
- Vérifie si le proxy est en cours d’exécution.
- Redirige vers l’URL du proxy s’il est en cours d’exécution ou lance une nouvelle instance du proxy.
- Retourne une page du navigateur qui s’actualise automatiquement toutes les quelques secondes jusqu’à ce que le proxy soit en place et que le navigateur soit redirigé.
Le principal avantage fourni par les modèles SPA ASP.NET Core :
- Lance un proxy s’il n’est pas déjà en cours d’exécution.
- Configuration de HTTPS.
- Configuration de certaines demandes pour être transmises au serveur principal ASP.NET Core.
Lorsque le navigateur envoie une requête pour un point de terminaison principal, par exemple /weatherforecast
dans les modèles. Le proxy de la SPA reçoit la requête et la renvoie au serveur en toute transparence. Le serveur répond et le proxy de la SPA renvoie la requête au navigateur :
Applications monopages publiées
Lorsque l’application est publiée, la SPA devient une collection de fichiers dans le dossier wwwroot
.
Aucun composant runtime n’est requis pour servir l’application :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
app.MapFallbackToFile("index.html");
app.Run();
Dans le fichier Program.cs
généré par le modèle précédent :
app.
UseStaticFiles permet aux fichiers d’être traités.app.
MapFallbackToFile("index.html")
permet de servir le document par défaut pour toute requête inconnue que le serveur reçoit.
Lorsque l’application est publiée avec dotnet publish, les tâches suivantes dans le fichier csproj
garantissent que npm restore
s’exécute et que le script npm approprié s’exécute pour générer les artefacts de production :
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)build\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>wwwroot\%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>
Développement d’applications monopages
Le fichier projet définit quelques propriétés qui contrôlent le comportement de l’application pendant le développement :
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<SpaProxyServerUrl>https://localhost:44414</SpaProxyServerUrl>
<SpaProxyLaunchCommand>npm start</SpaProxyLaunchCommand>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SpaProxy" Version="7.0.1" />
</ItemGroup>
<ItemGroup>
<!-- Don't publish the SPA source files, but do show them in the project files list -->
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)build\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>wwwroot\%(RecursiveDir)%(FileName)%(Extension)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>
SpaProxyServerUrl
: Contrôle l’URL où le serveur s’attend à ce que le proxy de la SPA s’exécute. Il s’agit de l’URL :- Le serveur effectue un test ping après le lancement du proxy pour savoir s’il est prêt.
- Où il redirige le navigateur après une réponse réussie.
SpaProxyLaunchCommand
: Commande que le serveur utilise pour lancer le proxy de la SPA lorsqu’il détecte que le proxy n’est pas en cours d’exécution.
Le package Microsoft.AspNetCore.SpaProxy
est responsable de la logique précédente pour détecter le proxy et rediriger le navigateur.
L’assembly de démarrage d’hébergement défini dans Properties/launchSettings.json
est utilisé pour ajouter automatiquement les composants requis pendant le développement nécessaires pour détecter si le proxy est en cours d’exécution et le lancer sinon :
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:51783",
"sslPort": 44329
}
},
"profiles": {
"MyReact": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:7145;http://localhost:5273",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy"
}
}
}
}
Configuration de l’application cliente
Cette configuration est spécifique à l’infrastructure frontale utilisée par l’application, mais de nombreux aspects de la configuration sont similaires.
Configuration Angular
Fichier généré par le modèle ClientApp/package.json
:
{
"name": "myangular",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"prestart": "node aspnetcore-https",
"start": "run-script-os",
"start:windows": "ng serve --port 44483 --ssl --ssl-cert \"%APPDATA%\\ASP.NET\\https\\%npm_package_name%.pem\" --ssl-key \"%APPDATA%\\ASP.NET\\https\\%npm_package_name%.key\"",
"start:default": "ng serve --port 44483 --ssl --ssl-cert \"$HOME/.aspnet/https/${npm_package_name}.pem\" --ssl-key \"$HOME/.aspnet/https/${npm_package_name}.key\"",
"build": "ng build",
"build:ssr": "ng run MyAngular:server:dev",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^14.1.3",
"@angular/common": "^14.1.3",
"@angular/compiler": "^14.1.3",
"@angular/core": "^14.1.3",
"@angular/forms": "^14.1.3",
"@angular/platform-browser": "^14.1.3",
"@angular/platform-browser-dynamic": "^14.1.3",
"@angular/platform-server": "^14.1.3",
"@angular/router": "^14.1.3",
"bootstrap": "^5.2.0",
"jquery": "^3.6.0",
"oidc-client": "^1.11.5",
"popper.js": "^1.16.0",
"run-script-os": "^1.1.6",
"rxjs": "~7.5.6",
"tslib": "^2.4.0",
"zone.js": "~0.11.8"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.1.3",
"@angular/cli": "^14.1.3",
"@angular/compiler-cli": "^14.1.3",
"@types/jasmine": "~4.3.0",
"@types/jasminewd2": "~2.0.10",
"@types/node": "^18.7.11",
"jasmine-core": "~4.3.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.1",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "^2.0.0",
"typescript": "~4.7.4"
},
"overrides": {
"autoprefixer": "10.4.5"
},
"optionalDependencies": {}
}
Contient des scripts qui lancent le serveur de développement Angular :
Le script
prestart
appelleClientApp/aspnetcore-https.js
qui est chargé de s’assurer que le certificat HTTPS du serveur de développement est disponible pour le serveur proxy de la SPA.Le
start:windows
etstart:default
:- Lancez le serveur de développement Angular via
ng serve
. - Indiquez le port, les options d’utilisation du protocole HTTPS et le chemin d’accès au certificat et à la clé associée. Le numéro de port fourni correspond au numéro de port spécifié dans le fichier
.csproj
.
- Lancez le serveur de développement Angular via
Le fichier ClientApp/angular.json
généré par le modèle contient :
Commande
serve
.Un élément
proxyconfig
de la configurationdevelopment
pour indiquer queproxy.conf.js
doit être utilisé pour configurer le proxy front-end, comme indiqué dans le JSON mis en surbrillance suivant :{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "MyAngular": { "projectType": "application", "schematics": { "@schematics/angular:application": { "strict": true } }, "root": "", "sourceRoot": "src", "prefix": "app", "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "progress": false, "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", "allowedCommonJsDependencies": [ "oidc-client" ], "assets": [ "src/assets" ], "styles": [ "node_modules/bootstrap/dist/css/bootstrap.min.css", "src/styles.css" ], "scripts": [] }, "configurations": { "production": { "budgets": [ { "type": "initial", "maximumWarning": "500kb", "maximumError": "1mb" }, { "type": "anyComponentStyle", "maximumWarning": "2kb", "maximumError": "4kb" } ], "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ], "outputHashing": "all" }, "development": { "buildOptimizer": false, "optimization": false, "vendorChunk": true, "extractLicenses": false, "sourceMap": true, "namedChunks": true } }, "defaultConfiguration": "production" }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { "browserTarget": "MyAngular:build:production" }, "development": { "browserTarget": "MyAngular:build:development", "proxyConfig": "proxy.conf.js" } }, "defaultConfiguration": "development" }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "MyAngular:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "src/test.ts", "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.spec.json", "karmaConfig": "karma.conf.js", "assets": [ "src/assets" ], "styles": [ "src/styles.css" ], "scripts": [] } }, "server": { "builder": "@angular-devkit/build-angular:server", "options": { "outputPath": "dist-server", "main": "src/main.ts", "tsConfig": "tsconfig.server.json" }, "configurations": { "dev": { "optimization": true, "outputHashing": "all", "sourceMap": false, "namedChunks": false, "extractLicenses": true, "vendorChunk": true }, "production": { "optimization": true, "outputHashing": "all", "sourceMap": false, "namedChunks": false, "extractLicenses": true, "vendorChunk": false } } } } } }, "defaultProject": "MyAngular" }
ClientApp/proxy.conf.js
définit les routes qui doivent être renvoyées par proxy au serveur principal. L’ensemble général d’options est défini dans http-proxy-middleware pour react et angular, car ils utilisent tous les deux le même proxy.
Le code mis en surbrillance suivant de ClientApp/proxy.conf.js
utilise une logique basée sur les variables d’environnement définies pendant le développement pour déterminer le port sur lequel le back-end s’exécute :
const { env } = require('process');
const target = env.ASPNETCORE_HTTPS_PORTS ? `https://localhost:${env.ASPNETCORE_HTTPS_PORTS}` :
env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'http://localhost:51951';
const PROXY_CONFIG = [
{
context: [
"/weatherforecast",
],
target: target,
secure: false,
headers: {
Connection: 'Keep-Alive'
}
}
]
module.exports = PROXY_CONFIG;
Configuration de React
La section scripts
package.json
contient les scripts suivants qui lancent l’application react pendant le développement, comme illustré dans le code mis en surbrillance suivant :{ "name": "myreact", "version": "0.1.0", "private": true, "dependencies": { "bootstrap": "^5.2.0", "http-proxy-middleware": "^2.0.6", "jquery": "^3.6.0", "merge": "^2.1.1", "oidc-client": "^1.11.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-bootstrap": "^0.26.2", "react-router-dom": "^6.3.0", "react-scripts": "^5.0.1", "reactstrap": "^9.1.3", "rimraf": "^3.0.2", "web-vitals": "^2.1.4", "workbox-background-sync": "^6.5.4", "workbox-broadcast-update": "^6.5.4", "workbox-cacheable-response": "^6.5.4", "workbox-core": "^6.5.4", "workbox-expiration": "^6.5.4", "workbox-google-analytics": "^6.5.4", "workbox-navigation-preload": "^6.5.4", "workbox-precaching": "^6.5.4", "workbox-range-requests": "^6.5.4", "workbox-routing": "^6.5.4", "workbox-strategies": "^6.5.4", "workbox-streams": "^6.5.4" }, "devDependencies": { "ajv": "^8.11.0", "cross-env": "^7.0.3", "eslint": "^8.22.0", "eslint-config-react-app": "^7.0.1", "eslint-plugin-flowtype": "^8.0.3", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-react": "^7.30.1", "nan": "^2.16.0", "typescript": "^4.7.4" }, "overrides": { "autoprefixer": "10.4.5" }, "resolutions": { "css-what": "^5.0.1", "nth-check": "^3.0.1" }, "scripts": { "prestart": "node aspnetcore-https && node aspnetcore-react", "start": "rimraf ./build && react-scripts start", "build": "react-scripts build", "test": "cross-env CI=true react-scripts test --env=jsdom", "eject": "react-scripts eject", "lint": "eslint ./src/" }, "eslintConfig": { "extends": [ "react-app" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } }
Le script
prestart
appelle :aspnetcore-https.js
, qui est chargé de s’assurer que le certificat HTTPS du serveur de développement est disponible pour le serveur proxy de la SPA.- Appelle
aspnetcore-react.js
pour configurer le fichier approprié.env.development.local
pour utiliser le certificat de développement local HTTPS.aspnetcore-react.js
configure le certificat de développement local HTTPS en ajoutantSSL_CRT_FILE=<certificate-path>
etSSL_KEY_FILE=<key-path>
au fichier.
Le fichier
.env.development
définit le port du serveur de développement et spécifie HTTPS.
Le src/setupProxy.js
configure le proxy de la SPA pour transférer les requêtes au back-end. L’ensemble général d’options est défini dans http-proxy-middleware.
Le code mis en surbrillance suivant dans ClientApp/src/setupProxy.js
utilise une logique basée sur les variables d’environnement définies pendant le développement pour déterminer le port sur lequel le back-end s’exécute :
const { createProxyMiddleware } = require('http-proxy-middleware');
const { env } = require('process');
const target = env.ASPNETCORE_HTTPS_PORTS ? `https://localhost:${env.ASPNETCORE_HTTPS_PORTS}` :
env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'http://localhost:51783';
const context = [
"/weatherforecast",
];
const onError = (err, req, resp, target) => {
console.error(`${err.message}`);
}
module.exports = function (app) {
const appProxy = createProxyMiddleware(context, {
target: target,
// Handle errors to prevent the proxy middleware from crashing when
// the ASP NET Core webserver is unavailable
onError: onError,
secure: false,
// Uncomment this line to add support for proxying websockets
//ws: true,
headers: {
Connection: 'Keep-Alive'
}
});
app.use(appProxy);
};
Version de l’infrastructure de la SPA prise en charge dans ASP.NET Core modèles de SPA
Les modèles de projet de SPA fournis avec chaque version ASP.NET Core référencent la dernière version de l’infrastructure de la SPA appropriée.
Les infrastructures de SPA ont généralement un cycle de mise en production plus court que .NET. En raison des deux cycles de publication différents, la version prise en charge de l’infrastructure de la SPA et de .NET peut être désynchronisée : la version principale de l’infrastructure de la SPA, dont dépend une version majeure de .NET, peut ne plus être prise en charge, tandis que la version .NET avec laquelle l’infrastructure SPA fournie est toujours prise en charge.
Les modèles SPA ASP.NET Core peuvent être mis à jour dans une version corrective vers une nouvelle version de l’infrastructure de la SPA pour conserver les modèles dans un état pris en charge et sécurisé.