ASP.NET Core'da Tek Sayfalı Uygulamalar Oluşturmak için JavaScript Hizmetlerini Kullanma

Tarafından Fiyaz Hasan

Uyarı

Bu makalede açıklanan özellikler, ASP.NET Core 3.0 itibarıyla kullanımdan kaldırılmaktadır. Microsoft.AspNetCore.SpaServices.Extensions NuGet paketinde daha basit bir SPA çerçeveleri tümleştirme mekanizması sağlanır. Daha fazla bilgi için bkz . [Duyuru] Microsoft.AspNetCore.SpaServices ve Microsoft.AspNetCore.NodeServices'i kullanımdan kaldırma.

Tek Sayfalı Uygulama (SPA), doğası gereği zengin kullanıcı deneyimi nedeniyle popüler bir web uygulaması türüdür. Angular veya React gibi istemci tarafı SPA çerçevelerini veya kitaplıklarını ASP.NET Core gibi sunucu tarafı çerçevelerle tümleştirmek zor olabilir. JavaScript Hizmetleri, tümleştirme sürecindeki uyuşmaları azaltmak için geliştirilmiştir. Farklı istemci ve sunucu teknolojisi yığınları arasında sorunsuz işlem sağlar.

JavaScript Hizmetleri nedir?

JavaScript Hizmetleri, ASP.NET Core için istemci tarafı teknolojilerinden oluşan bir koleksiyondur. Amacı, ASP.NET Core'un SPA'lar oluşturmak için geliştiricilerin tercih ettiği sunucu tarafı platformu olarak konumlandırmaktır.

JavaScript Hizmetleri iki ayrı NuGet paketinden oluşur:

Bu paketler aşağıdaki senaryolarda kullanışlıdır:

  • Sunucuda JavaScript çalıştırma
  • SPA çerçevesi veya kitaplığı kullanma
  • Webpack ile istemci tarafı varlıkları oluşturma

Bu makaledeki odağın büyük kısmı SpaServices paketini kullanmaya odaklanır.

SpaServices nedir?

SpaServices, ASP.NET Core'ı geliştiricilerin SPA'ları oluşturmak için tercih ettiği sunucu tarafı platformu olarak konumlandırmak için oluşturulmuştur. SpaServices'in ASP.NET Core ile SPA geliştirmesi gerekmez ve geliştiricileri belirli bir istemci çerçevesine kilitlemez.

SpaServices aşağıdakiler gibi kullanışlı bir altyapı sağlar:

Bu altyapı bileşenleri toplu olarak hem geliştirme iş akışını hem de çalışma zamanı deneyimini geliştirir. Bileşenler ayrı ayrı benimsenebilir.

SpaServices'i kullanma önkoşulları

SpaServices ile çalışmak için aşağıdakileri yükleyin:

  • npm ile Node.js (sürüm 6 veya üzeri)

    • Bu bileşenlerin yüklendiğini ve bulunabildiğini doğrulamak için komut satırından aşağıdakileri çalıştırın:

      node -v && npm -v
      
    • Azure web sitesine dağıtılıyorsa hiçbir eylem gerekmez; Node.js yüklenir ve sunucu ortamlarında kullanılabilir.

  • .NET Core SDK 2.0 veya üzeri

    • Visual Studio 2017 kullanan Windows'ta SDK, .NET Core platformlar arası geliştirme iş yükü seçilerek yüklenir.
  • Microsoft.AspNetCore.SpaServices NuGet paketi

Sunucu tarafı prerendering

Evrensel (izomorfik olarak da bilinir) uygulaması, hem sunucuda hem de istemcide çalıştırabilen bir JavaScript uygulamasıdır. Angular, React ve diğer popüler çerçeveler, bu uygulama geliştirme stili için evrensel bir platform sağlar. Fikir, önce Node.js aracılığıyla sunucudaki çerçeve bileşenlerini işlemek ve ardından istemciye daha fazla yürütme temsilcisi atamaktır.

SpaServices tarafından sağlanan ASP.NET Çekirdek Etiket Yardımcıları , sunucudaki JavaScript işlevlerini çağırarak sunucu tarafı ön kayıt uygulamasını basitleştirir.

Sunucu tarafı ön kayıt önkoşulları

aspnet-prerendering npm paketini yükleyin:

npm i -S aspnet-prerendering

Sunucu tarafı ön kayıt yapılandırması

Etiket Yardımcıları, projenin _ViewImports.cshtml dosyasındaki ad alanı kaydı aracılığıyla bulunabilir hale getirilir:

@using SpaServicesSampleApp
@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers"
@addTagHelper "*, Microsoft.AspNetCore.SpaServices"

