Partilhar via


Visão geral de aplicativos de página única (SPAs) no ASP.NET Core

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 10 deste artigo.

O Visual Studio fornece modelos de projeto para criar aplicativos de página única (SPAs) com base em tecnologias JavaScript, como Angular, React e Vue , que têm um back-end ASP.NET Core. Estes modelos:

  • Crie uma solução do Visual Studio com um projeto de frontend e um projeto de back-end.
  • Use o tipo de projeto Visual Studio para JavaScript e TypeScript (.esproj) para o frontend.
  • Use um projeto ASP.NET Core para o back-end.

Os projetos criados usando os modelos do Visual Studio podem ser executados a partir da linha de comando no Windows, Linux e macOS. Para executar o aplicativo, use dotnet run --launch-profile https para executar o projeto de servidor. A execução do projeto de servidor inicia automaticamente o servidor de desenvolvimento JavaScript frontend. O perfil de lançamento https é necessário atualmente.

Tutoriais do Visual Studio

Para começar, siga um dos tutoriais na documentação do Visual Studio:

Para obter mais informações, consulte JavaScript e TypeScript no Visual Studio

ASP.NET Modelos de SPA principais

O Visual Studio inclui modelos para criar aplicativos ASP.NET Core com um front-end JavaScript ou TypeScript. Esses modelos estão disponíveis no Visual Studio 2022 versão 17.8 ou posterior com a carga de trabalho de desenvolvimento ASP.NET e Web instalada.

Os modelos do Visual Studio para criar aplicativos ASP.NET Core com um frontend JavaScript ou TypeScript oferecem os seguintes benefícios:

  • Separação limpa do projeto entre o frontend e o backend.
  • Mantenha-se up-toatualizado com as versões mais recentes do framework frontend.
  • Integre com as mais recentes ferramentas de linha de comando do frontend framework, como o Vite.
  • Modelos para JavaScript e TypeScript (apenas TypeScript para Angular).
  • Rica experiência de edição de código JavaScript e TypeScript.
  • Integre ferramentas de compilação JavaScript com a compilação .NET.
  • Interface do usuário de gestão de dependências do npm.
  • Compatível com depuração de código do Visual Studio e configuração de inicialização.
  • Execute testes de unidade frontend no Test Explorer usando estruturas de teste JavaScript.

Modelos de SPA ASP.NET Core herdados

As versões anteriores do SDK do .NET incluíam o que agora são modelos herdados para criar aplicativos SPA com o ASP.NET Core. Para obter documentação sobre esses modelos mais antigos, consulte a versão .NET 7 da visão geral do SPA e os artigos Angular e React .

Arquitetura de modelos de aplicativos de página única

Os modelos de Aplicativo de Página Única (SPA) para Angular e React oferecem a capacidade de desenvolver aplicativos Angular e React hospedados em um servidor back-end .NET.

No momento da publicação, os arquivos do aplicativo Angular e React são copiados para a wwwroot pasta e são servidos por meio do Static File Middleware.

Em vez de retornar HTTP 404 (Não encontrado), uma rota de fallback manipula solicitações desconhecidas para o backend e serve a index.html para a SPA.

Durante o desenvolvimento, o aplicativo é configurado para usar o proxy frontend. React e Angular usam o mesmo proxy frontend.

Quando o aplicativo é iniciado, a index.html página é aberta no navegador. Um middleware especial que só é ativado no desenvolvimento:

  • Interceta as solicitações recebidas.
  • Verifica se o proxy está em execução.
  • Redireciona para a URL do proxy se ele estiver em execução ou inicia uma nova instância do proxy.
  • Retorna uma página para o navegador que é atualizada automaticamente a cada poucos segundos até que o proxy esteja ativo e o navegador seja redirecionado.

Diagrama do servidor proxy do navegador

O principal benefício que os modelos de SPA ASP.NET Core oferecem:

  • Inicia um proxy se ele ainda não estiver em execução.
  • Configuração de HTTPS.
  • Configurando algumas solicitações para serem intermediadas por proxy para o back-end ASP.NET servidor Core.

Quando o navegador envia uma solicitação para um ponto de extremidade de back-end, por exemplo /weatherforecast nos modelos. O proxy SPA recebe a solicitação e a envia de volta ao servidor de forma transparente. O servidor responde e o proxy SPA envia a solicitação de volta para o navegador:

Diagrama do servidor proxy

Aplicativos de página única publicados

Quando o aplicativo é publicado, o SPA se torna uma coleção de arquivos na wwwroot pasta.

Não há nenhum componente de tempo de execução necessário para servir o aplicativo:

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();

No arquivo gerado Program.cs pelo modelo precedente:

  • app. UseStaticFiles permite que os arquivos sejam servidos.
  • app. MapFallbackToFile ("index.html") Permite servir o documento padrão para qualquer solicitação desconhecida que o servidor receba.

