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:

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:

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í:

  • Node.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 – Node.js se nainstaluje a zpřístupní 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.
  • Balíček NuGet Microsoft.AspNetCore.SpaServices

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 a 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 ClientApp/dist/main-server.js použitá v předchozím příkladu kódu se 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-moduleasp-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í předdefinovaného JSON serializátoru 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í JSkonfigurace serializace ON.

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.

global postList variable attached to window object

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:

Hot Module Replacement connected message

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.pngkterý 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 Core 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 React.js react [C#] Web/MVC/SPA
MVC ASP.NET Core s React.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 Core

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 + F5a 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:

  1. Obnovte balíčky npm.
  2. Vytvořte sestavení prostředků na straně klienta třetí strany na úrovni produkčního prostředí.
  3. Vytvořte sestavení vlastních prostředků na straně klienta na úrovni produkčního prostředí.
  4. 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

Další prostředky