Bu Etiket Yardımcıları, görünümün içindeki HTML benzeri söz dizimlerinden yararlanarak alt düzey API'lerle doğrudan iletişim kurmanın karmaşıklıklarını Razor soyutlar:

<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>

asp-prerender-module Etiket Yardımcısı

Önceki asp-prerender-module kod örneğinde kullanılan Etiket Yardımcısı, Node.js aracılığıyla sunucuda yürütülür ClientApp/dist/main-server.js . Netlik için dosya, main-server.js Webpack derleme işlemindeki TypeScript'den JavaScript'e dönüştürme görevinin bir yapıtıdır. Webpack öğesinin main-servergiriş noktası diğer adını tanımlar ve bu diğer ad için bağımlılık grafiğinin çapraz geçişi dosyada ClientApp/boot-server.ts başlar:

entry: { 'main-server': './ClientApp/boot-server.ts' },

Aşağıdaki Angular örneğindeClientApp/boot-server.ts, dosya Node.js aracılığıyla sunucu işlemeyi yapılandırmak için npm paketinin aspnet-prerendering işlevini ve RenderResult türünü kullanırcreateServerRenderer. Sunucu tarafı işlemeyi hedefleyen HTML işaretlemesi, kesin olarak türü belirlenmiş bir JavaScript Promise nesnesine sarmalanan bir çözüm işlevi çağrısına geçirilir. Nesnenin Promise önemi, DOM'un yer tutucu öğesine eklemek üzere sayfaya zaman uyumsuz olarak HTML işaretlemesi sağladığıdır.

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 Etiket Yardımcısı

Etiket Yardımcısı ile asp-prerender-module birlikte kullanıldığında, asp-prerender-data Etiket Yardımcısı görünümdeki Razor bağlamsal bilgileri sunucu tarafı JavaScript'e geçirmek için kullanılabilir. Örneğin, aşağıdaki işaretleme kullanıcı verilerini modüle main-server geçirir:

<app asp-prerender-module="ClientApp/dist/main-server"
        asp-prerender-data='new {
            UserName = "John Doe"
        }'>Loading...</app>

Alınan UserName bağımsız değişken yerleşik JSON seri hale getiricisi kullanılarak serileştirilir ve nesnesinde params.data depolanır. Aşağıdaki Angular örneğinde veriler, bir h1 öğe içinde kişiselleştirilmiş bir karşılama oluşturmak için kullanılır:

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();
                });
            });
        });
    });
});

Etiket Yardımcıları'nda geçirilen özellik adları PascalCase gösterimiyle temsil edilir. Aynı özellik adlarının camelCase ile temsil edildiği JavaScript ile karşıtlık. Bu fark, varsayılan JSON serileştirme yapılandırmasından sorumludur.

Yukarıdaki kod örneğini genişletmek için, işleve sağlanan özelliğin nemlendirilmesiyle veriler sunucudan globals görünüme resolve geçirilebilir:

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 Nesnenin globals içinde tanımlanan dizi, tarayıcının genel window nesnesine eklenir. Genel kapsama yönelik bu değişken hoisting, özellikle de aynı verileri bir kez sunucuya ve yeniden istemciye yüklemeye yönelik olduğu için çabanın yinelenmesini ortadan kaldırır.

global postList variable attached to window object

Webpack Geliştirici Ara Yazılımı

Webpack Dev Ara Yazılımı , Webpack'in kaynakları isteğe bağlı olarak derlediği kolaylaştırılmış bir geliştirme iş akışı sunar. Ara yazılım, tarayıcıda bir sayfa yeniden yüklendiğinde istemci tarafı kaynaklarını otomatik olarak derler ve hizmet eder. Alternatif yaklaşım, bir üçüncü taraf bağımlılığı veya özel kod değiştiğinde projenin npm derleme betiği aracılığıyla Webpack'i el ile çağırmaktır. Dosyadaki package.json bir npm derleme betiği aşağıdaki örnekte gösterilmiştir:

"build": "npm run build:vendor && npm run build:custom",

Webpack Geliştirme Ara Yazılımı önkoşulları

aspnet-webpack npm paketini yükleyin:

npm i -D aspnet-webpack

Webpack Geliştirme Ara Yazılımı yapılandırması

Webpack Dev Ara Yazılımı, dosyanın Configure yönteminde aşağıdaki kod aracılığıyla HTTP isteği işlem hattına Startup.cs kaydedilir:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseWebpackDevMiddleware();
}
else
{
    app.UseExceptionHandler("/Home/Error");
}

// Call UseWebpackDevMiddleware before UseStaticFiles
app.UseStaticFiles();

