ASP.NET Core Blazor JavaScript mit statischem serverseitigen Rendering (statisches SSR)

In diesem Artikel wird erläutert, wie JavaScript (JS) in einer Blazor-Web-App mit statischem serverseitigen Rendering (statisches SSR) und erweiterter Navigation geladen wird.

Einige Anwendungen hängen von JS ab, um Initialisierungsaufgaben auszuführen, die für jede Seite spezifisch sind. Wenn Sie die erweiterte Navigationsfunktion Blazor verwenden, die es dem Benutzer ermöglicht, das Neuladen der gesamten Seite zu vermeiden, wird die seitenbezogene Funktion JS möglicherweise nicht wie erwartet jedes Mal erneut ausgeführt, wenn eine erweiterte Seitennavigation erfolgt.

Um dieses Problem zu vermeiden, empfiehlt es sich nicht, auf seitenspezifische <script>-Elemente zu vertrauen, die außerhalb der Layout-Datei platziert wurden, die auf die Komponente angewendet wurde. Stattdessen sollten Skripts einen afterWebStartedJS-Initialisierer registrieren, um Initialisierungslogik auszuführen, und einen Ereignislistener (blazor.addEventListener("enhancedload", callback)) verwenden, um auf Seitenaktualisierungen zu lauschen, die durch die erweiterte Navigation ausgelöst werden.

Im folgenden Beispiel wird eine Möglichkeit zum Konfigurieren von JS-Code veranschaulicht, der ausgeführt wird, wenn eine statisch gerenderte Seite mit erweiterter Navigation anfänglich geladen oder aktualisiert wird.

Das folgende PageWithScript-Komponentenbeispiel ist eine Komponente in der App, für die Skripts mit statischem SSR und erweiterter Navigation ausgeführt werden müssen. Das folgende Komponentenbeispiel enthält eine PageScript-Komponente aus einer Razor-Klassenbibliothek (RCL), die weiter unten in diesem Artikel zur Projektmappe hinzugefügt wird.

Components/Pages/PageWithScript.razor:

@page "/page-with-script"
@using BlazorPageScript

<PageTitle>Enhanced Load Script Example</PageTitle>

<PageScript Src="./Components/Pages/PageWithScript.razor.js" />

Welcome to my page.

Fügen Sie in der Blazor-Webanwendung die folgende zusammengestellte JS Datei hinzu:

  • onLoad wird aufgerufen, wenn das Skript der Seite hinzugefügt wird.
  • onUpdate wird aufgerufen, wenn das Skript nach einer erweiterten Aktualisierung noch auf der Seite vorhanden ist.
  • onDispose wird aufgerufen, wenn das Skript nach einer erweiterten Aktualisierung von der Seite entfernt wird.

Components/Pages/PageWithScript.razor.js:

export function onLoad() {
  console.log('Loaded');
}

export function onUpdate() {
  console.log('Updated');
}

export function onDispose() {
  console.log('Disposed');
}

Fügen Sie in einer Razor-Klassenbibliothek (RCL) (die Beispiel-RCL heißt BlazorPageScript) das folgende Modul hinzu.

wwwroot/BlazorPageScript.lib.module.js:

const pageScriptInfoBySrc = new Map();

function registerPageScriptElement(src) {
  if (!src) {
    throw new Error('Must provide a non-empty value for the "src" attribute.');
  }

  let pageScriptInfo = pageScriptInfoBySrc.get(src);

  if (pageScriptInfo) {
    pageScriptInfo.referenceCount++;
  } else {
    pageScriptInfo = { referenceCount: 1, module: null };
    pageScriptInfoBySrc.set(src, pageScriptInfo);
    initializePageScriptModule(src, pageScriptInfo);
  }
}

function unregisterPageScriptElement(src) {
  if (!src) {
    return;
  }

  const pageScriptInfo = pageScriptInfoBySrc.get(src);
  
  if (!pageScriptInfo) {
    return;
  }

  pageScriptInfo.referenceCount--;
}

async function initializePageScriptModule(src, pageScriptInfo) {
  if (src.startsWith("./")) {
    src = new URL(src.substr(2), document.baseURI).toString();
  }

  const module = await import(src);

  if (pageScriptInfo.referenceCount <= 0) {
    return;
  }

  pageScriptInfo.module = module;
  module.onLoad?.();
  module.onUpdate?.();
}

function onEnhancedLoad() {
  for (const [src, { module, referenceCount }] of pageScriptInfoBySrc) {
    if (referenceCount <= 0) {
      module?.onDispose?.();
      pageScriptInfoBySrc.delete(src);
    }
  }

  for (const { module } of pageScriptInfoBySrc.values()) {
    module?.onUpdate?.();
  }
}

export function afterWebStarted(blazor) {
  customElements.define('page-script', class extends HTMLElement {
    static observedAttributes = ['src'];

    attributeChangedCallback(name, oldValue, newValue) {
      if (name !== 'src') {
        return;
      }

      this.src = newValue;
      unregisterPageScriptElement(oldValue);
      registerPageScriptElement(newValue);
    }

    disconnectedCallback() {
      unregisterPageScriptElement(this.src);
    }
  });

  blazor.addEventListener('enhancedload', onEnhancedLoad);
}

Fügen Sie in der RCL die folgende PageScript-Komponente hinzu.

PageScript.razor:

<page-script src="@Src"></page-script>

@code {
    [Parameter]
    [EditorRequired]
    public string Src { get; set; } = default!;
}

Die PageScript-Komponente funktioniert normalerweise auf der obersten Ebene einer Seite.

Wenn Sie die PageScript-Komponente in ein App-Layout einfügen (z. B. MainLayout.razor) und damit ein gemeinsam genutztes PageScript für die Seiten erhalten, die das Layout verwenden, führt die Komponente onLoad nur nach dem vollständigen erneuten Laden der Seite aus und onUpdate, wenn eine erweiterte Seitenaktualisierung erfolgt (einschließlich erweiterter Navigation).

Wenn Sie dasselbe Modul auf verschiedenen Seiten wiederverwenden möchten, aber die Rückrufe onLoad und onDispose bei jeder Seitenänderung aufrufen, fügen Sie eine Abfragezeichenfolge an das Ende des Skripts an, damit es als ein anderes Modul erkannt wird. Sie können für eine App als Konvention festlegen, dass der Komponentenname als Abfragezeichenfolgenwert verwendet wird. Im folgenden Beispiel lautet die Abfragezeichenfolge „counter“, da dieser PageScript-Komponentenverweis in eine Counter-Komponente eingefügt wurde. Dies ist lediglich ein Vorschlag, Sie können ein beliebiges Schema für Abfragezeichenfolgen verwenden.

<PageScript Src="./Components/Pages/PageWithScript.razor.js?counter" />

Verwenden Sie das MutationObserver-Muster in JS auf dem Client, um Änderungen in bestimmten DOM-Elementen zu überwachen. Weitere Informationen finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Beispielimplementierung ohne Verwendung einer RCL

Der in diesem Artikel beschriebene Ansatz kann direkt in einer Blazor-Web-App implementiert werden, ohne eine Razor-Klassenbibliothek (RCL) zu verwenden. Ein Beispiel finden Sie unter Aktivieren der QR-Code-Generierung für TOTP-Authentifikator-Apps in einer ASP.NET Core Blazor-Web-App.