JavaScript Services gebruiken om toepassingen met één pagina te maken in ASP.NET Core
Door Fiyaz Hasan-
Waarschuwing
De functies die in dit artikel worden beschreven, zijn verouderd vanaf ASP.NET Core 3.0. Een eenvoudiger SPA-frameworks-integratiemechanisme is beschikbaar in het Microsoft.AspNetCore.SpaServices.Extensions NuGet-pakket. Zie voor meer informatie [Aankondiging] Het overbodig maken van Microsoft.AspNetCore.SpaServices en Microsoft.AspNetCore.NodeServices.
Een SPA (Single Page Application) is een populair type webtoepassing vanwege de inherente rijke gebruikerservaring. Het integreren van client-side SPA-frameworks of -bibliotheken, zoals Angular of React, met server-side frameworks zoals ASP.NET Core kan lastig zijn. JavaScript Services is ontwikkeld om wrijving in het integratieproces te verminderen. Het maakt naadloze werking mogelijk tussen de verschillende client- en servertechnologiestacks.
Wat is JavaScript Services?
JavaScript Services is een verzameling technologieën aan de clientzijde voor ASP.NET Core. Het doel is om ASP.NET Core te positioneren als het voorkeursplatform aan de serverzijde van ontwikkelaars voor het bouwen van SPA's.
JavaScript Services bestaat uit twee afzonderlijke NuGet-pakketten:
- Microsoft.AspNetCore.NodeServices (NodeServices)
- Microsoft.AspNetCore.SpaServices (SpaServices)
Deze pakketten zijn handig in de volgende scenario's:
- JavaScript uitvoeren op de server
- Gebruik een Single Page Application-framework of -bibliotheek
- Assets aan clientzijde bouwen met Webpack
Veel van de focus in dit artikel wordt gelegd op het gebruik van het SpaServices-pakket.
Wat is SpaServices?
SpaServices is gemaakt om ASP.NET Core als het voorkeursplatform aan de serverzijde van ontwikkelaars voor het ontwikkelen van SPAs te positioneren. SpaServices is niet vereist om SPA's te ontwikkelen met ASP.NET Core en vergrendelt ontwikkelaars niet in een bepaald clientframework.
SpaServices biedt een nuttige infrastructuur zoals:
Gezamenlijk verbeteren deze infrastructuuronderdelen zowel de ontwikkelwerkstroom als de runtime-ervaring. De onderdelen kunnen afzonderlijk worden aangenomen.
Vereisten voor het gebruik van SpaServices
Als u met SpaServices wilt werken, installeert u het volgende:
Node.js (versie 6 of hoger) met npm
Als u wilt controleren of deze onderdelen zijn geïnstalleerd en u deze kunt vinden, voert u het volgende uit vanaf de opdrachtregel:
node -v && npm -v
Als u implementeert op een Azure-website, is er geen actie vereist:Node.js is geïnstalleerd en beschikbaar in de serveromgevingen.
-
- Op Windows met Visual Studio 2017 wordt de SDK geïnstalleerd door de .NET Core cross-platformontwikkeling workload te selecteren.
Microsoft.AspNetCore.SpaServices NuGet-pakket
Prerendering aan de serverzijde
Een universele toepassing (ook wel isomorf genoemd) is een JavaScript-toepassing die zowel op de server als op de client kan worden uitgevoerd. Angular, React en andere populaire frameworks bieden een universeel platform voor deze toepassingsontwikkelingsstijl. Het idee is eerst de frameworkonderdelen op de server weer te geven via Node.jsen vervolgens verdere uitvoering aan de client te delegeren.
ASP.NET Core Tag Helpers geleverd door SpaServices vereenvoudigen de implementatie van prerendering aan de serverzijde door de JavaScript-functies op de server aan te roepen.
Vereisten voor prerendering aan de serverzijde
Installeer het aspnet-prerendering npm-pakket:
npm i -S aspnet-prerendering
Prerenderingsconfiguratie aan de serverzijde
De Tag Helpers kunnen worden gedetecteerd via naamruimteregistratie in het _ViewImports.cshtml
-bestand van het project:
@using SpaServicesSampleApp
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"
Deze Tag Helpers abstraheren de complexiteit van het rechtstreeks communiceren met API's op laag niveau door gebruik te maken van een HTML-achtige syntaxis in de Razor weergave:
<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>
asp-prerender-module Tag Helper
De asp-prerender-module
Tag Helper, die in het voorgaande codevoorbeeld wordt gebruikt, voert ClientApp/dist/main-server.js
uit op de server via Node.js. In het belang van duidelijkheid is main-server.js
bestand een artefact van de Transpilatietaak TypeScript-naar-JavaScript in het Webpack-buildproces. Webpack definieert een toegangspuntalias main-server
en het doorlopen van de afhankelijkheidsgrafiek met deze alias begint in het ClientApp/boot-server.ts
bestand.
entry: { 'main-server': './ClientApp/boot-server.ts' },
In het volgende Angular-voorbeeld maakt het ClientApp/boot-server.ts
bestand gebruik van de functie createServerRenderer
en RenderResult
type van het aspnet-prerendering
npm-pakket om serverweergave te configureren via Node.js. De HTML-markering die is bestemd voor rendering aan de serverzijde, wordt doorgegeven aan een functieaanroep voor oplossen, die is verpakt in een sterk getypeerd JavaScript-Promise
-object. De betekenis van het Promise
object is dat het asynchroon de HTML-markering levert aan de pagina voor injectie in het tijdelijke aanduidingselement van de 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
In combinatie met de asp-prerender-module
Tag Helper kan de asp-prerender-data
Tag Helper worden gebruikt om contextuele informatie uit de Razor weergave door te geven aan de JavaScript-serverzijde. Met de volgende markering worden gebruikersgegevens bijvoorbeeld doorgegeven aan de main-server
-module:
<app asp-prerender-module="ClientApp/dist/main-server"
asp-prerender-data='new {
UserName = "John Doe"
}'>Loading...</app>
Het ontvangen UserName
argument wordt geserialiseerd met behulp van de ingebouwde JSON-serializer en wordt opgeslagen in het params.data
-object. In het volgende Angular-voorbeeld worden de gegevens gebruikt om een gepersonaliseerde begroeting te maken binnen een h1
element:
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();
});
});
});
});
});
Eigenschapsnamen die worden doorgegeven in Tag Helpers, worden weergegeven met PascalCase notatie. Vergelijk dit met JavaScript, waarbij dezelfde eigenschapsnamen worden weergegeven met camelCase. De standaardconfiguratie voor JSON-serialisatie is verantwoordelijk voor dit verschil.
Als u het voorgaande codevoorbeeld wilt uitbreiden, kunnen gegevens van de server worden doorgegeven aan de weergave door de eigenschap globals
te hydrateren die aan de resolve
functie is verstrekt:
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();
});
});
});
});
});
De postList
matrix die in het globals
-object is gedefinieerd, wordt gekoppeld aan het globale window
-object van de browser. Deze variabele die naar een globaal bereik wordt ge hoist, elimineert duplicatie van de inspanning, met name omdat het betrekking heeft op het laden van dezelfde gegevens eenmaal op de server en opnieuw op de client.
Webpack Dev Middleware
Webpack Dev Middleware introduceert een gestroomlijnde ontwikkelwerkstroom waarbij Webpack resources op aanvraag bouwt. De middleware compileert automatisch en dient client-side bronnen wanneer een pagina opnieuw wordt geladen in de browser. De alternatieve methode is om webpack handmatig aan te roepen via het npm-buildscript van het project wanneer een afhankelijkheid van derden of de aangepaste code wordt gewijzigd. In het volgende voorbeeld wordt een npm-buildscript in het package.json
-bestand weergegeven:
"build": "npm run build:vendor && npm run build:custom",
Vereisten voor Webpack Dev Middleware
Installeer het aspnet-webpack npm-pakket:
npm i -D aspnet-webpack
Configuratie van Webpack Dev Middleware
Webpack Dev Middleware is geregistreerd in de HTTP-aanvraagpijplijn via de volgende code in de Configure
methode van het Startup.cs
-bestand:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
// Call UseWebpackDevMiddleware before UseStaticFiles
app.UseStaticFiles();
De UseWebpackDevMiddleware
-extensiemethode moet worden aangeroepen voordat het hosten van statische bestanden registreert via de UseStaticFiles
-extensiemethode. Registreer om veiligheidsredenen de middleware alleen wanneer de app wordt uitgevoerd in de ontwikkelmodus.
De eigenschap output.publicPath
van het webpack.config.js
-bestand instrueert de middleware de map dist
in de gaten te houden op wijzigingen.
module.exports = (env) => {
output: {
filename: '[name].js',
publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
Vervanging van hotmodule
Denk aan de functie Hot Module Replacement (HMR) van Webpack als een evolutie van Webpack Dev Middleware. HMR introduceert dezelfde voordelen, maar het stroomlijnt de ontwikkelwerkstroom verder door pagina-inhoud automatisch bij te werken na het compileren van de wijzigingen. Verwar dit niet met een vernieuwen van de browser, wat de huidige in-memory toestand en foutopsporingssessie van de Single Page Application (SPA) zou verstoren. Er is een livekoppeling tussen de Webpack Dev Middleware-service en de browser. Dit betekent dat wijzigingen naar de browser worden gepusht.
Vereisten voor vervanging van hotmodules
Installeer het webpack-hot-middleware npm-pakket:
npm i -D webpack-hot-middleware
Vervangingsconfiguratie voor hotmodules
Het HMR-onderdeel moet worden geregistreerd bij de HTTP-aanvraagpijplijn van MVC in de Configure
methode:
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
HotModuleReplacement = true
});
Zoals het geval was met Webpack Dev Middleware, moet de UseWebpackDevMiddleware
-extensiemethode worden aangeroepen vóór de UseStaticFiles
-extensiemethode. Registreer om veiligheidsredenen de middleware alleen wanneer de app wordt uitgevoerd in de ontwikkelmodus.
Het webpack.config.js
-bestand moet een plugins
matrix definiëren, zelfs als het leeg blijft:
module.exports = (env) => {
plugins: [new CheckerPlugin()]
Na het laden van de app in de browser, biedt het consoletabblad van de ontwikkelhulpprogramma's een bevestiging van HMR-activering:
Hulp voor routering
In de meeste ASP.NET Core-SPA's is routering aan de clientzijde vaak gewenst naast routering aan de serverzijde. De SPA- en MVC-routeringssystemen kunnen onafhankelijk werken zonder interferentie. Er is echter één edge-case die uitdagingen met zich mee brengt: het identificeren van 404 HTTP-antwoorden.
Houd rekening met het scenario waarin een extensieloze route van /some/page
wordt gebruikt. Stel dat de aanvraag niet overeenkomt met een route aan de serverzijde, maar het patroon komt wel overeen met een route aan de clientzijde. Overweeg nu een binnenkomende aanvraag voor /images/user-512.png
, die over het algemeen verwacht een afbeeldingsbestand op de server te vinden. Als het aangevraagde resourcepad niet overeenkomt met een route aan de serverzijde of een statisch bestand, is het onwaarschijnlijk dat de toepassing aan de clientzijde dit zou verwerken. Over het algemeen is het gewenst om een 404 HTTP-statuscode te retourneren.
Vereisten voor routeringshelpers
Installeer het npm-pakket voor routering aan de clientzijde. Angular gebruiken als voorbeeld:
npm i -S @angular/router
Configuratie van routeringshelpers
Een extensiemethode met de naam MapSpaFallbackRoute
wordt gebruikt in de methode 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" });
});
Routes worden geëvalueerd in de volgorde waarin ze zijn geconfigureerd. Daarom wordt de default
route in het voorgaande codevoorbeeld eerst gebruikt voor patroonkoppeling.
Een nieuw project maken
JavaScript Services bieden vooraf geconfigureerde toepassingssjablonen. SpaServices wordt gebruikt in deze sjablonen in combinatie met verschillende frameworks en bibliotheken, zoals Angular, React en Redux.
Deze sjablonen kunnen worden geïnstalleerd via de .NET CLI door de volgende opdracht uit te voeren:
dotnet new --install Microsoft.AspNetCore.SpaTemplates::*
Er wordt een lijst met beschikbare Single Page Application-sjablonen weergegeven:
Sjablonen | Korte naam | Taal | Tags |
---|---|---|---|
MVC ASP.NET Core met Angular | hoekig | [C#] | Web/MVC/SPA |
MVC ASP.NET Core met React.js | reageren | [C#] | Web/MVC/SPA |
MVC ASP.NET Core met React.js en Redux | reactredux | [C#] | Web/MVC/SPA |
Als u een nieuw project wilt maken met een van de beveiligd-WACHTWOORDVERIFICATIE-sjablonen, neemt u de korte naam van de sjabloon op in de opdracht dotnet new. Met de volgende opdracht maakt u een Angular-toepassing met ASP.NET Core MVC geconfigureerd voor de serverzijde:
dotnet new angular
Stel de runtimeconfiguratiemodus in
Er bestaan twee primaire runtimeconfiguratiemodi:
-
Ontwikkeling:
- Bevat brontoewijzingen om foutopsporing te vereenvoudigen.
- Optimaliseert de code aan de clientzijde niet voor prestaties.
-
Productie:
- Hiermee worden brontoewijzingen uitgesloten.
- Optimaliseert de code aan de clientzijde via bundeling en minificatie.
ASP.NET Core maakt gebruik van een omgevingsvariabele met de naam ASPNETCORE_ENVIRONMENT
om de configuratiemodus op te slaan. Zie De omgeving instellenvoor meer informatie.
Uitvoeren met .NET CLI
Herstel de vereiste NuGet- en NPM-pakketten door de volgende opdracht uit te voeren in de hoofdmap van het project:
dotnet restore && npm i
Bouw en voer de toepassing uit:
dotnet run
De toepassing wordt gestart op localhost volgens de runtimeconfiguratiemodus. Als u in de browser naar http://localhost:5000
navigeert, wordt de landingspagina weergegeven.
Uitvoeren met Visual Studio 2017
Open het .csproj
bestand dat is gegenereerd door de opdracht dotnet new. De vereiste NuGet- en NPM-pakketten worden automatisch hersteld wanneer het project is geopend. Dit herstelproces kan enkele minuten duren en de toepassing kan worden uitgevoerd wanneer deze is voltooid. Klik op de groene knop Uitvoeren of druk op Ctrl + F5
en de browser wordt geopend op de landingspagina van de toepassing. De toepassing wordt uitgevoerd op localhost volgens de runtime-configuratiemodus.
De app testen
SpaServices-sjablonen zijn vooraf geconfigureerd voor het uitvoeren van tests aan de clientzijde met behulp van Karma en Jasmine-. Jasmine is een populair framework voor eenheidstests voor JavaScript, terwijl Karma een testloper is voor deze tests. Karma is geconfigureerd om te werken met de Webpack Dev Middleware zodat de ontwikkelaar niet hoeft te stoppen en de test uit te voeren telkens wanneer er wijzigingen worden aangebracht. Of het nu gaat om de code die wordt uitgevoerd voor de testcase of de testcase zelf, de test wordt automatisch uitgevoerd.
Met behulp van de Angular-toepassing als voorbeeld zijn er al twee Jasmine-testcases opgegeven voor de CounterComponent
in het bestand 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');
}));
Open de opdrachtprompt in de map ClientApp. Voer de volgende opdracht uit:
npm test
Het script start de Karma-testrunner, die de instellingen leest die zijn gedefinieerd in het bestand karma.conf.js
. Onder andere identificeert de karma.conf.js
de testbestanden die moeten worden uitgevoerd via de files
array.
module.exports = function (config) {
config.set({
files: [
'../../wwwroot/dist/vendor.js',
'./boot-tests.ts'
],
De app publiceren
Zie dit GitHub-issue voor meer gegevens over het publiceren naar Azure.
Het combineren van de gegenereerde assets aan de clientzijde en de gepubliceerde ASP.NET Core-artefacten in een kant-en-klaar pakket kan lastig zijn. Gelukkig organiseert SpaServices dat hele publicatieproces met een aangepast MSBuild-doel met de naam 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>
Het MSBuild-doel heeft de volgende verantwoordelijkheden:
- Herstel de npm-pakketten.
- Maak een build op productieniveau van de assets aan de clientzijde van derden.
- Maak een build op productieniveau van de aangepaste assets aan de clientzijde.
- Kopieer de door Webpack gegenereerde assets naar de publicatiemap.
Het MSBuild-doel wordt aangeroepen bij het uitvoeren:
dotnet publish -c Release