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

Observação

Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

O Visual Studio fornece modelos de projeto para a criação de SPAs (aplicativos de página única) baseados em estruturas JavaScript, como o Angular, o React e o Vue, que têm um back-end ASP.NET Core. Estes modelos:

  • Criam uma solução do Visual Studio com um projeto de front-end e um projeto de back-end.
  • Usam o tipo de projeto do Visual Studio para JavaScript e TypeScript (.esproj) para o front-end.
  • Usam 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 do servidor. A execução do projeto do servidor inicia automaticamente o servidor de desenvolvimento JavaScript de front-end. O perfil de inicialização https é obrigatório no momento.

Tutoriais do Visual Studio

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

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

Modelos ASP.NET Core de SPAs

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

Os modelos do Visual Studio para a criação de aplicativos ASP.NET Core com um front-end JavaScript ou TypeScript oferecem os seguintes benefícios:

  • Separação limpa de projetos para front-end e back-end.
  • Mantenha-se atualizado com as últimas versões da estrutura de front-end.
  • Integre-se às ferramentas mais recentes de linha de comando da estrutura de front-end, como o Vite.
  • Modelos para JavaScript e TypeScript (somente TypeScript para Angular).
  • Experiência sofisticada de edição de códigos JavaScript e TypeScript.
  • Integre as ferramentas de build do JavaScript ao build do .NET.
  • Interface do usuário do gerenciamento de dependências do npm.
  • Compatível com a depuração e a configuração de inicialização do Visual Studio Code.
  • Execute testes de unidade de front-end no Gerenciador de Testes usando estruturas de teste JavaScript.

Modelos ASP.NET Core herdados de SPA

As versões anteriores do SDK do .NET incluíam o que passaram a ser os modelos herdados para a criação de aplicativos SPA com o ASP.NET Core. Para ver a documentação sobre esses modelos mais antigos, confira a versão ASP.NET Core 7.0 da visão geral do SPA e os artigos sobre o Angular e o React.

Arquitetura de modelos de aplicativo de página única

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

No momento da publicação, os arquivos do aplicativo Angular e React são copiados para a pasta wwwroot e são atendidos por meio do middleware de arquivos estáticos.

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

Durante o desenvolvimento, o aplicativo é configurado para usar o proxy de front-end. React e Angular usam o mesmo proxy de front-end.

Quando o aplicativo é iniciado, a página index.html é aberta no navegador. Um middleware especial habilitado apenas no desenvolvimento:

  • Intercepta as solicitações de entrada.
  • Verifica se o proxy está em execução.
  • Redireciona para a URL do proxy se ele estiver em execução ou iniciar 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 pronto e o navegador seja redirecionado.

Diagrama do servidor proxy do navegador

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

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

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 para o 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 pasta wwwroot.

Não há nenhum componente de runtime necessário para atender ao 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 anterior arquivo Program.cs gerado pelo modelo:

  • app.UseStaticFiles permite que os arquivos sejam atendidos.
  • app.MapFallbackToFile("index.html") habilita o fornecimento do documento padrão para qualquer solicitação desconhecida que o servidor recebe.

Quando o aplicativo é publicado com dotnet publish, as seguintes tarefas no arquivo csproj 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>

Desenvolver 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 em que o servidor espera que o proxy SPA esteja em execução. Esta é a URL:
    • O servidor executa ping depois de iniciar o proxy para saber se ele está pronto.
    • Onde ele redireciona o navegador após uma resposta bem-sucedida.
  • SpaProxyLaunchCommand: o comando que o servidor usa para iniciar o proxy SPA quando detecta que o proxy não está em execução.

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

O assembly de inicialização de hospedagem definido em Properties/launchSettings.json é usado para adicionar automaticamente os componentes necessários durante o desenvolvimento necessário para detectar se o proxy está em execução e iniciá-lo de outra forma:

{
  "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 front-end que o aplicativo está usando, no entanto, muitos aspectos da configuração são semelhantes.

Configuração do Angular

O arquivo ClientApp/package.json gerado 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 script prestart 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 do Angular por meio de 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 fornecida corresponde ao número da porta especificado no arquivo .csproj.

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

  • O comando serve.

  • Um elemento proxyconfig na configuração development para indicar que proxy.conf.js deve ser usado para configurar o proxy de front-end, conforme mostrado no seguinte JS realçado:

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

O ClientApp/proxy.conf.js define as rotas que precisam ser enviadas 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, pois ambos usam o mesmo proxy.

O código realçado a seguir de ClientApp/proxy.conf.js usa 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 { 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 seção de scripts package.json contém os seguintes scripts que iniciam o aplicativo React durante o desenvolvimento, conforme mostrado no seguinte código realçado:

    {
      "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 script prestart 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 .env.development.local apropriado 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 arquivo .env.development define a porta para o servidor de desenvolvimento e especifica o 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 realçado a seguir em ClientApp/src/setupProxy.js usa 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 com suporte em modelos de SPA do ASP.NET Core

Os modelos de projeto do SPA que são fornecidos com cada versão do 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 versão diferentes, a versão com suporte da estrutura do SPA e do .NET pode ficar fora de sincronia: a versão principal da estrutura do SPA, da qual uma versão principal do .NET depende, pode ficar sem suporte, enquanto a versão do .NET com a qual a estrutura do SPA foi fornecida ainda tem suporte.

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

Recursos adicionais