UseWebpackDevMiddleware Uzantı yöntemi aracılığıyla UseStaticFiles statik dosya barındırma kaydedilmeden önce uzantı yöntemi çağrılmalıdır. Güvenlik nedeniyle ara yazılımı yalnızca uygulama geliştirme modunda çalıştığında kaydedin.

Dosyanın webpack.config.jsoutput.publicPath özelliği ara yazılıma klasörü değişiklikler için izlemesini dist söyler:

module.exports = (env) => {
        output: {
            filename: '[name].js',
            publicPath: '/dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
        },

Sık Erişimli Modül Değiştirme

Webpack'in Sık Erişimli Modül Değiştirme (HMR) özelliğini Webpack Geliştirme Ara Yazılımının bir evrimi olarak düşünün. HMR tüm avantajları sunar, ancak değişiklikleri derledikten sonra sayfa içeriğini otomatik olarak güncelleştirerek geliştirme iş akışını daha da kolaylaştırır. Bunu tarayıcının yenilemesiyle karıştırmayın; bu, SPA'nın geçerli bellek içi durumu ve hata ayıklama oturumunu engeller. Webpack Geliştirme Ara Yazılımı hizmeti ile tarayıcı arasında canlı bağlantı vardır ve bu da değişikliklerin tarayıcıya gönderildiği anlamına gelir.

Sık Erişimli Modül Değiştirme önkoşulları

Webpack-hot-middleware npm paketini yükleyin:

npm i -D webpack-hot-middleware

Sık Erişimli Modül Değiştirme yapılandırması

HMR bileşeninin yönteminde MVC'nin HTTP isteği işlem hattına Configure kaydedilmesi gerekir:

app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions {
    HotModuleReplacement = true
});

Webpack Dev Ara Yazılımı'nın UseWebpackDevMiddlewaredoğru olduğu gibi, uzantı yöntemi uzantı yönteminden UseStaticFiles önce çağrılmalıdır. Güvenlik nedeniyle ara yazılımı yalnızca uygulama geliştirme modunda çalıştığında kaydedin.

Dosya webpack.config.js boş bıraksa bile bir plugins dizi tanımlamalıdır:

module.exports = (env) => {
        plugins: [new CheckerPlugin()]

Uygulamayı tarayıcıda yükledikten sonra geliştirici araçlarının Konsol sekmesi HMR etkinleştirme onayı sağlar:

Hot Module Replacement connected message

Yönlendirme yardımcıları

Çoğu ASP.NET Çekirdek tabanlı SPA'larda, sunucu tarafı yönlendirmeye ek olarak genellikle istemci tarafı yönlendirmesi de istenir. SPA ve MVC yönlendirme sistemleri, müdahale olmadan bağımsız olarak çalışabilir. Ancak zorluklara neden olan bir uç durum vardır: 404 HTTP yanıtını tanımlama.

Uzantısız bir yolunun kullanıldığı senaryoyu /some/page göz önünde bulundurun. İsteğin bir sunucu tarafı yolu ile desen eşleşmediğini, ancak deseninin istemci tarafı yoluyla eşleşdiğini varsayalım. Şimdi, genellikle sunucuda bir görüntü dosyası bulmayı bekleyen için gelen bir isteği /images/user-512.pnggöz önünde bulundurun. İstenen kaynak yolu herhangi bir sunucu tarafı yolu veya statik dosyayla eşleşmiyorsa, istemci tarafı uygulamasının bunu işleme olasılığı düşüktür; genellikle 404 HTTP durum kodu döndürülmesi istenir.

Yönlendirme yardımcıları önkoşulları

İstemci tarafı yönlendirme npm paketini yükleyin. Örnek olarak Angular'ın kullanılması:

npm i -S @angular/router

Yönlendirme yardımcıları yapılandırması

yönteminde Configure adlı MapSpaFallbackRoute bir uzantı yöntemi kullanılır:

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");

    routes.MapSpaFallbackRoute(
        name: "spa-fallback",
        defaults: new { controller = "Home", action = "Index" });
});

Yollar, yapılandırıldıkları sırayla değerlendirilir. Sonuç olarak, default önceki kod örneğindeki yol önce desen eşleştirme için kullanılır.

Yeni proje oluşturma

JavaScript Hizmetleri önceden yapılandırılmış uygulama şablonları sağlar. SpaServices bu şablonlarda Angular, React ve Redux gibi farklı çerçeveler ve kitaplıklarla birlikte kullanılır.

Bu şablonlar aşağıdaki komutu çalıştırarak .NET Core CLI aracılığıyla yüklenebilir:

dotnet new --install Microsoft.AspNetCore.SpaTemplates::*

Kullanılabilir SPA şablonlarının listesi görüntülenir:

