Použití javascriptových služeb k vytváření jednostrákových aplikací v ASP.NET Core
Autor: Fiyaz Hasan
Upozorňující
Funkce popsané v tomto článku jsou zastaralé od ASP.NET Core 3.0. Jednodušší integrační mechanismus architektury SPA je k dispozici v balíčku NuGet Microsoft.AspNetCore.SpaServices.Extensions . Další informace naleznete v tématu [Oznámení] Obsoleting Microsoft.AspNetCore.SpaServices a Microsoft.AspNetCore.NodeServices.
Jednostránkové aplikace (SPA) je oblíbeným typem webové aplikace z důvodu vlastního bohatého uživatelského prostředí. Integrace architektur SPA na straně klienta nebo knihoven, jako je Angular nebo React, s architekturami na straně serveru, jako je ASP.NET Core, může být obtížné. Byly vyvinuty služby JavaScript Services, aby se snížily tření v procesu integrace. Umožňuje bezproblémovou operaci mezi různými zásobníky klientských a serverových technologií.
Co je JavaScript Services
JavaScript Services je kolekce technologií na straně klienta pro ASP.NET Core. Jejím cílem je umístit ASP.NET Core jako upřednostňovanou serverovou platformu pro vytváření služeb na straně vývojářů.
JavaScript Services se skládá ze dvou samostatných balíčků NuGet:
- Microsoft.AspNetCore.NodeServices (NodeServices)
- Microsoft.AspNetCore.SpaServices (SpaServices)
Tyto balíčky jsou užitečné v následujících scénářích:
- Spuštění JavaScriptu na serveru
- Použití architektury nebo knihovny SPA
- Sestavení prostředků na straně klienta pomocí webpacku
Většina pozornosti v tomto článku se zaměřuje na použití balíčku SpaServices.
Co je SpaServices
Služba SpaServices byla vytvořena k umístění ASP.NET Core jako upřednostňované platformy na straně serveru pro vytváření spA. Služba SpaServices není nutná k vývoji spA s ASP.NET Core a nezamkne vývojáře do konkrétní klientské architektury.
SpaServices poskytuje užitečnou infrastrukturu, například:
- Prerendering na straně serveru
- Middleware pro vývoj pro Webpack
- Výměna horkého modulu
- Pomocné rutiny směrování
Společně tyto komponenty infrastruktury vylepšují vývojový pracovní postup i prostředí runtime. Komponenty lze přijmout jednotlivě.
Předpoklady pro použití SpaServices
Pokud chcete pracovat se službami SpaServices, nainstalujte následující:
Uzel.js (verze 6 nebo novější) s npm
Pokud chcete ověřit, že jsou tyto komponenty nainstalované a můžete je najít, spusťte z příkazového řádku následující příkaz:
node -v && npm -v
Pokud se nasazuje na web Azure, nevyžaduje se žádná akce – Uzel.js je nainstalovaný a dostupný v serverových prostředích.
.NET Core SDK 2.0 nebo novější
- Ve Windows pomocí sady Visual Studio 2017 se sada SDK nainstaluje výběrem úlohy vývoje pro různé platformy .NET Core.
Prerendering na straně serveru
Univerzální (označovaná také jako isomorfní) aplikace je javascriptová aplikace schopná běžet na serveru i klientovi. Angular, React a další oblíbené architektury poskytují univerzální platformu pro tento styl vývoje aplikací. Cílem je nejprve vykreslit komponenty architektury na serveru prostřednictvím Node.js Pak delegovat další spuštění klientovi.
ASP.NET pomocné rutiny základních značek poskytované spaServices zjednodušují implementaci předrenderování na straně serveru vyvoláním funkcí JavaScriptu na serveru.
Požadavky na předkreslování na straně serveru
Nainstalujte balíček npm s prerenderingem aspnet-prerendering:
npm i -S aspnet-prerendering
Konfigurace předrenderování na straně serveru
Pomocné rutiny značek jsou zjistitelné prostřednictvím registrace oboru názvů v souboru projektu _ViewImports.cshtml
:
@using SpaServicesSampleApp
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"
Tyto pomocné rutiny značek abstrahují složité komunikace přímo s rozhraními API nízké úrovně pomocí syntaxe podobné HTML v Razor zobrazení:
<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>
Asp-prerender-module Tag Helper
Pomocná rutina asp-prerender-module
značky použitá v předchozím příkladu kódu se ClientApp/dist/main-server.js
provede na serveru prostřednictvím Node.js. Z důvodu srozumitelnosti main-server.js
je soubor artefaktem úlohy transpilace TypeScript-to-JavaScript v procesu sestavení Webpacku . Webpack definuje alias vstupního bodu pro main-server
; a procházení grafu závislostí pro tento alias začíná v ClientApp/boot-server.ts
souboru:
entry: { 'main-server': './ClientApp/boot-server.ts' },
V následujícím příkladu ClientApp/boot-server.ts
Angular soubor využívá createServerRenderer
funkci a RenderResult
typ aspnet-prerendering
balíčku npm ke konfiguraci vykreslování serveru přes Node.js. Kód HTML určený pro vykreslování na straně serveru se předává volání funkce překladu, které je zabaleno do objektu JavaScriptu Promise
silného typu. Význam Promise
objektu je, že asynchronně dodává kód HTML na stránku pro injektáž do zástupného elementu MODELU 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
Při spojení s pomocným rutinou asp-prerender-module
asp-prerender-data
značek lze pomocnou rutinu značek použít k předání kontextových informací ze Razor zobrazení do JavaScriptu na straně serveru. Například následující kód předá do modulu uživatelská data main-server
:
<app asp-prerender-module="ClientApp/dist/main-server"
asp-prerender-data='new {
UserName = "John Doe"
}'>Loading...</app>
Přijatý UserName
argument je serializován pomocí integrovaného serializátoru JSON a je uložen v objektu params.data
. V následujícím příkladu Angular se data používají k vytvoření přizpůsobeného pozdravu v elementu 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();
});
});
});
});
});
Názvy vlastností předané v pomocných rutinách značek jsou reprezentovány zápisem PascalCase . Na rozdíl od JavaScriptu, kde jsou stejné názvy vlastností reprezentovány ve camelCase. Za tento rozdíl zodpovídá výchozí konfigurace serializace JSON.
Pokud chcete rozšířit předchozí příklad kódu, mohou být data předána ze serveru do zobrazení hydratací globals
vlastnosti poskytnuté funkci 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();
});
});
});
});
});
Pole postList
definované uvnitř objektu globals
je připojeno k globálnímu window
objektu prohlížeče. Tato proměnná nahození do globálního oboru eliminuje duplicitu úsilí, zejména pokud se týká načítání stejných dat na serveru a znovu na klientovi.
Middleware pro vývoj pro Webpack
Webpack Dev Middleware představuje zjednodušený vývojový pracovní postup, kdy Webpack vytváří prostředky na vyžádání. Middleware se automaticky zkompiluje a obsluhuje prostředky na straně klienta, když se stránka znovu načte v prohlížeči. Alternativním přístupem je ruční vyvolání webpacku prostřednictvím skriptu sestavení npm projektu, když se změní závislost třetí strany nebo vlastní kód. Skript sestavení npm v package.json
souboru je uveden v následujícím příkladu:
"build": "npm run build:vendor && npm run build:custom",
Požadavky na Webpack Dev Middleware
Nainstalujte balíček npm aspnet-webpack:
npm i -D aspnet-webpack
Konfigurace middlewaru Pro vývoj pro Webpack
Webpack Dev Middleware se zaregistruje do kanálu požadavku HTTP prostřednictvím následujícího kódu v Startup.cs
metodě souboru Configure
:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
// Call UseWebpackDevMiddleware before UseStaticFiles
app.UseStaticFiles();
Před UseWebpackDevMiddleware
registrací hostování statického UseStaticFiles
souboru prostřednictvím metody rozšíření musí být volána metoda rozšíření. Z bezpečnostních důvodů zaregistrujte middleware jenom v případě, že aplikace běží ve vývojovém režimu.
Vlastnost webpack.config.js
souboru output.publicPath
říká middlewaru, aby sledoval dist
změny složky:
module.exports = (env) => {
output: {
filename: '[name].js',
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
Výměna horkého modulu
Funkci výměny horkého modulu (HMR) webpacku si můžete představit jako vývoj webpacku dev middleware. HMR přináší všechny stejné výhody, ale dále zjednodušuje vývojový pracovní postup tím, že po kompilaci změn automaticky aktualizuje obsah stránky. Nezaměňujte to s aktualizací prohlížeče, která by ovlivnila aktuální stav v paměti a ladicí relaci spa. Mezi službou Webpack Dev Middleware a prohlížečem je živý odkaz, což znamená, že se do prohlížeče nasdílí změny.
Požadavky na nahrazení horkého modulu
Nainstalujte balíček npm webpack-hot-middleware:
npm i -D webpack-hot-middleware
Konfigurace nahrazení horkého modulu
Komponenta HMR musí být zaregistrovaná v kanálu požadavku HTTP MVC v Configure
metodě:
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
HotModuleReplacement = true
});
Stejně jako u middlewaruUseWebpackDevMiddleware
Webpack Dev Musí být volána metoda rozšíření před metodou UseStaticFiles
rozšíření. Z bezpečnostních důvodů zaregistrujte middleware jenom v případě, že aplikace běží ve vývojovém režimu.
Soubor webpack.config.js
musí definovat plugins
pole, i když zůstane prázdné:
module.exports = (env) => {
plugins: [new CheckerPlugin()]
Po načtení aplikace v prohlížeči se na kartě Konzola vývojářských nástrojů potvrdí aktivace HMR:
Pomocné rutiny směrování
Ve většině ASP.NET směrovacích služeb založených na jádrech je směrování na straně klienta často žádoucí kromě směrování na straně serveru. Systémy směrování SPA a MVC můžou fungovat nezávisle bez rušení. Existuje však jeden hraniční případ, který představuje problémy: identifikace odpovědí HTTP 404.
Představte si scénář, ve kterém se používá trasa /some/page
bez rozšíření. Předpokládejme, že požadavek neodpovídá směrování na straně serveru, ale jeho vzor odpovídá trase na straně klienta. Nyní zvažte příchozí požadavek , /images/user-512.png
který obecně očekává nalezení souboru obrázku na serveru. Pokud se tato požadovaná cesta k prostředku neshoduje s žádnou trasou na straně serveru nebo statickým souborem, je nepravděpodobné, že by ji aplikace na straně klienta zpracovávala – obecně se vyžaduje stavový kód HTTP 404.
Požadavky pomocných rutin směrování
Nainstalujte balíček npm směrování na straně klienta. Použití Angular jako příkladu:
npm i -S @angular/router
Konfigurace pomocných rutin směrování
V metodě se používá Configure
metoda rozšíření s názvemMapSpaFallbackRoute
:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
Trasy se vyhodnocují v pořadí, v jakém jsou nakonfigurované. V důsledku toho default
se trasa v předchozím příkladu kódu používá jako první pro porovnávání vzorů.
Vytvoření nového projektu
Služby JavaScript Services poskytují předem nakonfigurované šablony aplikací. SpaServices se v těchto šablonách používá ve spojení s různými architekturami a knihovnami, jako jsou Angular, React a Redux.
Tyto šablony je možné nainstalovat prostřednictvím rozhraní příkazového řádku .NET spuštěním následujícího příkazu:
dotnet new --install Microsoft.AspNetCore.SpaTemplates::*
Zobrazí se seznam dostupných šablon SPA:
Šablony | Krátký název | Jazyk | Značky |
---|---|---|---|
MVC ASP.NET Core s Angular | úhlový | [C#] | Web/MVC/SPA |
MVC ASP.NET Core s Reactem.js | react | [C#] | Web/MVC/SPA |
MVC ASP.NET Core s Reactem.js a Redux | reactredux | [C#] | Web/MVC/SPA |
Pokud chcete vytvořit nový projekt pomocí jedné ze šablon SPA, zahrňte do nového příkazu dotnet krátký název šablony. Následující příkaz vytvoří aplikaci Angular s nakonfigurovaným ASP.NET Core MVC na straně serveru:
dotnet new angular
Nastavení režimu konfigurace modulu runtime
Existují dva primární režimy konfigurace modulu runtime:
- Vývoj:
- Zahrnuje zdrojové mapy pro usnadnění ladění.
- Neoptimalizuje kód na straně klienta pro výkon.
- Výroba:
- Vyloučí zdrojové mapy.
- Optimalizuje kód na straně klienta prostřednictvím sdružování a minifikace.
ASP.NET Core používá proměnnou prostředí s názvem ASPNETCORE_ENVIRONMENT
k uložení režimu konfigurace. Další informace najdete v tématu Nastavení prostředí.
Spuštění pomocí rozhraní příkazového řádku .NET
Obnovte požadované balíčky NuGet a npm spuštěním následujícího příkazu v kořenovém adresáři projektu:
dotnet restore && npm i
Sestavte a spusťte aplikaci:
dotnet run
Aplikace se spustí na místním hostiteli podle režimu konfigurace modulu runtime. Když přejdete do http://localhost:5000
prohlížeče, zobrazí se cílová stránka.
Spuštění se sadou Visual Studio 2017
.csproj
Otevřete soubor vygenerovaný novým příkazem dotnet. Požadované balíčky NuGet a npm se po otevření projektu automaticky obnoví. Tento proces obnovení může trvat až několik minut a aplikace je připravená ke spuštění po dokončení. Klikněte na zelené tlačítko spustit nebo stiskněte Ctrl + F5
a prohlížeč se otevře na cílové stránce aplikace. Aplikace běží na místním hostiteli podle režimu konfigurace modulu runtime.
Otestování aplikace
Šablony SpaServices jsou předem nakonfigurované pro spouštění testů na straně klienta pomocí Karma a Jasmine. Jasmine je oblíbená architektura testování jednotek pro JavaScript, zatímco Karma je spouštěč testů pro tyto testy. Karma je nakonfigurovaná tak, aby fungovala s webpackovým middlewarem pro vývoj, takže vývojář nemusí test zastavit a spustit při každém provedení změn. Ať už se jedná o kód spuštěný proti testovacímu případu nebo samotný testovací případ, test se spustí automaticky.
Použití aplikace Angular jako příklad, dva testovací případy Jasmine jsou již k dispozici pro CounterComponent
soubor 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');
}));
Otevřete příkazový řádek v adresáři ClientApp . Spusťte následující příkaz:
npm test
Skript spustí spouštěč testů Karma, který přečte nastavení definovaná karma.conf.js
v souboru. Kromě jiných nastavení identifikuje testovací soubory, karma.conf.js
které se mají spustit prostřednictvím pole files
:
module.exports = function (config) {
config.set({
files: [
'../../wwwroot/dist/vendor.js',
'./boot-tests.ts'
],
Publikování aplikace
Další informace o publikování do Azure najdete v tomto problému na GitHubu.
Kombinace vygenerovaných prostředků na straně klienta a publikovaných artefaktů ASP.NET Core do balíčku připraveného k nasazení může být těžkopádné. SpaServices naštěstí orchestruje celý proces publikování s vlastním cílem MSBuild s názvem 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>
Cíl NÁSTROJE MSBuild má následující odpovědnosti:
- Obnovte balíčky npm.
- Vytvořte sestavení prostředků na straně klienta třetí strany na úrovni produkčního prostředí.
- Vytvořte sestavení vlastních prostředků na straně klienta na úrovni produkčního prostředí.
- Zkopírujte prostředky vygenerované webpackem do složky publikování.
Cíl NÁSTROJE MSBuild se vyvolá při spuštění:
dotnet publish -c Release