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 8 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 8 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:

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.

Diagramma server proxy browser

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:

Diagramma del server proxy

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 richiama ClientApp/aspnetcore-https.js, che è responsabile di garantire che il certificato HTTPS del server di sviluppo sia disponibile per il server proxy spa.

  • e start:windowsstart: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.

Il file generato dal ClientApp/angular.json modello contiene:

  • Il comando serve.

  • Elemento proxyconfig nella development configurazione per indicare che proxy.conf.js deve essere usato per configurare il proxy front-end, come illustrato nell'elemento seguente evidenziato JSsu ON:

    {
      "$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 aggiungendo SSL_CRT_FILE=<certificate-path> e SSL_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.

Risorse aggiuntive