Menggunakan JavaScript Services untuk Membuat Aplikasi Halaman Tunggal di ASP.NET Core
Oleh Fiyaz Hasan
Peringatan
Fitur yang dijelaskan dalam artikel ini usang pada ASP.NET Core 3.0. Mekanisme integrasi kerangka kerja SPA yang lebih sederhana tersedia di paket NuGet Microsoft.AspNetCore.SpaServices.Extensions . Untuk informasi selengkapnya, lihat [Pengumuman] Usang Microsoft.AspNetCore.SpaServices dan Microsoft.AspNetCore.NodeServices.
Aplikasi Halaman Tunggal (SPA) adalah jenis aplikasi web yang populer karena pengalaman penggunanya yang kaya. Mengintegrasikan kerangka kerja atau pustaka SPA sisi klien, seperti Angular atau React, dengan kerangka kerja sisi server seperti ASP.NET Core bisa sulit. JavaScript Services dikembangkan untuk mengurangi gesekan dalam proses integrasi. Ini memungkinkan operasi yang mulus antara tumpukan teknologi klien dan server yang berbeda.
Apa itu JavaScript Services
JavaScript Services adalah kumpulan teknologi sisi klien untuk ASP.NET Core. Tujuannya adalah untuk memposisikan ASP.NET Core sebagai platform sisi server pilihan pengembang untuk membangun SPAs.
JavaScript Services terdiri dari dua paket NuGet yang berbeda:
- Microsoft.AspNetCore.NodeServices (NodeServices)
- Microsoft.AspNetCore.SpaServices (SpaServices)
Paket-paket ini berguna dalam skenario berikut:
- Jalankan JavaScript di server
- Menggunakan kerangka kerja atau pustaka SPA
- Membangun aset sisi klien dengan Webpack
Sebagian besar fokus dalam artikel ini ditempatkan pada penggunaan paket SpaServices.
Apa itu SpaServices
SpaServices dibuat untuk memosisikan ASP.NET Core sebagai platform sisi server pilihan pengembang untuk membangun SPA. SpaServices tidak diperlukan untuk mengembangkan SPAs dengan ASP.NET Core, dan tidak mengunci pengembang ke dalam kerangka kerja klien tertentu.
SpaServices menyediakan infrastruktur yang berguna seperti:
Secara kolektif, komponen infrastruktur ini meningkatkan alur kerja pengembangan dan pengalaman runtime. Komponen dapat diadopsi secara individual.
Prasyarat untuk menggunakan SpaServices
Untuk bekerja dengan SpaServices, instal hal berikut:
Simpul.js (versi 6 atau yang lebih baru) dengan npm
Untuk memverifikasi komponen-komponen ini diinstal dan dapat ditemukan, jalankan hal berikut dari baris perintah:
node -v && npm -v
Jika menyebarkan ke situs web Azure, tidak ada tindakan yang diperlukan—Simpul.js diinstal dan tersedia di lingkungan server.
.NET Core SDK 2.0 atau yang lebih baru
- Di Windows menggunakan Visual Studio 2017, SDK diinstal dengan memilih beban kerja pengembangan lintas platform .NET Core.
Pra-penyajian sisi server
Aplikasi universal (juga dikenal sebagai isomorphic) adalah aplikasi JavaScript yang mampu menjalankan baik di server maupun klien. Angular, React, dan kerangka kerja populer lainnya menyediakan platform universal untuk gaya pengembangan aplikasi ini. Idenya adalah pertama-tama merender komponen kerangka kerja di server melalui Node.js, dan kemudian mendelegasikan eksekusi lebih lanjut kepada klien.
ASP.NET Core Tag Helpers yang disediakan oleh SpaServices menyederhanakan implementasi prarendering sisi server dengan memanggil fungsi JavaScript di server.
Prasyarat pra-penyajian sisi server
Instal paket npm pra-penyajian aspnet:
npm i -S aspnet-prerendering
Konfigurasi pra-penyajian sisi server
Pembantu Tag dibuat dapat ditemukan melalui pendaftaran namespace dalam file proyek _ViewImports.cshtml
:
@using SpaServicesSampleApp
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"
Pembantu Tag ini mengabstraksi seluk beluk berkomunikasi langsung dengan API tingkat rendah dengan memanfaatkan sintaks seperti HTML di dalam Razor tampilan:
<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>
Asp-prerender-module Tag Helper
Pembantu asp-prerender-module
Tag, yang digunakan dalam contoh kode sebelumnya, dijalankan ClientApp/dist/main-server.js
di server melalui Node.js. Demi kejelasan, main-server.js
file adalah artefak dari tugas transpilasi TypeScript-to-JavaScript dalam proses build Webpack . Webpack mendefinisikan alias titik masuk ; main-server
dan, traversal grafik dependensi untuk alias ini dimulai pada ClientApp/boot-server.ts
file:
entry: { 'main-server': './ClientApp/boot-server.ts' },
Dalam contoh Angular berikut, ClientApp/boot-server.ts
file menggunakan createServerRenderer
fungsi dan RenderResult
jenis aspnet-prerendering
paket npm untuk mengonfigurasi penyajian server melalui Node.js. Markup HTML yang ditujukan untuk penyajian sisi server diteruskan ke panggilan fungsi resolve, yang dibungkus dalam objek JavaScript Promise
yang sangat ditik. Signifikansi Promise
objek adalah secara asinkron memasok markup HTML ke halaman untuk injeksi dalam elemen tempat penampung DOM.
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
export default createServerRenderer(params => {
const providers = [
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } },
{ provide: 'ORIGIN_URL', useValue: params.origin }
];
return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => {
const appRef = moduleRef.injector.get(ApplicationRef);
const state = moduleRef.injector.get(PlatformState);
const zone = moduleRef.injector.get(NgZone);
return new Promise<RenderResult>((resolve, reject) => {
zone.onError.subscribe(errorInfo => reject(errorInfo));
appRef.isStable.first(isStable => isStable).subscribe(() => {
// Because 'onStable' fires before 'onError', we have to delay slightly before
// completing the request in case there's an error to report
setImmediate(() => {
resolve({
html: state.renderToString()
});
moduleRef.destroy();
});
});
});
});
});
Asp-prerender-data Tag Helper
Saat digabungkan dengan Pembantu asp-prerender-module
Tag, Pembantu asp-prerender-data
Tag dapat digunakan untuk meneruskan informasi kontekstual dari Razor tampilan ke JavaScript sisi server. Misalnya, markup berikut meneruskan data pengguna ke main-server
modul:
<app asp-prerender-module="ClientApp/dist/main-server"
asp-prerender-data='new {
UserName = "John Doe"
}'>Loading...</app>
Argumen yang diterima UserName
diserialisasikan menggunakan serializer JSON bawaan params.data
dan disimpan dalam objek. Dalam contoh Angular berikut, data digunakan untuk membuat salam yang dipersonalisasi dalam h1
elemen:
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
export default createServerRenderer(params => {
const providers = [
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } },
{ provide: 'ORIGIN_URL', useValue: params.origin }
];
return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => {
const appRef = moduleRef.injector.get(ApplicationRef);
const state = moduleRef.injector.get(PlatformState);
const zone = moduleRef.injector.get(NgZone);
return new Promise<RenderResult>((resolve, reject) => {
const result = `<h1>Hello, ${params.data.userName}</h1>`;
zone.onError.subscribe(errorInfo => reject(errorInfo));
appRef.isStable.first(isStable => isStable).subscribe(() => {
// Because 'onStable' fires before 'onError', we have to delay slightly before
// completing the request in case there's an error to report
setImmediate(() => {
resolve({
html: result
});
moduleRef.destroy();
});
});
});
});
});
Nama properti yang diteruskan di Pembantu Tag diwakili dengan notasi PascalCase . Berbeda dengan JavaScript, di mana nama properti yang sama diwakili dengan camelCase. Konfigurasi serialisasi JSON default bertanggung jawab atas perbedaan ini.
Untuk memperluas contoh kode sebelumnya, data dapat diteruskan dari server ke tampilan dengan menghidrasi globals
properti yang disediakan untuk resolve
fungsi:
import { createServerRenderer, RenderResult } from 'aspnet-prerendering';
export default createServerRenderer(params => {
const providers = [
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } },
{ provide: 'ORIGIN_URL', useValue: params.origin }
];
return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => {
const appRef = moduleRef.injector.get(ApplicationRef);
const state = moduleRef.injector.get(PlatformState);
const zone = moduleRef.injector.get(NgZone);
return new Promise<RenderResult>((resolve, reject) => {
const result = `<h1>Hello, ${params.data.userName}</h1>`;
zone.onError.subscribe(errorInfo => reject(errorInfo));
appRef.isStable.first(isStable => isStable).subscribe(() => {
// Because 'onStable' fires before 'onError', we have to delay slightly before
// completing the request in case there's an error to report
setImmediate(() => {
resolve({
html: result,
globals: {
postList: [
'Introduction to ASP.NET Core',
'Making apps with Angular and ASP.NET Core'
]
}
});
moduleRef.destroy();
});
});
});
});
});
postList
Array yang ditentukan di dalam globals
objek dilampirkan ke objek global window
browser. Variabel ini hoisting ke cakupan global menghilangkan duplikasi upaya, terutama karena berkaitan dengan pemuatan data yang sama sekali di server dan sekali lagi pada klien.
Middleware Dev Paket Web
Webpack Dev Middleware memperkenalkan alur kerja pengembangan yang disederhanakan di mana Webpack membangun sumber daya sesuai permintaan. Middleware secara otomatis mengkompilasi dan melayani sumber daya sisi klien saat halaman dimuat ulang di browser. Pendekatan alternatif adalah memanggil Webpack secara manual melalui skrip build npm proyek ketika dependensi pihak ketiga atau kode kustom berubah. Skrip build npm dalam package.json
file ditampilkan dalam contoh berikut:
"build": "npm run build:vendor && npm run build:custom",
Prasyarat Webpack Dev Middleware
Instal paket npm aspnet-webpack:
npm i -D aspnet-webpack
Konfigurasi Middleware Dev Webpack
Webpack Dev Middleware didaftarkan ke dalam alur permintaan HTTP melalui kode berikut dalam Startup.cs
metode file Configure
:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
// Call UseWebpackDevMiddleware before UseStaticFiles
app.UseStaticFiles();
Metode UseWebpackDevMiddleware
ekstensi harus dipanggil sebelum mendaftarkan hosting file statis melalui UseStaticFiles
metode ekstensi. Untuk alasan keamanan, daftarkan middleware hanya saat aplikasi berjalan dalam mode pengembangan.
Properti webpack.config.js
file output.publicPath
memberi tahu middleware untuk menonton dist
folder untuk perubahan:
module.exports = (env) => {
output: {
filename: '[name].js',
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
Penggantian Modul Hot
Pikirkan fitur Hot Module Replacement (HMR) Webpack sebagai evolusi Webpack Dev Middleware. HMR memperkenalkan semua manfaat yang sama, tetapi lebih menyederhanakan alur kerja pengembangan dengan memperbarui konten halaman secara otomatis setelah mengompilasi perubahan. Jangan bingung dengan refresh browser, yang akan mengganggu status dalam memori saat ini dan sesi debugging SPA. Ada tautan langsung antara layanan Webpack Dev Middleware dan browser, yang berarti perubahan didorong ke browser.
Prasyarat Penggantian Modul Hot
Instal paket npm webpack-hot-middleware:
npm i -D webpack-hot-middleware
Konfigurasi Penggantian Modul Hot
Komponen HMR harus didaftarkan ke alur permintaan HTTP MVC dalam Configure
metode :
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
HotModuleReplacement = true
});
Seperti yang benar dengan Webpack Dev Middleware, UseWebpackDevMiddleware
metode ekstensi harus dipanggil sebelum UseStaticFiles
metode ekstensi. Untuk alasan keamanan, daftarkan middleware hanya saat aplikasi berjalan dalam mode pengembangan.
File webpack.config.js
harus menentukan plugins
array, meskipun dibiarkan kosong:
module.exports = (env) => {
plugins: [new CheckerPlugin()]
Setelah memuat aplikasi di browser, tab Konsol alat pengembang memberikan konfirmasi aktivasi HMR:
Pembantu perutean
Di sebagian besar ASP.NET SPAs berbasis Core, perutean sisi klien sering kali diinginkan selain perutean sisi server. Sistem perutean SPA dan MVC dapat bekerja secara independen tanpa gangguan. Namun, ada satu kasus tepi yang menimbulkan tantangan: mengidentifikasi respons HTTP 404.
Pertimbangkan skenario di mana rute /some/page
tanpa ekstensi digunakan. Asumsikan permintaan tidak cocok dengan rute sisi server, tetapi polanya cocok dengan rute sisi klien. Sekarang pertimbangkan permintaan masuk untuk /images/user-512.png
, yang umumnya mengharapkan untuk menemukan file gambar di server. Jika jalur sumber daya yang diminta tidak cocok dengan rute sisi server atau file statis apa pun, tidak mungkin aplikasi sisi klien akan menanganinya—umumnya mengembalikan kode status HTTP 404 yang diinginkan.
Prasyarat pembantu perutean
Instal paket npm perutean sisi klien. Menggunakan Angular sebagai contoh:
npm i -S @angular/router
Konfigurasi pembantu perutean
Metode ekstensi bernama MapSpaFallbackRoute
digunakan dalam Configure
metode :
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
Rute dievaluasi dalam urutan konfigurasinya. Akibatnya, default
rute dalam contoh kode sebelumnya digunakan terlebih dahulu untuk pencocokan pola.
Membuat proyek baru
JavaScript Services menyediakan templat aplikasi yang telah dikonfigurasi sebelumnya. SpaServices digunakan dalam templat ini bersama dengan kerangka kerja dan pustaka yang berbeda seperti Angular, React, dan Redux.
Templat ini dapat diinstal melalui .NET CLI dengan menjalankan perintah berikut:
dotnet new --install Microsoft.AspNetCore.SpaTemplates::*
Daftar templat SPA yang tersedia ditampilkan:
Templat | Nama Pendek | Bahasa | Tag |
---|---|---|---|
MVC ASP.NET Core dengan Angular | Sudut | [C#] | Web/MVC/SPA |
MVC ASP.NET Core dengan React.js | react | [C#] | Web/MVC/SPA |
MVC ASP.NET Core dengan React.js dan Redux | reactredux | [C#] | Web/MVC/SPA |
Untuk membuat proyek baru menggunakan salah satu templat SPA, sertakan Nama Pendek templat dalam perintah baru dotnet. Perintah berikut membuat aplikasi Angular dengan ASP.NET Core MVC yang dikonfigurasi untuk sisi server:
dotnet new angular
Mengatur mode konfigurasi runtime
Ada dua mode konfigurasi runtime utama:
- Pengembangan:
- Menyertakan peta sumber untuk memudahkan penelusuran kesalahan.
- Tidak mengoptimalkan kode sisi klien untuk performa.
- Produksi:
- Mengecualikan peta sumber.
- Mengoptimalkan kode sisi klien melalui bundling dan minifikasi.
ASP.NET Core menggunakan variabel lingkungan bernama ASPNETCORE_ENVIRONMENT
untuk menyimpan mode konfigurasi. Untuk informasi selengkapnya, lihat Mengatur lingkungan.
Jalankan dengan .NET CLI
Pulihkan paket NuGet dan npm yang diperlukan dengan menjalankan perintah berikut di akar proyek:
dotnet restore && npm i
Membangun dan menjalankan aplikasi:
dotnet run
Aplikasi dimulai pada localhost sesuai dengan mode konfigurasi runtime. Menavigasi ke http://localhost:5000
di browser menampilkan halaman arahan.
Jalankan dengan Visual Studio 2017
Buka file yang .csproj
dihasilkan oleh perintah baru dotnet. Paket NuGet dan npm yang diperlukan dipulihkan secara otomatis setelah proyek terbuka. Proses pemulihan ini mungkin memakan waktu hingga beberapa menit, dan aplikasi siap dijalankan ketika selesai. Klik tombol jalankan hijau atau tekan Ctrl + F5
, dan browser terbuka ke halaman arahan aplikasi. Aplikasi berjalan pada localhost sesuai dengan mode konfigurasi runtime.
Menguji aplikasi
Templat SpaServices telah dikonfigurasi sebelumnya untuk menjalankan pengujian sisi klien menggunakan Karma dan Jasmine. Jasmine adalah kerangka kerja pengujian unit populer untuk JavaScript, sedangkan Karma adalah pelari uji untuk pengujian tersebut. Karma dikonfigurasi untuk bekerja dengan Webpack Dev Middleware sehingga pengembang tidak diharuskan untuk menghentikan dan menjalankan pengujian setiap kali perubahan dilakukan. Apakah itu kode yang berjalan terhadap kasus pengujian atau kasus pengujian itu sendiri, pengujian berjalan secara otomatis.
Menggunakan aplikasi Angular sebagai contoh, dua kasus pengujian Jasmine sudah disediakan untuk CounterComponent
dalam counter.component.spec.ts
file:
it('should display a title', async(() => {
const titleText = fixture.nativeElement.querySelector('h1').textContent;
expect(titleText).toEqual('Counter');
}));
it('should start with count 0, then increments by 1 when clicked', async(() => {
const countElement = fixture.nativeElement.querySelector('strong');
expect(countElement.textContent).toEqual('0');
const incrementButton = fixture.nativeElement.querySelector('button');
incrementButton.click();
fixture.detectChanges();
expect(countElement.textContent).toEqual('1');
}));
Buka perintah di direktori ClientApp . Jalankan perintah berikut:
npm test
Skrip meluncurkan runner uji Karma, yang membaca pengaturan yang ditentukan dalam karma.conf.js
file. Di antara pengaturan lainnya, karma.conf.js
mengidentifikasi file pengujian yang akan dijalankan melalui arraynya files
:
module.exports = function (config) {
config.set({
files: [
'../../wwwroot/dist/vendor.js',
'./boot-tests.ts'
],
Menerbitkan aplikasi
Lihat masalah GitHub ini untuk informasi selengkapnya tentang penerbitan ke Azure.
Menggabungkan aset sisi klien yang dihasilkan dan artefak ASP.NET Core yang diterbitkan ke dalam paket siap disebarkan dapat rumit. Untungnya, SpaServices mengatur bahwa seluruh proses publikasi dengan target MSBuild kustom bernama RunWebpack
:
<Target Name="RunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec Command="npm install" />
<Exec Command="node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod" />
<Exec Command="node node_modules/webpack/bin/webpack.js --env.prod" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="wwwroot\dist\**; ClientApp\dist\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
Target MSBuild memiliki tanggung jawab berikut:
- Pulihkan paket npm.
- Buat build tingkat produksi dari aset pihak ketiga dan sisi klien.
- Buat build tingkat produksi dari aset sisi klien kustom.
- Salin aset yang dihasilkan Webpack ke folder terbitkan.
Target MSBuild dipanggil saat menjalankan:
dotnet publish -c Release
Sumber Daya Tambahan:
ASP.NET Core