Bagikan melalui


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:

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:

  • Node.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 required—Node.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.
  • Paket NuGet Microsoft.AspNetCore.SpaServices

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-serverdan, 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 JSmenggunakan serializer ON 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 ON default JSbertanggung 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.

variabel postList global yang dilampirkan ke objek jendela

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:

Pesan tersambung Penggantian Modul Hot

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:

  1. Pulihkan paket npm.
  2. Buat build tingkat produksi dari aset pihak ketiga dan sisi klien.
  3. Buat build tingkat produksi dari aset sisi klien kustom.
  4. Salin aset yang dihasilkan Webpack ke folder terbitkan.

Target MSBuild dipanggil saat menjalankan:

dotnet publish -c Release

Sumber Daya Tambahan: