Executing Scripts in ASP.NET Core MVC with Blazor Enhanced Navigation
Lee Timmins
2nd Oct 2025
Comments
In my previous post, we looked at enabling Blazor-enhanced navigation in an ASP.NET Core MVC app. One challenge you’ll quickly run into is executing page-specific scripts after navigation updates.
One solution is to use a custom web component to manage script lifecycle events. This approach is explained in the official docs, but here I’ll walk you through how to apply it in an MVC project.
Step 1. Add the BlazorPageScript Library
Create a new file named BlazorPageScript.lib.module.js in your wwwroot/js folder and add the following code:
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);
}
This file defines a <page-script> web component that manages the lifecycle of JavaScript modules when navigating between pages with enhanced navigation.
Step 2. Register the Initializer
Unlike a Blazor app, MVC doesn’t automatically load this script. To fix that, add the following snippet just after the closing </html> tag of your _Layout.cshtml file:
<!--Blazor-Web-Initializers:@Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(@"[ ""/js/BlazorPageScript.lib.module.js"" ]"))-->
This ensures the initializer is registered with Blazor so our component is available.
Step 3. Add a Page Script to Test
Now let’s test the setup. Create a new file called page.js in the wwwroot/js folder:
export function onLoad() { console.log('Loaded'); } export function onUpdate() { console.log('Updated'); } export function onDispose() { console.log('Disposed'); }
Here’s what each function does:
- onLoad → Called when the script is first added to the page.
- onUpdate → Called when the script is still present after an enhanced navigation update.
- onDispose → Called when the script is removed after navigation.
Now add the following inside one of your views (e.g., Index.cshtml):
<page-script src="/js/page.js"></page-script>
Final Thoughts
That’s it! You now have a reusable web component that manages page-level scripts in an MVC app with Blazor-enhanced navigation.
This approach ensures that your scripts load, update, and dispose correctly across enhanced page loads — just like in a full Blazor app.