ASP.NET Core でのシングルページ アプリケーション (SPA) の概要
Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
Visual Studio には、ASP.NET Core バックエンドを持つ Angular、React、Vue などの JavaScript フレームワークに基づいてシングルページ アプリ (SPA) を作成するためのプロジェクト テンプレートが用意されています。 これらのテンプレートでは、次のことを行います。
- フロントエンド プロジェクトとバックエンド プロジェクトを含む Visual Studio ソリューションを作成します。
- JavaScript には Visual Studio プロジェクトの種類を使用し、フロントエンドには TypeScript (.esproj) を使用します。
- バックエンドには ASP.NET Core プロジェクトを使用します。
Visual Studio テンプレートを使って作成されたプロジェクトは、Windows、Linux、macOS のコマンド ラインから実行できます。 アプリを実行するには、dotnet run --launch-profile https
を使用してサーバー プロジェクトを実行します。 サーバー プロジェクトを実行すると、フロントエンドの JavaScript 開発サーバーが自動的に起動します。 現在は、https
起動プロファイルが必須です。
Visual Studio チュートリアル
作業を開始するには、Visual Studio ドキュメントのいずれかのチュートリアルに従います。
詳細については、「Visual Studio の JavaScript および TypeScript」を参照してください
ASP.NET Core SPA テンプレート
Visual Studio には、JavaScript または TypeScript フロントエンドを使用して ASP.NET Core アプリを構築するためのテンプレートが含まれています。 これらのテンプレートは、ASP.NET と Web 開発ワークロードがインストールされている Visual Studio 2022 バージョン 17.8 以降で使用できます。
JavaScript または TypeScript フロントエンドを使用して ASP.NET Core アプリを構築するための Visual Studio テンプレートには、次の利点があります。
- フロントエンドとバックエンドのプロジェクト分離をクリーンにする。
- 最新のフロントエンド フレームワーク バージョンを最新の状態に保つ。
- Vite など、最新のフロントエンド フレームワーク コマンドライン ツールと統合する。
- JavaScript と TypeScript の両方のテンプレート (Angular に対しては TypeScript のみ)。
- 豊富な JavaScript と TypeScript コードの編集エクスペリエンス。
- JavaScript ビルド ツールを .NET ビルドと統合する。
- npm 依存関係管理 UI。
- Visual Studio Code のデバッグと起動の構成と互換性がある。
- JavaScript テスト フレームワークを使用して、テスト エクスプローラーでフロントエンド単体テストを実行する。
レガシ ASP.NET Core SPA テンプレート
以前のバージョンの .NET SDK には、ASP.NET Core を使用して SPA アプリを構築するためのレガシ テンプレートが含まれていました。 これらの以前のテンプレートに関するドキュメントについては、ASP.NET Core 7.0 バージョンの SPA の概要と Angular および React に関する記事を参照してください。
シングル ページ アプリケーション テンプレートのアーキテクチャ
Angular および React のシングル ページ アプリ (SPA) テンプレートは、.NET バックエンド サーバー内でホストされる Angular アプリと React アプリを開発する機能を提供します。
公開時に、Angular アプリと React アプリのファイルが wwwroot
フォルダーにコピーされ、静的ファイル ミドルウェアを介して提供されます。
フォールバック ルートは HTTP 404 (見つかりません) を返すのではなく、バックエンドへの不明な要求を処理し、SPA 用に index.html
を提供します。
開発中は、フロントエンド プロキシを使うようにアプリが構成されています。 React と Angular は同じフロントエンド プロキシを使います。
アプリが起動すると、ブラウザーで index.html
ページが開きます。 開発時のみ有効な特別なミドルウェアです。
- 受信した要求を取得します。
- プロキシが動作しているかどうかを確認します。
- プロキシが動作している場合はプロキシの URL にリダイレクトし、そうでなければプロキシの新しいインスタンスを起動します。
- プロキシが起動してブラウザーがリダイレクトされるまで、数秒ごとに自動更新されるページをブラウザーに返します。
ASP.NET Core SPA テンプレートが提供する主な利点は次のとおりです。
- まだ実行されていない場合、プロキシを起動します。
- HTTPS を設定します。
- バックエンドの ASP.NET Core サーバーにプロキシされるように、一部の要求を構成します。
ブラウザーがバックエンドのエンドポイント、たとえばテンプレート内の /weatherforecast
に要求を送信するとき。 SPA プロキシは要求を受け取り、透過的にサーバーに送信します。 サーバーが応答し、SPA プロキシによって要求がブラウザーに返されます。
公開されたシングル ページ アプリ
アプリが公開されると、SPA は wwwroot
フォルダー内のファイルのコレクションとなります。
アプリを提供するために必要なランタイム コンポーネントはありません。
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();
上記のテンプレートで生成された Program.cs
ファイルでは、次のようになります。
app.
UseStaticFiles を使うと、ファイルを提供できます。app.
MapFallbackToFile("index.html")
は、サーバーが受け取る未知の要求に対して、既定のドキュメントを提供できるようにします。
dotnet publish でアプリを公開すると、csproj
ファイル内の次のタスクにより npm restore
が実行され、適切な npm スクリプトが実行されて運用環境の成果物が生成されます。
<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>
シングル ページ アプリの開発
プロジェクト ファイルには、開発中のアプリの動作を制御するいくつかのプロパティが定義されています。
<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
: サーバーが SPA プロキシの実行を想定する URL を制御します。 これは次のような URL です。- サーバーがプロキシを起動した後、準備ができたかどうかを知るために ping を送信します。
- 応答が成功した後にブラウザーをリダイレクトする場所になります。
SpaProxyLaunchCommand
: プロキシが実行されていないことを検出したときに、サーバーが SPA プロキシの起動に使うコマンドです。
パッケージ Microsoft.AspNetCore.SpaProxy
は、プロキシを検出してブラウザーをリダイレクトする上記のロジックを担当します。
Properties/launchSettings.json
で定義されたホスティング起動アセンブリは、開発中に必要なコンポーネントを自動的に追加して、プロキシが実行されているかどうかを検出し、それ以外の場合は起動するために使われます。
{
"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"
}
}
}
}
クライアント アプリのセットアップ
このセットアップは、アプリが使っているフロントエンド フレームワークに固有のものですが、構成は多くの面で似ています。
Angular のセットアップ
テンプレートで生成された ClientApp/package.json
ファイルです。
{
"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": {}
}
Angular の開発サーバーを起動するスクリプトが含まれています。
prestart
スクリプトはClientApp/aspnetcore-https.js
を起動し、開発サーバーの HTTPS 証明書が SPA プロキシ サーバーで使用可能であることを確認する役割を担います。start:windows
とstart:default
:ng serve
を使って Angular 開発サーバーを起動します。- ポート、HTTPS を使うオプション、証明書と関連付けられたキーへのパスを指定します。 指定するポート番号は、
.csproj
ファイルで指定されたポート番号と一致します。
テンプレートで生成された ClientApp/angular.json
ファイルには、次が含まれます。
serve
コマンド。development
構成のproxyconfig
要素。次の JSON の強調表示で示されるように、proxy.conf.js
を使ってフロントエンド プロキシを構成する必要があることを示します。{ "$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
は、サーバーのバックエンドにプロキシする必要があるルートを定義します。 一般的なオプションのセットは、react と angular の両方が同じプロキシを使うので、http-proxy-middleware で定義されています。
次の ClientApp/proxy.conf.js
の強調表示されたコードでは、開発中に設定された環境変数に基づくロジックを使って、バックエンドが実行されているポートを決定します。
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;
React のセットアップ
package.json
スクリプト セクションには、次の強調表示されたコードで示されるように、開発中に react アプリを起動する次のスクリプトが含まれています。{ "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" ] } }
prestart
スクリプトは、次を呼び出します。aspnetcore-https.js
は、開発サーバーの HTTPS 証明書を SPA のプロキシ サーバーで使用できるようにする役割を担っています。- HTTPS ローカル開発証明書を使うために適切な
.env.development.local
ファイルをセットアップするaspnetcore-react.js
を呼び出します。aspnetcore-react.js
は、SSL_CRT_FILE=<certificate-path>
とSSL_KEY_FILE=<key-path>
をファイルに追加して HTTPS ローカル開発用証明書を構成します。
.env.development
ファイルは、開発サーバーのポートを定義し、HTTPS を指定します。
src/setupProxy.js
は、バックエンドに要求を転送するように SPA プロキシを構成します。 一般的なオプションのセットは、http-proxy-middleware で定義されています。
ClientApp/src/setupProxy.js
にある次の強調表示されたコードは、開発中に設定された環境変数に基づくロジックを使って、バックエンドが実行されているポートを決定します。
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);
};
ASP.NET Core SPA テンプレートでサポートされている SPA フレームワークのバージョン
各 ASP.NET Core リリースに付属する SPA プロジェクト テンプレートは、適切な SPA フレームワークの最新バージョンを参照します。
通常、SPA フレームワークのリリース サイクルは .NET よりも短くなります。 2 つの異なるリリース サイクルが原因で、SPA フレームワークと .NET のサポートされているバージョンが合わなくなる可能性があります。つまり、.NET のメジャー リリースが依存する SPA のメジャー フレームワーク バージョンはサポート対象外になり、SPA フレームワークが附属する .NET バージョンは引き続きサポートされている可能性があります。
ASP.NET Core SPA テンプレートは、パッチ リリースで新しい SPA フレームワーク バージョンに更新し、テンプレートをサポートされている安全な状態に保つことができます。
その他の技術情報
ASP.NET Core