Panoramica delle app a pagina singola in ASP.NET Core
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Importante
Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.
Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Visual Studio offre modelli di progetto per la creazione di app a pagina singola basate su framework JavaScript come Angular, React e Vue con back-end ASP.NET Core. Questi modelli:
- Creare una soluzione di Visual Studio con un progetto front-end e un progetto back-end.
- Usare il tipo di progetto di Visual Studio per JavaScript e TypeScript (con estensione esproj) per il front-end.
- Usare un progetto ASP.NET Core per il back-end.
I progetti creati usando i modelli di Visual Studio possono essere eseguiti dalla riga di comando in Windows, Linux e macOS. Per eseguire l'app, usare dotnet run --launch-profile https
per eseguire il progetto server. L'esecuzione del progetto server avvia automaticamente il server di sviluppo JavaScript front-end. Il https
profilo di avvio è attualmente necessario.
Esercitazioni su Visual Studio
Per iniziare, seguire una delle esercitazioni nella documentazione di Visual Studio:
- Creare un'app ASP.NET Core con Angular
- Creare un'app ASP.NET Core con React
- Creare un'app core ASP.NET con Vue
Per altre informazioni, vedere JavaScript e TypeScript in Visual Studio
modelli di ASP.NET Core SPA
Visual Studio include modelli per la creazione di app ASP.NET Core con un front-end JavaScript o TypeScript. Questi modelli sono disponibili in Visual Studio 2022 versione 17.8 o successiva con il carico di lavoro ASP.NET e sviluppo Web installato.
I modelli di Visual Studio per la compilazione di app ASP.NET Core con un front-end JavaScript o TypeScript offrono i vantaggi seguenti:
- Pulire la separazione del progetto per il front-end e il back-end.
- Rimanere aggiornati sulle versioni più recenti del framework front-end.
- Eseguire l'integrazione con gli strumenti da riga di comando del framework front-end più recenti, ad esempio Vite.
- Modelli per JavaScript e TypeScript (solo TypeScript per Angular).
- Esperienza avanzata di modifica del codice JavaScript e TypeScript.
- Integrare gli strumenti di compilazione JavaScript con la build .NET.
- Interfaccia utente di gestione delle dipendenze npm.
- Compatibile con il debug e l'avvio di Visual Studio Code.
- Eseguire unit test front-end in Esplora test usando framework di test JavaScript.
Modelli legacy ASP.NET Core SPA
Le versioni precedenti di .NET SDK includevano i modelli legacy per la compilazione di app a pagina singola con ASP.NET Core. Per la documentazione su questi modelli meno recenti, vedere la versione ASP.NET Core 7.0 della panoramica dell'applicazione a pagina singola e gli articoli su Angular e React.
Architettura dei modelli di applicazione a pagina singola
I modelli applicazione a pagina singola (SPA) per Angular e React offrono la possibilità di sviluppare app Angular e React ospitate all'interno di un server back-end .NET.
Al momento della pubblicazione, i file dell'app Angular e React vengono copiati nella wwwroot
cartella e vengono gestiti tramite il middleware dei file statici.
Anziché restituire HTTP 404 (Non trovato), una route di fallback gestisce le richieste sconosciute al back-end e gestisce per l'applicazione index.html
a pagina singola.
Durante lo sviluppo, l'app è configurata per l'uso del proxy front-end. React e Angular usano lo stesso proxy front-end.
All'avvio dell'app, la index.html
pagina viene aperta nel browser. Middleware speciale abilitato solo nello sviluppo:
- Intercetta le richieste in ingresso.
- Controlla se il proxy è in esecuzione.
- Reindirizza all'URL del proxy se è in esecuzione o avvia una nuova istanza del proxy.
- Restituisce una pagina al browser che viene aggiornata automaticamente ogni pochi secondi finché il proxy non è attivo e il browser viene reindirizzato.
Il vantaggio principale dei modelli di ASP.NET Core SPA offre:
- Avvia un proxy se non è già in esecuzione.
- Configurazione di HTTPS.
- Configurazione di alcune richieste da inviare tramite proxy al server back-end ASP.NET Core.
Quando il browser invia una richiesta per un endpoint back-end, ad esempio /weatherforecast
nei modelli. Il proxy spa riceve la richiesta e lo invia al server in modo trasparente. Il server risponde e il proxy spa invia di nuovo la richiesta al browser:
App a pagina singola pubblicate
Quando l'app viene pubblicata, l'applicazione a pagina singola diventa una raccolta di file nella wwwroot
cartella .
Non è necessario alcun componente di runtime per gestire l'app:
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();
Nel file generato Program.cs
dal modello precedente:
app.
UseStaticFiles consente di gestire i file.app.
MapFallbackToFile("index.html")
abilita la gestione del documento predefinito per qualsiasi richiesta sconosciuta ricevuta dal server.
Quando l'app viene pubblicata con dotnet publish, le attività seguenti nel csproj
file assicurano che npm restore
vengano eseguite e che lo script npm appropriato venga eseguito per generare gli artefatti di produzione:
<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>
Sviluppo di app a pagina singola
Il file di progetto definisce alcune proprietà che controllano il comportamento dell'app durante lo sviluppo:
<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
: controlla l'URL in cui il server prevede che il proxy SPA sia in esecuzione. Questo è l'URL:- Il server esegue il ping dopo l'avvio del proxy per sapere se è pronto.
- Dove reindirizza il browser dopo una risposta riuscita.
SpaProxyLaunchCommand
: il comando usato dal server per avviare il proxy spa quando rileva che il proxy non è in esecuzione.
Il pacchetto Microsoft.AspNetCore.SpaProxy
è responsabile della logica precedente per rilevare il proxy e reindirizzare il browser.
L'assembly di avvio dell'hosting definito in Properties/launchSettings.json
viene usato per aggiungere automaticamente i componenti necessari durante lo sviluppo necessario per rilevare se il proxy è in esecuzione e avviarlo in caso contrario:
{
"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"
}
}
}
}
Configurare l'app client
Questa configurazione è specifica del framework front-end usato dall'app, ma molti aspetti della configurazione sono simili.
Configurazione di Angular
File generato dal ClientApp/package.json
modello:
{
"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": {}
}
Contiene script che avviano il server di sviluppo angular:
Lo
prestart
script richiamaClientApp/aspnetcore-https.js
, che è responsabile di garantire che il certificato HTTPS del server di sviluppo sia disponibile per il server proxy spa.e
start:windows
start:default
:- Avviare il server di sviluppo Angular tramite
ng serve
. - Specificare la porta, le opzioni per usare HTTPS e il percorso del certificato e la chiave associata. Il numero di porta specificato corrisponde al numero di porta specificato nel
.csproj
file.
- Avviare il server di sviluppo Angular tramite
Il file generato dal ClientApp/angular.json
modello contiene:
Il comando
serve
.Elemento
proxyconfig
nelladevelopment
configurazione per indicare cheproxy.conf.js
deve essere usato per configurare il proxy front-end, come illustrato nel codice JSON evidenziato seguente:{ "$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
definisce le route che devono essere inoltrate tramite proxy al back-end del server. Il set generale di opzioni è definito in http-proxy-middleware per react e angular poiché usano entrambi lo stesso proxy.
Il codice evidenziato seguente da ClientApp/proxy.conf.js
usa la logica basata sulle variabili di ambiente impostate durante lo sviluppo per determinare la porta in cui è in esecuzione il back-end:
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;
Configurazione di React
La
package.json
sezione scripts contiene gli script seguenti che avviano l'app react durante lo sviluppo, come illustrato nel codice evidenziato seguente:{ "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" ] } }
Lo
prestart
script richiama:aspnetcore-https.js
, che è responsabile di garantire che il certificato HTTPS del server di sviluppo sia disponibile per il server proxy SPA.aspnetcore-react.js
Richiama per configurare il file appropriato.env.development.local
per l'uso del certificato di sviluppo locale HTTPS.aspnetcore-react.js
configura il certificato di sviluppo locale HTTPS aggiungendoSSL_CRT_FILE=<certificate-path>
eSSL_KEY_FILE=<key-path>
al file .
Il
.env.development
file definisce la porta per il server di sviluppo e specifica HTTPS.
src/setupProxy.js
Configura il proxy spa per inoltrare le richieste al back-end. Il set generale di opzioni è definito in http-proxy-middleware.
Il codice evidenziato seguente in ClientApp/src/setupProxy.js
usa la logica basata sulle variabili di ambiente impostate durante lo sviluppo per determinare la porta in cui è in esecuzione il back-end:
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);
};
Versione del framework SPA supportata nei modelli spa di ASP.NET Core SPA
I modelli di progetto SPA forniti con ogni versione ASP.NET Core fanno riferimento alla versione più recente del framework SPA appropriato.
I framework SPA hanno in genere un ciclo di rilascio più breve rispetto a .NET. A causa dei due diversi cicli di rilascio, la versione supportata del framework SPA e .NET può uscire dalla sincronizzazione: la versione principale del framework SPA, da cui dipende una versione principale di .NET, può uscire dal supporto, mentre la versione di .NET fornita con il framework SPA è ancora supportata.
I modelli di ASP.NET Core SPA possono essere aggiornati in una versione patch a una nuova versione del framework SPA per mantenere i modelli in uno stato supportato e sicuro.