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.
Aviso
Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, confira .NET e a Política de Suporte do .NET Core. 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:
- Criar um aplicativo ASP.NET Core com o Angular
- Criar um aplicativo ASP.NET Core com o React
- Criar um aplicativo ASP.NET Core com o Vue
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.
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:
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
invocaClientApp/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
estart: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
.
- Inicie o servidor de desenvolvimento do Angular por meio de
O arquivo gerado pelo modelo ClientApp/angular.json
contém:
O comando
serve
.Um elemento
proxyconfig
na configuraçãodevelopment
para indicar queproxy.conf.js
deve ser usado para configurar o proxy de front-end, conforme mostrado no JSON seguinte 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" }
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 adicionandoSSL_CRT_FILE=<certificate-path>
eSSL_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.