Gambaran Umum Aplikasi Halaman Tunggal (SPAs) di ASP.NET Core
Catatan
Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Peringatan
Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Penting
Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.
Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Visual Studio menyediakan templat proyek untuk membuat aplikasi satu halaman (SPAs) berdasarkan kerangka kerja JavaScript seperti Angular, React, dan Vue yang memiliki backend ASP.NET Core. Templat ini:
- Buat solusi Visual Studio dengan proyek frontend dan proyek backend.
- Gunakan jenis proyek Visual Studio untuk JavaScript dan TypeScript (.esproj) untuk frontend.
- Gunakan proyek ASP.NET Core untuk backend.
Proyek yang dibuat dengan menggunakan templat Visual Studio dapat dijalankan dari baris perintah di Windows, Linux, dan macOS. Untuk menjalankan aplikasi, gunakan dotnet run --launch-profile https
untuk menjalankan proyek server. Menjalankan proyek server secara otomatis memulai server pengembangan JavaScript frontend. Profil https
peluncuran saat ini diperlukan.
Tutorial Visual Studio
Untuk memulai, ikuti salah satu tutorial dalam dokumentasi Visual Studio:
- Buat aplikasi ASP.NET Core dengan Angular
- Buat aplikasi ASP.NET Core dengan React
- Membuat aplikasi ASP.NET Core dengan Vue
Untuk informasi selengkapnya, lihat JavaScript dan TypeScript di Visual Studio
ASP.NET templat SPA Inti
Visual Studio menyertakan templat untuk membangun aplikasi ASP.NET Core dengan frontend JavaScript atau TypeScript. Templat ini tersedia di Visual Studio 2022 versi 17.8 atau yang lebih baru dengan beban kerja ASP.NET dan pengembangan web terinstal.
Templat Visual Studio untuk membangun aplikasi ASP.NET Core dengan frontend JavaScript atau TypeScript menawarkan manfaat berikut:
- Bersihkan pemisahan proyek untuk frontend dan backend.
- Tetap up-to-date dengan versi kerangka kerja frontend terbaru.
- Integrasikan dengan alat baris perintah kerangka kerja frontend terbaru, seperti Vite.
- Templat untuk JavaScript & TypeScript (hanya TypeScript untuk Angular).
- Pengalaman pengeditan kode JavaScript dan TypeScript yang kaya.
- Integrasikan alat build JavaScript dengan build .NET.
- UI manajemen dependensi npm.
- Kompatibel dengan debugging Visual Studio Code dan konfigurasi peluncuran.
- Jalankan pengujian unit frontend di Test Explorer menggunakan kerangka kerja pengujian JavaScript.
Templat Warisan ASP.NET Core SPA
Versi .NET SDK yang lebih lama menyertakan templat warisan apa yang sekarang untuk membangun aplikasi SPA dengan ASP.NET Core. Untuk dokumentasi tentang templat lama ini, lihat gambaran umum SPA versi ASP.NET Core 7.0 dan artikel Angular dan React .
Arsitektur templat Aplikasi Halaman Tunggal
Templat Aplikasi Halaman Tunggal (SPA) untuk Angular dan React menawarkan kemampuan untuk mengembangkan aplikasi Angular dan React yang dihosting di dalam server backend .NET.
Pada waktu publikasi, file aplikasi Angular dan React disalin ke wwwroot
folder dan disajikan melalui middleware file statis.
Daripada mengembalikan HTTP 404 (Tidak Ditemukan), rute fallback menangani permintaan yang tidak diketahui ke backend dan melayani index.html
untuk SPA.
Selama pengembangan, aplikasi dikonfigurasi untuk menggunakan proksi frontend. React dan Angular menggunakan proksi frontend yang sama.
Saat aplikasi diluncurkan, index.html
halaman dibuka di browser. Middleware khusus yang hanya diaktifkan dalam pengembangan:
- Mencegat permintaan masuk.
- Memeriksa apakah proksi sedang berjalan.
- Mengalihkan ke URL untuk proksi jika sedang berjalan atau meluncurkan instans proksi baru.
- Mengembalikan halaman ke browser yang di-refresh secara otomatis setiap beberapa detik hingga proksi aktif dan browser dialihkan.
Manfaat utama yang disediakan templat ASP.NET Core SPA:
- Meluncurkan proksi jika belum berjalan.
- Menyiapkan HTTPS.
- Mengonfigurasi beberapa permintaan untuk diproksi ke server backend ASP.NET Core.
Saat browser mengirim permintaan untuk titik akhir backend, misalnya /weatherforecast
dalam templat. Proksi SPA menerima permintaan dan mengirimkannya kembali ke server secara transparan. Server merespons dan proksi SPA mengirim permintaan kembali ke browser:
Aplikasi Halaman Tunggal yang Diterbitkan
Saat aplikasi diterbitkan, SPA menjadi kumpulan file di wwwroot
folder.
Tidak ada komponen runtime yang diperlukan untuk melayani aplikasi:
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();
Dalam file yang dihasilkan Program.cs
templat sebelumnya:
app.
UseStaticFiles memungkinkan file dilayani.app.
MapFallbackToFile("index.html")
memungkinkan penyajian dokumen default untuk setiap permintaan yang tidak diketahui yang diterima server.
Saat aplikasi diterbitkan dengan penerbitan dotnet, tugas berikut dalam csproj
file memastikan bahwa npm restore
eksekusi dan bahwa skrip npm yang sesuai berjalan untuk menghasilkan artefak produksi:
<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>
Mengembangkan Aplikasi Halaman Tunggal
File proyek menentukan beberapa properti yang mengontrol perilaku aplikasi selama pengembangan:
<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
: Mengontrol URL tempat server mengharapkan proksi SPA berjalan. Ini adalah URL:- Ping server setelah meluncurkan proksi untuk mengetahui apakah sudah siap.
- Di mana ia mengalihkan browser setelah respons berhasil.
SpaProxyLaunchCommand
: Perintah yang digunakan server untuk meluncurkan proksi SPA saat mendeteksi proksi tidak berjalan.
Paket Microsoft.AspNetCore.SpaProxy
ini bertanggung jawab atas logika sebelumnya untuk mendeteksi proksi dan mengalihkan browser.
Rakitan startup hosting yang didefinisikan digunakan Properties/launchSettings.json
untuk secara otomatis menambahkan komponen yang diperlukan selama pengembangan yang diperlukan untuk mendeteksi apakah proksi berjalan dan meluncurkannya sebaliknya:
{
"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"
}
}
}
}
Penyiapan untuk aplikasi klien
Penyiapan ini khusus untuk kerangka kerja frontend yang digunakan aplikasi, namun banyak aspek konfigurasi yang serupa.
Penyiapan Angular
File yang dihasilkan ClientApp/package.json
templat:
{
"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": {}
}
Berisi skrip yang meluncurkan server pengembangan sudut:
prestart
Skrip memanggilClientApp/aspnetcore-https.js
, yang bertanggung jawab untuk memastikan sertifikat HTTPS server pengembangan tersedia untuk server proksi SPA.dan
start:windows
start:default
:- Luncurkan server pengembangan Angular melalui
ng serve
. - Berikan port, opsi untuk menggunakan HTTPS, dan jalur ke sertifikat dan kunci terkait. Nomor port yang disediakan cocok dengan nomor port yang ditentukan dalam
.csproj
file.
- Luncurkan server pengembangan Angular melalui
File yang dihasilkan ClientApp/angular.json
templat berisi:
Perintah
serve
.proxyconfig
Elemen dalamdevelopment
konfigurasi untuk menunjukkan bahwaproxy.conf.js
harus digunakan untuk mengonfigurasi proksi frontend, seperti yang ditunjukkan dalam JSON yang disorot berikut:{ "$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
menentukan rute yang perlu diproksi kembali ke backend server. Sekumpulan opsi umum didefinisikan di http-proxy-middleware untuk react dan angular karena keduanya menggunakan proksi yang sama.
Kode yang disorot berikut dari ClientApp/proxy.conf.js
menggunakan logika berdasarkan variabel lingkungan yang ditetapkan selama pengembangan untuk menentukan port yang dijalankan backend:
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;
Penyiapan react
Bagian
package.json
skrip berisi skrip berikut yang meluncurkan aplikasi react selama pengembangan, seperti yang ditunjukkan dalam kode yang disorot berikut:{ "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
Skrip memanggil:aspnetcore-https.js
, yang bertanggung jawab untuk memastikan sertifikat HTTPS server pengembangan tersedia untuk server proksi SPA.aspnetcore-react.js
Memanggil untuk menyiapkan file yang sesuai.env.development.local
untuk menggunakan sertifikat pengembangan lokal HTTPS.aspnetcore-react.js
mengonfigurasi sertifikat pengembangan lokal HTTPS dengan menambahkanSSL_CRT_FILE=<certificate-path>
danSSL_KEY_FILE=<key-path>
ke file.
File
.env.development
menentukan port untuk server pengembangan dan menentukan HTTPS.
mengonfigurasi src/setupProxy.js
proksi SPA untuk meneruskan permintaan ke backend. Sekumpulan opsi umum didefinisikan dalam http-proxy-middleware.
Kode yang disorot berikut dalam ClientApp/src/setupProxy.js
menggunakan logika berdasarkan variabel lingkungan yang ditetapkan selama pengembangan untuk menentukan port yang dijalankan backend:
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);
};
Versi kerangka kerja SPA yang didukung dalam templat ASP.NET Core SPA
Templat proyek SPA yang dikirim dengan setiap rilis ASP.NET Core mereferensikan versi terbaru dari kerangka kerja SPA yang sesuai.
Kerangka kerja SPA biasanya memiliki siklus rilis yang lebih pendek daripada .NET. Karena dua siklus rilis yang berbeda, versi kerangka kerja SPA yang didukung dan .NET dapat tidak sinkron: versi kerangka kerja SPA utama, yang bergantung pada rilis utama .NET, dapat keluar dari dukungan, sementara versi .NET kerangka kerja SPA yang dikirimkan masih didukung.
Templat ASP.NET Core SPA dapat diperbarui dalam rilis patch ke versi kerangka kerja SPA baru untuk menjaga templat dalam status yang didukung dan aman.
Sumber Daya Tambahan:
ASP.NET Core