ASP.NET Core Blazor JavaScript con rendering statico lato server (SSR statico)

Questo articolo illustra come caricare JavaScript (JS) in un'app Blazor Web con rendering statico lato server (SSR statico) e spostamento avanzato.

Alcune app dipendono JS da per eseguire attività di inizializzazione specifiche di ogni pagina. Quando si usa Blazorla funzionalità di spostamento avanzata, che consente all'utente di evitare di ricaricare l'intera pagina, è possibile che non JS venga eseguito di nuovo come previsto ogni volta che si verifica un spostamento di pagina avanzato.

Per evitare questo problema, non è consigliabile basarsi su elementi specifici <script> della pagina posizionati all'esterno del file di layout applicato al componente. Gli script devono invece registrare un afterWebStartedJS inizializzatore per eseguire la logica di inizializzazione e usare un listener di eventi (blazor.addEventListener("enhancedload", callback)) per ascoltare gli aggiornamenti delle pagine causati dalla navigazione avanzata.

Nell'esempio seguente viene illustrato un modo per configurare JS il codice da eseguire quando viene inizialmente caricato o aggiornato una pagina sottoposta a rendering statico con navigazione avanzata.

L'esempio di componente seguente PageWithScript è un componente nell'app che richiede l'esecuzione di script con SSR statico e navigazione avanzata. L'esempio di componente seguente include un PageScript componente di una libreria di Razor classi (RCL) aggiunta alla soluzione più avanti in questo articolo.

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.

Nell'app Blazor Web aggiungere il file collocato JS seguente:

  • onLoad viene chiamato quando lo script viene aggiunto alla pagina.
  • onUpdate viene chiamato quando lo script esiste ancora nella pagina dopo un aggiornamento avanzato.
  • onDispose viene chiamato quando lo script viene rimosso dalla pagina dopo un aggiornamento avanzato.

Components/Pages/PageWithScript.razor.js:

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

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

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

In una libreria di Razor classi (RCL) (l'esempio RCL è denominato BlazorPageScript), aggiungere il modulo seguente.

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

Nell'RCL aggiungere il componente seguente PageScript .

PageScript.razor:

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

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

Il PageScript componente funziona normalmente nel livello superiore di una pagina.

Se si inserisce il PageScript componente nel layout di un'app (ad esempio , MainLayout.razor), che genera una condivisione PageScript tra le pagine che usano il layout, il componente viene eseguito onLoad solo dopo un ricaricamento completo della pagina e onUpdate quando si verifica un aggiornamento di pagina avanzato, inclusa la navigazione avanzata.

Per riutilizzare lo stesso modulo tra le pagine, ma richiamare i onLoad callback e onDispose in ogni modifica della pagina, aggiungere una stringa di query alla fine dello script in modo che venga riconosciuta come un modulo diverso. Un'app potrebbe adottare la convenzione di usare il nome del componente come valore della stringa di query. Nell'esempio seguente la stringa di query è "counter" perché questo PageScript riferimento al componente viene inserito in un Counter componente. Si tratta semplicemente di un suggerimento ed è possibile usare qualsiasi schema di stringa di query preferito.

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

Per monitorare le modifiche in elementi DOM specifici, usare il MutationObserver modello in JS nel client. Per altre informazioni, vedere ASP.NET Core JavaScript interoperabilità (interoperabilità).For more information, see ASP.NET Core Blazor JavaScript interoperability (JS interop).

Implementazione di esempio senza usare un RCL

L'approccio descritto in questo articolo può essere implementato direttamente in un'app Blazor Web senza usare una Razor libreria di classi (RCL). Per un esempio, vedere Abilitare la generazione di codice a matrice per le app di autenticazione TOTP in un'app Web di ASP.NET CoreBlazor.