Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Предупреждение
Функции, описанные в этой статье, устарели с версии ASP.NET Core 3.0. В пакете NuGet Microsoft.AspNetCore.SpaServices.Extensions реализован более простой механизм интеграции с платформами SPA. Дополнительные сведения см. в объявлении об устаревании Microsoft.AspNetCore.SpaServices и Microsoft.AspNetCore.NodeServices.
Одностраничные приложения (SPA) — это популярный тип веб-приложений из-за присущих им широких возможностей пользовательского интерфейса. Интеграция клиентских платформ и библиотек SPA, таких как Angular или React, с серверными платформами, такими как ASP.NET Core, может представлять сложность. Чтобы упростить интеграцию, были разработаны службы JavaScript. Они обеспечивают эффективное взаимодействие между различными стеками клиентских и серверных технологий.
Что такое службы JavaScript
Службы JavaScript — это набор технологий на стороне клиента для ASP.NET Core. Его целью является позиционирование ASP.NET Core в качестве предпочтительной серверной платформы для разработки одностраничных приложений.
В состав служб JavaScript входят два отдельных пакета NuGet:
Эти пакеты полезны в следующих случаях:
- выполнение JavaScript на сервере;
- использование платформы или библиотеки SPA;
- создание ресурсов на стороне клиента с помощью Webpack.
В этой статье основное внимание уделяется использованию пакета SpaServices.
Что такое SpaServices
Пакет SpaServices был создан с целью позиционирования ASP.NET Core в качестве предпочтительной серверной платформы для разработки одностраничных приложений. SpaServices не обязателен для разработки одностраничных приложений с помощью ASP.NET Core и не ограничивает выбор разработчиков определенной клиентской платформой.
Пакет SpaServices предоставляет полезные инфраструктурные возможности, в том числе:
- предварительная обработка на стороне сервера;
- Промежуточное ПО Webpack для разработки
- горячая замена модулей;
- вспомогательные методы маршрутизации.
В совокупности эти компоненты инфраструктуры улучшают как процесс разработки, так и выполнение приложений. Их можно внедрять по отдельности.
Необходимые условия для использования SpaServices
Для работы со SpaServices установите перечисленные ниже компоненты.
Node.js (версия 6 или более поздняя) с npm
Чтобы проверить, установлены ли эти компоненты, выполните в командной строке следующую команду:
node -v && npm -v
Если развертывание выполняется на веб-сайте Azure, никаких действий не требуется —Node.js уже установлен и доступен в серверных средах.
Пакет SDK для .NET Core 2.0. или более поздней версии
- В ОС Windows с Visual Studio 2017 для установки пакета SDK нужно выбрать рабочую нагрузку Кроссплатформенная разработка .NET Core.
Пакет NuGet Microsoft.AspNetCore.SpaServices
Предварительная обработка на стороне сервера
Универсальное приложение (также называемое изоморфным) — это приложение JavaScript, которое может выполняться как на сервере, так и в клиенте. Angular, React и другие популярные платформы обеспечивают возможности для разработки приложений в таком стиле. Идея заключается в том, чтобы сначала отобразить компоненты платформы на сервере с помощью Node.js, а затем делегировать дальнейшее выполнение клиенту.
Вспомогательные функции тегов ASP.NET Core, предоставляемые пакетом SpaServices, упрощают реализацию предварительной обработки на стороне сервера путем вызова функций JavaScript на сервере.
Необходимые условия для предварительной обработки на стороне сервера
Установите пакет npm aspnet-prerendering:
npm i -S aspnet-prerendering
Настройка предварительной обработки на стороне сервера
Вспомогательные функции тегов становятся доступными для обнаружения с помощью регистрации пространства имен в файле проекта _ViewImports.cshtml
:
@using SpaServicesSampleApp
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"
Эти вспомогательные элементы абстрагируют сложности прямого взаимодействия с низкоуровневыми интерфейсами API, используя синтаксис, похожий на HTML, в представлении Razor.
<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>
Вспомогательная функция тегов asp-prerender-module
Вспомогательный компонент тега asp-prerender-module
, используемый в предыдущем примере кода, выполняет ClientApp/dist/main-server.js
на сервере с помощью Node.js. Ради ясности, main-server.js
файл — это артефакт задачи транспиляции TypeScript в JavaScript в процессе сборки с помощью Webpack. В Webpack определен псевдоним точки входа main-server
. Обход схемы зависимостей для этого псевдонима начинается с файла ClientApp/boot-server.ts
:
entry: { 'main-server': './ClientApp/boot-server.ts' },
В следующем примере Angular файл ClientApp/boot-server.ts
использует функцию createServerRenderer
и тип RenderResult
пакета npm aspnet-prerendering
для настройки отрисовки сервера с помощью Node.js. Разметка HTML, предназначенная для обработки на стороне сервера, передается в вызов функции resolve, который заключен в строго типизированный объект JavaScript Promise
. Важность объекта Promise
заключается в том, что он асинхронно передает HTML-разметку странице для внедрения в элемент-заполнитель модели 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
При использовании в сочетании с функцией asp-prerender-module
вспомогательную функцию тегов asp-prerender-data
можно использовать для передачи контекстных сведений из представления Razor в код JavaScript на стороне сервера. Например, следующая разметка передает пользовательские данные в модуль main-server
:
<app asp-prerender-module="ClientApp/dist/main-server"
asp-prerender-data='new {
UserName = "John Doe"
}'>Loading...</app>
Полученный аргумент UserName
сериализуется с помощью встроенного сериализатора JSON и сохраняется в объекте params.data
. В следующем примере для Angular данные используются для создания персонализированного приветствия в элементе h1
:
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();
});
});
});
});
});
Имена свойств, передаваемые в помощниках тегов, представлены в нотации PascalCase. В отличие от этого, в JavaScript те же имена свойств представлены в camelCase. Это различие обуславливается конфигурацией сериализации JSON по умолчанию.
Если развить предыдущий пример кода, можно передать данные с сервера в представление путем гидратации свойства globals
, принимаемого функцией resolve
.
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
, определенный внутри объекта globals
, присоединяется к глобальному объекту window
браузера. Перевод переменной в глобальную область позволяет избежать лишних усилий, особенно когда одни и те же данные загружаются сначала на сервере, а затем в клиенте.
Промежуточное ПО для разработки Webpack
Webpack Dev Middleware предлагает оптимизированный процесс разработки, при котором Webpack собирает ресурсы по требованию. Оно автоматически компилирует и предоставляет ресурсы на стороне клиента при перезагрузке страницы в браузере. Альтернативный подход заключается в вызове Webpack вручную через скрипт сборки npm проекта при изменении сторонней зависимости или пользовательского кода. Скрипт сборки npm в package.json
файле показан в следующем примере:
"build": "npm run build:vendor && npm run build:custom",
Необходимые условия для использования ПО промежуточного слоя Webpack для разработки
Установите пакет npm aspnet-webpack:
npm i -D aspnet-webpack
Настройка промежуточного ПО Webpack для разработки
Промежуточное программное обеспечение Webpack для разработки регистрируется в конвейере HTTP-запросов с помощью следующего кода в методе Startup.cs
файла Configure
:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
// Call UseWebpackDevMiddleware before UseStaticFiles
app.UseStaticFiles();
Метод расширения UseWebpackDevMiddleware
должен вызываться перед регистрацией размещения статических файлов с помощью метода расширения UseStaticFiles
. В целях безопасности регистрировать ПО промежуточного слоя следует только при запуске приложения в режиме разработки.
Свойство webpack.config.js
в файле output.publicPath
предписывает ПО промежуточного слоя отслеживать изменения в папке dist
:
module.exports = (env) => {
output: {
filename: '[name].js',
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
Горячая замена модулей
Функцию горячей замены модулей (HMR) в Webpack можно рассматривать как дальнейшее развитие Webpack Dev Middleware. HMR дает те же преимущества, но еще более упрощает процесс разработки благодаря автоматическому обновлению содержимого страницы после компиляции изменений. Не путайте это с обновлением содержимого браузера, которое нарушает текущее состояние в памяти и сеанс отладки приложения SPA. Между службой ПО промежуточного слоя Webpack для разработки и браузером существует динамическая связь, благодаря которой изменения передаются в браузер.
Необходимые условия для горячей замены модулей
Установите пакет npm webpack-hot-middleware:
npm i -D webpack-hot-middleware
Настройка горячей замены модулей
Компонент HMR необходимо зарегистрировать в конвейере HTTP-запросов MVC в методе Configure
:
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
HotModuleReplacement = true
});
Как и в случае с промежуточным ПО Webpack для разработки, метод расширения UseWebpackDevMiddleware
должен вызываться до метода расширения UseStaticFiles
. В целях безопасности регистрировать ПО промежуточного слоя следует только при запуске приложения в режиме разработки.
В файле webpack.config.js
должен определяться массив plugins
, даже если он будет пустым:
module.exports = (env) => {
plugins: [new CheckerPlugin()]
После загрузки приложения в браузере на вкладке "Консоль" средств разработчика выводится подтверждение активации HMR.
Вспомогательные методы маршрутизации
В большинстве одностраничных приложений на основе ASP.NET Core, помимо маршрутизации на стороне сервера, часто требуется маршрутизация на стороне клиента. Системы маршрутизации SPA и MVC могут работать независимо, не мешая друг другу. Однако существует один пограничный случай, вызывающий трудности: идентификация HTTP-ответов 404.
Рассмотрим ситуацию, в которой используется маршрут /some/page
без расширений. Предположим, что шаблон запроса не соответствует маршруту на стороне сервера, но соответствует маршруту на стороне клиента. Теперь предположим, что поступил запрос на /images/user-512.png
, который обычно ожидает найти файл изображения на сервере. Если запрошенный путь к ресурсу не соответствует ни одному маршруту на стороне сервера или статическому файлу, то маловероятно, что клиентское приложение будет его обрабатывать — обычно в таких случаях возвращается код состояния HTTP 404.
Предпосылки для использования вспомогательных средств маршрутизации
Установите пакет npm для маршрутизации на стороне клиента. Вот пример для Angular:
npm i -S @angular/router
Настройка вспомогательных методов маршрутизации
В методе MapSpaFallbackRoute
используется метод расширения 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" });
});
Маршруты оцениваются в той очередности, в которой они были настроены. Поэтому в предыдущем примере кода для сопоставления шаблонов сначала используется маршрут default
.
Создание нового проекта
Службы JavaScript предоставляют предварительно настроенные шаблоны приложений. Пакет SpaServices используется в них в сочетании с различными платформами и библиотеками, таким как Angular, React и Redux.
Эти шаблоны можно установить с помощью интерфейса командной строки .NET, выполнив следующую команду:
dotnet new --install Microsoft.AspNetCore.SpaTemplates::*
Отобразится список доступных шаблонов SPA.
Шаблоны | Короткое имя | Язык | Теги |
---|---|---|---|
MVC ASP.NET Core с Angular | угловой | [C#] | MVC/Веб/SPA |
MVC ASP.NET Core с использованием React.js | реагировать | [C#] | MVC/Веб/SPA |
MVC ASP.NET Core с React.js и Redux | Reactredux | [C#] | MVC/Веб/SPA |
Чтобы создать проект с помощью одного из шаблонов SPA, включите короткое имя шаблона в команду dotnet new. Следующая команда создает приложение Angular с MVC ASP.NET Core, настроенное для серверной стороны.
dotnet new angular
Установите режим конфигурации среды выполнения
Существует два основных режима конфигурации среды выполнения:
-
Разработка:
- включает карты исходного кода для упрощения отладки.
- не оптимизирует производительность кода на стороне клиента.
-
Производство:
- исключает карты источников.
- оптимизирует код на стороне клиента посредством объединения и минификации.
В ASP.NET Core режим конфигурации хранится в переменной среды ASPNETCORE_ENVIRONMENT
. Дополнительные сведения см. в разделе Настройка среды.
Запуск с помощью .NET CLI
Восстановите необходимые пакеты NuGet и npm, выполнив следующую команду в корневом каталоге проекта:
dotnet restore && npm i
Выполните сборку и запуск приложения:
dotnet run
Приложение запускается на узле localhost в соответствии с режимом конфигурации среды выполнения. При переходе по адресу http://localhost:5000
в браузере открывается целевая страница.
Запуск с помощью Visual Studio 2017
Откройте файл, .csproj
созданный командой dotnet new . Необходимые пакеты NuGet и npm восстанавливаются автоматически при открытии проекта: Процесс восстановления может занять несколько минут. По его завершении приложение будет готово к запуску. Нажмите зеленую кнопку запуска или клавиши Ctrl + F5
, и в браузере откроется целевая страница приложения. Приложение запускается на узле localhost в соответствии с режимом конфигурации среды выполнения.
Тестирование приложения
В шаблонах SpaServices предварительно настроено выполнение тестов на стороне клиента с помощью Karma и Jasmine. Jasmine — это популярная платформа модульного тестирования для JavaScript, а Karma — это средство запуска тестов. Karma настроен для работы с Webpack Dev Middleware так, что разработчику не нужно останавливать и запускать тесты снова каждый раз, когда вносятся изменения. Независимо от того, выполняется ли тестовый случай сам по себе или код для этого случая, тест выполняется автоматически.
Возьмем в качестве примера приложение Angular. В файле CounterComponent
уже имеется два тестовых случая Jasmine для 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');
}));
Откройте командную строку в каталоге ClientApp. Выполните следующую команду:
npm test
Скрипт запускает средство выполнения теста Кармы, которое считывает параметры, определенные в karma.conf.js
файле. Помимо прочих параметров, в файле karma.conf.js
определены тестовые файлы, которые должны выполняться через массив files
:
module.exports = function (config) {
config.set({
files: [
'../../wwwroot/dist/vendor.js',
'./boot-tests.ts'
],
Публикация приложения
Дополнительные сведения о публикации в Azure см. в описании этой проблемы в GitHub.
Объединение созданных клиентских ресурсов и опубликованных артефактов ASP.NET Core в пакет, готовый к развертыванию, может быть непростой задачей. К счастью, пакет SpaServices координирует весь процесс публикации с помощью настраиваемого целевого объекта MSBuild с именем 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 отвечает за следующие задачи:
- восстановление пакетов npm;
- Создание продукционной сборки сторонних клиентских ресурсов.
- создание производственной сборки индивидуальных клиентских ресурсов.
- Скопируйте ресурсы, созданные средством Webpack, в папку публикации.
Целевой объект MSBuild вызывается при выполнении следующей команды:
dotnet publish -c Release
Дополнительные ресурсы
ASP.NET Core