Quando o aplicativo é publicado com dotnet publish, as seguintes tarefas no csproj arquivo garantem que npm restore seja executado e que o script npm apropriado seja executado para gerar os artefatos de produção:

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

Desenvolvendo aplicativos de página única

O arquivo de projeto define algumas propriedades que controlam o comportamento do aplicativo durante o desenvolvimento:

<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: Controla a URL onde o servidor espera que o proxy SPA seja executado. Este é o URL:
    • O servidor faz pings depois de iniciar o proxy para saber se ele está pronto.
    • Onde redireciona o navegador após uma resposta bem-sucedida.
  • SpaProxyLaunchCommand: O comando que o servidor usa para iniciar o proxy SPA quando deteta que o proxy não está em execução.

O pacote Microsoft.AspNetCore.SpaProxy é responsável pela lógica anterior para detetar o proxy e redirecionar o navegador.

O conjunto de inicialização de hospedagem definido em Properties/launchSettings.json é usado para adicionar automaticamente os componentes necessários durante o desenvolvimento para detetar se o proxy está em execução e iniciá-lo caso contrário.

{
  "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"
      }
    }
  }
}

Configuração para o aplicativo cliente

Essa configuração é específica para a estrutura de frontend que o aplicativo está usando, no entanto, muitos aspetos da configuração são semelhantes.

Configuração angular

O arquivo gerado ClientApp/package.json pelo modelo:

{
  "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": {}
}
  • Contém scripts que iniciam o servidor de desenvolvimento angular:

  • O prestart script invoca ClientApp/aspnetcore-https.js, que é responsável por garantir que o certificado HTTPS do servidor de desenvolvimento esteja disponível para o servidor proxy SPA.

  • O start:windows e start:default:

    • Inicie o servidor de desenvolvimento Angular via ng serve.
    • Forneça a porta, as opções para usar HTTPS e o caminho para o certificado e a chave associada. O número da porta fornecido corresponde ao número da porta especificado no ficheiro .csproj.

O ficheiro gerado pelo modelo ClientApp/angular.json contém:

  • O serve comando.

  • Um proxyconfig elemento na development configuração para indicar que proxy.conf.js deve ser usado para configurar o proxy frontend, conforme mostrado no seguinte JSON destacado:

    {
      "$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 Define as rotas que precisam ser intermediadas por proxy de volta para o back-end do servidor. O conjunto geral de opções é definido em http-proxy-middleware para react e angular, uma vez que ambos usam o mesmo proxy.

O código destacado a seguir usa a lógica com base nas variáveis de ClientApp/proxy.conf.js ambiente definidas durante o desenvolvimento para determinar a porta em que o back-end está sendo executado:

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;

Configuração do React

  • A package.json seção de scripts contém os seguintes scripts que iniciam o aplicativo react durante o desenvolvimento, conforme mostrado no código destacado a seguir:

    {
      "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"
        ]
      }
    }
    
  • O prestart script invoca:

    • aspnetcore-https.js, que é responsável por garantir que o certificado HTTPS do servidor de desenvolvimento esteja disponível para o servidor proxy SPA.
    • Invoca aspnetcore-react.js para configurar o arquivo apropriado .env.development.local para usar o certificado de desenvolvimento local HTTPS. aspnetcore-react.js configura o certificado de desenvolvimento local HTTPS adicionando SSL_CRT_FILE=<certificate-path> e SSL_KEY_FILE=<key-path> ao arquivo.
  • O .env.development arquivo define a porta para o servidor de desenvolvimento e especifica HTTPS.

O src/setupProxy.js configura o proxy SPA para encaminhar as solicitações para o back-end. O conjunto geral de opções é definido em http-proxy-middleware.

O código destacado a seguir usa ClientApp/src/setupProxy.js a lógica com base nas variáveis de ambiente definidas durante o desenvolvimento para determinar a porta em que o back-end está sendo executado:

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);
};

Versão da estrutura SPA suportada em modelos de SPA ASP.NET Core

Os modelos de projeto SPA fornecidos com cada versão ASP.NET Core fazem referência à versão mais recente da estrutura SPA apropriada.

As estruturas SPA normalmente têm um ciclo de lançamento mais curto do que o .NET. Devido aos dois ciclos de lançamento diferentes, a versão suportada da estrutura SPA e do .NET pode ficar fora de sincronia: a versão principal da estrutura SPA, da qual uma versão principal do .NET depende, pode ficar sem suporte, enquanto a versão .NET com a qual a estrutura SPA foi fornecida ainda é suportada.

Os modelos de SPA ASP.NET Core podem ser atualizados em uma versão de patch para uma nova versão da estrutura SPA para manter os modelos em um estado suportado e seguro.

Recursos adicionais