JavaScript Services trong ASP.NET Core
Cảnh báo: Các tính năng được mô tả trong bài viết này đã lỗi thời (obsolete) kể từ ASP.NET Core 3.0. Một cơ chế tích hợp framework SPA đơn giản hơn có sẵn trong gói NuGet Microsoft.AspNetCore.SpaServices.Extensions. Để biết thêm thông tin, xem [\[Announcement\] Obsoleting Microsoft.AspNetCore.SpaServices and Microsoft.AspNetCore.NodeServices](https://github.com/dotnet/AspNetCore/issues/12890).
Một Single Page Application (SPA - Ứng dụng trang đơn) là một loại ứng dụng web phổ biến do trải nghiệm người dùng phong phú vốn có. Việc tích hợp các framework hoặc thư viện SPA phía client, chẳng hạn như Angular hoặc React, với các framework phía máy chủ như ASP.NET Core có thể khó khăn. JavaScript Services được phát triển để giảm bớt sự ma sát trong quá trình tích hợp. Nó cho phép hoạt động liền mạch giữa các công nghệ client và server khác nhau.
JavaScript Services là gì?
JavaScript Services là một tập hợp các công nghệ phía client cho ASP.NET Core. Mục tiêu của nó là định vị ASP.NET Core như nền tảng phía máy chủ được các nhà phát triển ưu tiên để xây dựng SPA.
JavaScript Services bao gồm hai gói NuGet riêng biệt:
- Microsoft.AspNetCore.NodeServices (NodeServices)
- Microsoft.AspNetCore.SpaServices (SpaServices)
Các gói này hữu ích trong các tình huống sau:
- Chạy JavaScript trên máy chủ
- Sử dụng framework hoặc thư viện SPA
- Build các assets phía client với Webpack
Phần lớn sự tập trung trong bài viết này là vào việc sử dụng gói SpaServices.
SpaServices là gì?
SpaServices được tạo ra để định vị ASP.NET Core như nền tảng phía máy chủ được các nhà phát triển ưu tiên để xây dựng SPA. SpaServices không bắt buộc để phát triển SPA với ASP.NET Core, và nó không ràng buộc các nhà phát triển vào một client framework cụ thể.
SpaServices cung cấp cơ sở hạ tầng hữu ích như:
- Server-side prerendering (kết xuất phía máy chủ trước)
- Webpack Dev Middleware
- Hot Module Replacement (thay thế module nóng)
- Routing helpers (trình trợ giúp định tuyến)
Tổng hợp lại, các thành phần cơ sở hạ tầng này cải thiện cả quy trình phát triển và trải nghiệm runtime. Các thành phần có thể được áp dụng riêng lẻ.
Điều kiện tiên quyết để sử dụng SpaServices
Để làm việc với SpaServices, hãy cài đặt:
- Node.js (phiên bản 6 trở lên) với npm
- Để xác minh các thành phần này đã được cài đặt và có thể được tìm thấy, hãy chạy lệnh sau từ command line:
``console node -v && npm -v ``
Server-side prerendering (Kết xuất phía máy chủ trước)
Một ứng dụng universal (còn gọi là isomorphic - đồng hình) là ứng dụng JavaScript có khả năng chạy trên cả máy chủ và client. Angular, React và các framework phổ biến khác cung cấp nền tảng universal cho phong cách phát triển ứng dụng này. Ý tưởng là đầu tiên kết xuất các thành phần framework trên máy chủ qua Node.js, sau đó ủy thác thực thi tiếp theo cho client.
ASP.NET Core Tag Helpers được cung cấp bởi SpaServices đơn giản hóa việc triển khai server-side prerendering bằng cách gọi các hàm JavaScript trên máy chủ.
Điều kiện tiên quyết cho server-side prerendering
Cài đặt gói npm aspnet-prerendering:
npm i -S aspnet-prerendering
Cấu hình server-side prerendering
Tag Helpers được làm có thể phát hiện thông qua đăng ký namespace trong tệp _ViewImports.cshtml của dự án:
@using SpaServicesSampleApp @addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" @addTagHelper "*, Microsoft.AspNetCore.SpaServices"
Các Tag Helpers này trừu tượng hóa sự phức tạp của việc giao tiếp trực tiếp với các API cấp thấp bằng cách tận dụng cú pháp giống HTML bên trong Razor view:
<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>
Tag Helper asp-prerender-module
Tag Helper asp-prerender-module, được sử dụng trong ví dụ mã trên, thực thi ClientApp/dist/main-server.js trên máy chủ qua Node.js.
Tag Helper asp-prerender-data
Khi kết hợp với Tag Helper asp-prerender-module, Tag Helper asp-prerender-data có thể được sử dụng để truyền thông tin ngữ cảnh từ Razor view đến JavaScript phía máy chủ. Ví dụ: đoạn markup sau truyền dữ liệu người dùng đến module main-server:
<app asp-prerender-module="ClientApp/dist/main-server"
asp-prerender-data='new {
UserName = "John Doe"
}'>Loading...</app>Đối số UserName nhận được được serialize bằng bộ serialize JSON tích hợp sẵn và được lưu trong đối tượng params.data. Dữ liệu có thể được truyền từ máy chủ đến view bằng cách hydrating thuộc tính globals được cung cấp cho hàm resolve.
Tên thuộc tính được truyền trong Tag Helpers được biểu diễn bằng ký hiệu PascalCase. Ngược lại với JavaScript, nơi các tên thuộc tính tương tự được biểu diễn bằng camelCase. Cấu hình serialization JSON mặc định chịu trách nhiệm cho sự khác biệt này.
Webpack Dev Middleware
Webpack Dev Middleware giới thiệu một quy trình phát triển được tối ưu hóa trong đó Webpack xây dựng các tài nguyên theo yêu cầu. Middleware tự động biên dịch và phục vụ các tài nguyên phía client khi một trang được tải lại trong trình duyệt.
Điều kiện tiên quyết cho Webpack Dev Middleware
Cài đặt gói npm aspnet-webpack:
npm i -D aspnet-webpack
Cấu hình Webpack Dev Middleware
Webpack Dev Middleware được đăng ký vào HTTP request pipeline thông qua mã sau trong phương thức Configure của tệp Startup.cs:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
// Gọi UseWebpackDevMiddleware trước UseStaticFiles
app.UseStaticFiles();Phương thức mở rộng UseWebpackDevMiddleware phải được gọi trước khi đăng ký hosting tệp tĩnh qua phương thức mở rộng UseStaticFiles. Vì lý do bảo mật, chỉ đăng ký middleware khi ứng dụng chạy ở chế độ phát triển.
Hot Module Replacement (Thay thế module nóng)
Hãy nghĩ tính năng Hot Module Replacement (HMR) của Webpack như một sự tiến hóa của Webpack Dev Middleware. HMR giới thiệu tất cả những lợi ích tương tự, nhưng nó còn tối ưu hóa thêm quy trình phát triển bằng cách tự động cập nhật nội dung trang sau khi biên dịch các thay đổi. Đừng nhầm lẫn điều này với việc làm mới trình duyệt, điều đó sẽ can thiệp vào trạng thái in-memory hiện tại và phiên gỡ lỗi của SPA. Có một kết nối trực tiếp giữa dịch vụ Webpack Dev Middleware và trình duyệt, có nghĩa là các thay đổi được đẩy đến trình duyệt.
Điều kiện tiên quyết cho HMR
Cài đặt gói npm webpack-hot-middleware:
npm i -D webpack-hot-middleware
Cấu hình HMR
Thành phần HMR phải được đăng ký vào HTTP request pipeline MVC trong phương thức Configure:
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
HotModuleReplacement = true
});Routing helpers (Trình trợ giúp định tuyến)
Trong hầu hết các SPA dựa trên ASP.NET Core, client-side routing (định tuyến phía client) thường được mong muốn bên cạnh server-side routing (định tuyến phía máy chủ). Các hệ thống routing SPA và MVC có thể hoạt động độc lập mà không có sự can thiệp. Tuy nhiên, có một edge case đặt ra thách thức: xác định các phản hồi HTTP 404.
Xét tình huống trong đó một tuyến không có phần mở rộng /some/page được sử dụng. Giả sử yêu cầu không khớp mẫu với server-side route, nhưng mẫu của nó khớp với client-side route. Nếu đường dẫn tài nguyên được yêu cầu không khớp với bất kỳ server-side route hay tệp tĩnh nào, thì ứng dụng client-side khó có thể xử lý nó — thông thường nên trả về mã trạng thái HTTP 404.
Điều kiện tiên quyết cho routing helpers
Cài đặt gói npm định tuyến phía client. Ví dụ với Angular:
npm i -S @angular/router
Cấu hình routing helpers
Một phương thức mở rộng có tên MapSpaFallbackRoute được sử dụng trong phương thức Configure:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});Các tuyến được đánh giá theo thứ tự chúng được cấu hình. Do đó, tuyến default trong ví dụ mã trên được sử dụng đầu tiên để khớp mẫu.
Tạo dự án mới
JavaScript Services cung cấp các application template được cấu hình sẵn. SpaServices được sử dụng trong các template này kết hợp với các framework và thư viện khác nhau như Angular, React và Redux.
Các template này có thể được cài đặt thông qua .NET CLI bằng cách chạy lệnh sau:
dotnet new --install Microsoft.AspNetCore.SpaTemplates::*
Danh sách các SPA template có sẵn được hiển thị:
| Templates | Short Name | Language | Tags |
|---|---|---|---|
| MVC ASP.NET Core with Angular | angular | [C#] | Web/MVC/SPA |
| MVC ASP.NET Core with React.js | react | [C#] | Web/MVC/SPA |
| MVC ASP.NET Core with React.js and Redux | reactredux | [C#] | Web/MVC/SPA |
Để tạo dự án mới sử dụng một trong các SPA template, hãy bao gồm Short Name của template trong lệnh dotnet new. Lệnh sau tạo một ứng dụng Angular với ASP.NET Core MVC được cấu hình cho phía máy chủ:
dotnet new angular
Đặt chế độ cấu hình runtime
Hai chế độ cấu hình runtime chính tồn tại:
- Development (Phát triển):
- Bao gồm source map để dễ gỡ lỗi.
- Không tối ưu hóa mã phía client cho hiệu suất.
- Production (Sản xuất):
- Loại trừ source map.
- Tối ưu hóa mã phía client thông qua bundling và minification (đóng gói và thu nhỏ).
ASP.NET Core sử dụng biến môi trường có tên ASPNETCORE_ENVIRONMENT để lưu trữ chế độ cấu hình. Để biết thêm thông tin, xem Đặt môi trường.
Chạy với .NET CLI
Khôi phục các gói NuGet và npm cần thiết bằng cách chạy lệnh sau tại thư mục gốc của dự án:
dotnet restore && npm i
Build và chạy ứng dụng:
dotnet run
Ứng dụng khởi động trên localhost theo chế độ cấu hình runtime. Điều hướng đến http://localhost:5000 trong trình duyệt hiển thị trang đích.
Chạy với Visual Studio 2017
Mở tệp .csproj được tạo bởi lệnh dotnet new. Các gói NuGet và npm cần thiết được khôi phục tự động khi mở dự án. Quá trình khôi phục này có thể mất đến vài phút, và ứng dụng sẵn sàng chạy khi hoàn thành. Nhấp vào nút chạy màu xanh lá hoặc nhấn Ctrl + F5, và trình duyệt mở đến trang đích của ứng dụng.
Test ứng dụng
Các SpaServices template được cấu hình sẵn để chạy các unit test phía client bằng cách sử dụng Karma và Jasmine. Jasmine là một framework unit testing phổ biến cho JavaScript, trong khi Karma là một test runner cho các test đó. Karma được cấu hình để làm việc với Webpack Dev Middleware sao cho nhà phát triển không cần phải dừng và chạy test mỗi khi có thay đổi.
Ví dụ với ứng dụng Angular, hai test case Jasmine đã được cung cấp cho CounterComponent trong tệp counter.component.spec.ts:
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');
}));Mở command prompt trong thư mục ClientApp. Chạy lệnh sau:
npm test
Xuất bản ứng dụng
Kết hợp các assets phía client được tạo ra và các artifacts ASP.NET Core đã xuất bản thành một gói sẵn sàng triển khai có thể cồng kềnh. SpaServices sắp xếp toàn bộ quá trình xuất bản đó với một MSBuild target tùy chỉnh có tên 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>MSBuild target có các trách nhiệm sau:
- Khôi phục các gói npm.
- Tạo bản build production-grade của các assets phía client của bên thứ ba.
- Tạo bản build production-grade của các assets phía client tùy chỉnh.
- Sao chép các assets được tạo bởi Webpack vào thư mục xuất bản.
MSBuild target được gọi khi chạy:
dotnet publish -c Release