Şablonlar Kısa Ad Dil Etiketler
Angular ile MVC ASP.NET Core Açısal [C#] Web/MVC/SPA
React.js ile MVC ASP.NET Core Tepki [C#] Web/MVC/SPA
React.js ve Redux ile MVC ASP.NET Core reactredux [C#] Web/MVC/SPA

SPA şablonlarından birini kullanarak yeni bir proje oluşturmak için dotnet new komutuna şablonun Kısa Adını ekleyin. Aşağıdaki komut, sunucu tarafı için yapılandırılmış ASP.NET Core MVC ile bir Angular uygulaması oluşturur:

dotnet new angular

Çalışma zamanı yapılandırma modunu ayarlama

İki birincil çalışma zamanı yapılandırma modu vardır:

  • Geliştirme:
    • Hata ayıklamayı kolaylaştırmak için kaynak eşlemeleri içerir.
    • İstemci tarafı kodunu performans için iyileştirmez.
  • Üretim:
    • Kaynak eşlemeleri dışlar.
    • Paketleme ve küçültme yoluyla istemci tarafı kodunu iyileştirir.

ASP.NET Core, yapılandırma modunu depolamak için adlı ASPNETCORE_ENVIRONMENT bir ortam değişkeni kullanır. Daha fazla bilgi için bkz . Ortamı ayarlama.

.NET Core CLI ile çalıştırma

Proje kökünde aşağıdaki komutu çalıştırarak gerekli NuGet ve npm paketlerini geri yükleyin:

dotnet restore && npm i

Uygulamayı derleyin ve çalıştırın:

dotnet run

Uygulama, çalışma zamanı yapılandırma moduna göre localhost'ta başlar. Tarayıcıda adresine giderek http://localhost:5000 giriş sayfası görüntülenir.

Visual Studio 2017 ile çalıştırma

.csproj dotnet new komutu tarafından oluşturulan dosyayı açın. Gerekli NuGet ve npm paketleri proje açıldığında otomatik olarak geri yüklenir. Bu geri yükleme işlemi birkaç dakika kadar sürebilir ve uygulama tamamlandığında çalışmaya hazırdır. Yeşil çalıştırma düğmesine tıklayın veya tuşuna basın Ctrl + F5; tarayıcı uygulamanın giriş sayfasında açılır. Uygulama, çalışma zamanı yapılandırma moduna göre localhost üzerinde çalışır.

Uygulamayı test etme

SpaServices şablonları Karma ve Jasmine kullanarak istemci tarafı testleri çalıştırmak için önceden yapılandırılmıştır. Jasmine, JavaScript için popüler bir birim testi çerçevesidir, Karma ise bu testler için bir test çalıştırıcısıdır. Karma, Webpack Geliştirici Ara Yazılımı ile çalışacak şekilde yapılandırılır, böylece geliştiricinin her değişiklik yapıldığında testi durdurması ve çalıştırması gerekmez. İster test çalışmasına karşı çalışan kod ister test çalışmasının kendisi olsun, test otomatik olarak çalışır.

Angular uygulamasını örnek olarak kullanarak dosyasında için zaten iki Jasmine test çalışması sağlanmıştır CounterComponentcounter.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 dizininde komut istemini açın. Şu komutu çalıştırın:

npm test

Betik, dosyada tanımlanan ayarları okuyan Karma test çalıştırıcısını karma.conf.js başlatır. Diğer ayarlar arasında, karma.conf.js dizisi aracılığıyla files yürütülecek test dosyalarını tanımlar:

module.exports = function (config) {
    config.set({
        files: [
            '../../wwwroot/dist/vendor.js',
            './boot-tests.ts'
        ],

Uygulamayı yayımlayın

Azure'da yayımlama hakkında daha fazla bilgi için bu GitHub sorununa bakın.

Oluşturulan istemci tarafı varlıklarını ve yayımlanan ASP.NET Core yapıtlarını dağıtıma hazır bir pakette birleştirmek zahmetli olabilir. Neyse ki, SpaServices bu yayın işleminin tamamını adlı RunWebpacközel bir MSBuild hedefiyle düzenler:

<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 hedefi aşağıdaki sorumluluklara sahiptir:

  1. npm paketlerini geri yükleyin.
  2. Üçüncü taraf, istemci tarafı varlıkların üretim sınıfı bir derlemesini oluşturun.
  3. Özel istemci tarafı varlıklarının üretim sınıfı derlemesini oluşturun.
  4. Webpack tarafından oluşturulan varlıkları yayımlama klasörüne kopyalayın.

MSBuild hedefi çalıştırılırken çağrılır:

dotnet publish -c Release

Ek kaynaklar