ASP.NET Core Blazor JavaScript interoperability (JS interop)

This article explains general concepts on how to interact with JavaScript in Blazor apps.

A Blazor app can invoke JavaScript (JS) functions from .NET methods and .NET methods from JS functions. These scenarios are called JavaScript interoperability (JS interop).

Further JS interop guidance is provided in the following articles:

Interaction with the Document Object Model (DOM)

Only mutate the Document Object Model (DOM) with JavaScript (JS) when the object doesn't interact with Blazor. Blazor maintains representations of the DOM and interacts directly with DOM objects. If an element rendered by Blazor is modified externally using JS directly or via JS Interop, the DOM may no longer match Blazor's internal representation, which can result in undefined behavior. Undefined behavior may merely interfere with the presentation of elements or their functions but may also introduce security risks to the app or server.

This guidance not only applies to your own JS interop code but also to any JS libraries that the app uses, including anything provided by a third-party framework, such as Bootstrap JS and jQuery.

In a few documentation examples, JS interop is used to mutate an element purely for demonstration purposes as part of an example. In those cases, a warning appears in the text.

For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Asynchronous JavaScript calls

JS interop calls are asynchronous by default, regardless of whether the called code is synchronous or asynchronous. Calls are asynchronous by default to ensure that components are compatible across both Blazor hosting models, Blazor Server and Blazor WebAssembly. On Blazor Server, JS interop calls must be asynchronous because they're sent over a network connection. For apps that exclusively adopt the Blazor WebAssembly hosting model, synchronous JS interop calls are supported.

For more information, see the following articles:

Object serialization

Blazor uses System.Text.Json for serialization with the following requirements and default behaviors:

  • Types must have a default constructor, get/set accessors must be public, and fields are never serialized.
  • Global default serialization isn't customizable to avoid breaking existing component libraries, impacts on performance and security, and reductions in reliability.
  • Serializing .NET member names results in lowercase JSON key names.
  • JSON is deserialized as JsonElement C# instances, which permit mixed casing. Internal casting for assignment to C# model properties works as expected in spite of any case differences between JSON key names and C# property names.

JsonConverter API is available for custom serialization. Properties can be annotated with a [JsonConverter] attribute to override default serialization for an existing data type.

For more information, see the following resources in the .NET documentation:

JSON serializer support for System.DateOnly and System.TimeOnly is planned for ASP.NET Core 7.0, which is scheduled for release by the end of 2022. For more information, see Support DateOnly and TimeOnly in JsonSerializer (dotnet/runtime #53539).

Blazor supports optimized byte array JS interop that avoids encoding/decoding byte arrays into Base64. The app can apply custom serialization and pass the resulting bytes. For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Blazor supports unmarshalled JS interop when a high volume of .NET objects are rapidly serialized or when large .NET objects or many .NET objects must be serialized. For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

JavaScript initializers

JavaScript (JS) initializers execute logic before and after a Blazor app loads. JS initializers are useful in the following scenarios:

  • Customizing how a Blazor app loads.
  • Initializing libraries before Blazor starts up.
  • Configuring Blazor settings.

JS initializers are detected as part of the build process and imported automatically in Blazor apps. Use of JS initializers often removes the need to manually trigger script functions from the app when using Razor class libraries (RCLs).

To define a JS initializer, add a JS module to the project named {NAME}.lib.module.js, where the {NAME} placeholder is the assembly name, library name, or package identifier. Place the file in the project's web root, which is typically the wwwroot folder.

The module exports either or both of the following conventional functions:

  • beforeStart(options, extensions): Called before Blazor starts. For example, beforeStart is used to customize the loading process, logging level, and other options specific to the hosting model.
    • In Blazor WebAssembly, beforeStart receives the Blazor WebAssembly options (options in this section's example) and any extensions (extensions in this section's example) added during publishing. For example, options can specify the use of a custom boot resource loader.
    • In Blazor Server, beforeStart receives SignalR circuit start options (options in this section's example).
    • In BlazorWebViews, no options are passed.
  • afterStarted: Called after Blazor is ready to receive calls from JS. For example, afterStarted is used to initialize libraries by making JS interop calls and registering custom elements. The Blazor instance is passed to afterStarted as an argument (blazor in this section's example).

The following example demonstrates JS initializers for beforeStart and afterStarted. For the filename of the following example:

  • Use the app's assembly name in the filename if the JS initializers are consumed as a static asset in the project. For example, name the file BlazorSample.lib.module.js for a project with an assembly name of BlazorSample. Place the file in the app's wwwroot folder.
  • Use the project's library name or package identifier if the JS initializers are consumed from an RCL. For example, name the file RazorClassLibrary1.lib.module.js for an RCL with a package identifier of RazorClassLibrary1. Place the file in the library's wwwroot folder.
export function beforeStart(options, extensions) {
    console.log("beforeStart");
}

export function afterStarted(blazor) {
    console.log("afterStarted");
}

Note

MVC and Razor Pages apps don't automatically load JS initializers. However, developer code can include a script to fetch the app's manifest and trigger the load of the JS initializers.

For an examples of JS initializers, see the following resources:

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Location of JavaScript

Load JavaScript (JS) code using any of the following approaches:

Warning

Don't place a <script> tag in a Razor component file (.razor) because the <script> tag can't be updated dynamically by Blazor.

Note

Documentation examples usually place scripts in a <script> tag or load global scripts from external files. These approaches pollute the client with global functions. For production apps, we recommend placing JavaScript into separate JavaScript modules that can be imported when needed. For more information, see the JavaScript isolation in JavaScript modules section.

Load a script in <head> markup

The approach in this section isn't generally recommended.

Place the JavaScript (JS) tags (<script>...</script>) in the <head> element markup of wwwroot/index.html (Blazor WebAssembly) or Pages/_Layout.cshtml (Blazor Server):

<head>
    ...

    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</head>

Loading JS from the <head> isn't the best approach for the following reasons:

  • JS interop may fail if the script depends on Blazor. We recommend loading scripts using one of the other approaches, not via the <head> markup.
  • The page may become interactive slower due to the time it takes to parse the JS in the script.

Load a script in <body> markup

Place the JavaScript (JS) tags (<script>...</script>) inside the closing </body> element markup of wwwroot/index.html (Blazor WebAssembly) or Pages/_Layout.cshtml (Blazor Server):

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</body>

The {webassembly|server} placeholder in the preceding markup is either webassembly for a Blazor WebAssembly app (blazor.webassembly.js) or server for a Blazor Server app (blazor.server.js).

Load a script from an external JavaScript file (.js) collocated with a component

Collocation of JavaScript (JS) files for pages, views, and Razor components is a convenient way to organize scripts in an app.

Collocate JS files using the following filename extension conventions:

  • Pages of Razor Pages apps and views of MVC apps: .cshtml.js. Examples:
    • Pages/Index.cshtml.js for the Index page of a Razor Pages app at Pages/Index.cshtml.
    • Views/Home/Index.cshtml.js for the Index view of an MVC app at Views/Home/Index.cshtml.
  • Razor components of Blazor apps: .razor.js. Example: Pages/Index.razor.js for the Index component at Pages/Index.razor.

Collocated JS files are publicly addressable using the path to the file in the project:

  • Pages, views, and components from a collocated scripts file in the app:

    {PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

    • The {PATH} placeholder is the path to the page, view, or component.
    • The {PAGE, VIEW, OR COMPONENT} placeholder is the page, view, or component.
    • The {EXTENSION} placeholder matches the extension of the page, view, or component, either razor or cshtml.

    Razor Pages example:

    A JS file for the Index page is placed in the Pages folder (Pages/Index.cshtml.js) next to the Index page (Pages/Index.cshtml). In the Index page, the script is referenced at the path in the Pages folder:

    @section Scripts {
      <script src="~/Pages/Index.cshtml.js"></script>
    }
    

    When the app is published, the framework automatically moves the script to the web root. In the preceding example, the script is moved to bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js, where the {TARGET FRAMEWORK MONIKER} placeholder is the Target Framework Moniker (TFM). No change is required to the script's relative URL in the Index page.

    Blazor example:

    A JS file for the Index component is placed in the Pages folder (Pages/Index.razor.js) next to the Index component (Pages/Index.razor). In the Index component, the script is referenced at the path in the Pages folder. The following example is based on an example shown in the Call JavaScript functions from .NET methods in ASP.NET Core Blazor article.

    Pages/Index.razor.js:

    export function showPrompt(message) {
      return prompt(message, 'Type anything here');
    }
    

    In the OnAfterRenderAsync method of the Index component (Pages/Index.razor):

    module = await JS.InvokeAsync<IJSObjectReference>(
        "import", "./Pages/Index.razor.js");
    

    When the app is published, the framework automatically moves the script to the web root. In the preceding example, the script is moved to bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.razor.js, where the {TARGET FRAMEWORK MONIKER} placeholder is the Target Framework Moniker (TFM). No change is required to the script's relative URL in the Index component.

  • For scripts provided by a Razor class library (RCL):

    _content/{PACKAGE ID}/{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

    • The {PACKAGE ID} placeholder is the RCL's package identifier (or library name for a class library referenced by the app).
    • The {PATH} placeholder is the path to the page, view, or component. If a Razor component is located at the root of the RCL, the path segment isn't included.
    • The {PAGE, VIEW, OR COMPONENT} placeholder is the page, view, or component.
    • The {EXTENSION} placeholder matches the extension of page, view, or component, either razor or cshtml.

    In the following Blazor app example:

    • The RCL's package identifier is AppJS.
    • A module's scripts are loaded for the Index component (Index.razor).
    • The Index component is in the Pages folder of the RCL.
    var module = await JS.InvokeAsync<IJSObjectReference>("import", 
        "./_content/AppJS/Pages/Index.razor.js");
    

For more information on RCLs, see Consume ASP.NET Core Razor components from a Razor class library (RCL).

Load a script from an external JavaScript file (.js)

Place the JavaScript (JS) tags (<script>...</script>) with a script source (src) path inside the closing </body> tag after the Blazor script reference.

In wwwroot/index.html (Blazor WebAssembly) or Pages/_Layout.cshtml (Blazor Server):

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>

The {webassembly|server} placeholder in the preceding markup is either webassembly for a Blazor WebAssembly app (blazor.webassembly.js) or server for a Blazor Server app (blazor.server.js). The {SCRIPT PATH AND FILE NAME (.js)} placeholder is the path and script file name under wwwroot.

In the following example of the preceding <script> tag, the scripts.js file is in the wwwroot/js folder of the app:

<script src="js/scripts.js"></script>

When the external JS file is supplied by a Razor class library, specify the JS file using its stable static web asset path: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}:

  • The path segment for the current directory (./) is required in order to create the correct static asset path to the JS file.
  • The {PACKAGE ID} placeholder is the library's package ID. The package ID defaults to the project's assembly name if <PackageId> isn't specified in the project file.
  • The {SCRIPT PATH AND FILENAME (.js)} placeholder is the path and file name under wwwroot.
<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}"></script>
</body>

In the following example of the preceding <script> tag:

  • The Razor class library has an assembly name of ComponentLibrary, and a <PackageId> isn't specified in the library's project file.
  • The scripts.js file is in the class library's wwwroot folder.
<script src="./_content/ComponentLibrary/scripts.js"></script>

For more information, see Consume ASP.NET Core Razor components from a Razor class library (RCL).

Inject a script after Blazor starts

Load JS from an injected script in wwwroot/index.html (Blazor WebAssembly) or Pages/_Layout.cshtml (Blazor Server) when the app is initialized:

  • Add autostart="false" to the <script> tag that loads the Blazor script.
  • Inject a script into the <head> element markup that references a custom JS file after starting Blazor by calling Blazor.start().then(...). Place the script (<script>...</script>) inside the closing </body> tag after the Blazor script is loaded.

The following example injects the wwwroot/scripts.js file after Blazor starts:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js" 
        autostart="false"></script>
    <script>
      Blazor.start().then(function () {
        var customScript = document.createElement('script');
        customScript.setAttribute('src', 'scripts.js');
        document.head.appendChild(customScript);
      });
    </script>
</body>

The {webassembly|server} placeholder in the preceding markup is either webassembly for a Blazor WebAssembly app (blazor.webassembly.js) or server for a Blazor Server app (blazor.server.js).

For more information on Blazor startup, see ASP.NET Core Blazor startup.

JavaScript isolation in JavaScript modules

Blazor enables JavaScript (JS) isolation in standard JavaScript modules (ECMAScript specification).

JS isolation provides the following benefits:

  • Imported JS no longer pollutes the global namespace.
  • Consumers of a library and components aren't required to import the related JS.

For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Cached JavaScript files

JavaScript (JS) files and other static assets aren't generally cached on clients during development in the Development environment. During development, static asset requests include the Cache-Control header with a value of no-cache or max-age with a value of zero (0).

During production in the Production environment, JS files are usually cached by clients.

To disable client-side caching in browsers, developers usually adopt one of the following approaches:

For more information, see:

A Blazor app can invoke JavaScript (JS) functions from .NET methods and .NET methods from JS functions. These scenarios are called JavaScript interoperability (JS interop).

This overview article covers general concepts. Further JS interop guidance is provided in the following articles:

Interaction with the Document Object Model (DOM)

Only mutate the Document Object Model (DOM) with JavaScript (JS) when the object doesn't interact with Blazor. Blazor maintains representations of the DOM and interacts directly with DOM objects. If an element rendered by Blazor is modified externally using JS directly or via JS Interop, the DOM may no longer match Blazor's internal representation, which can result in undefined behavior. Undefined behavior may merely interfere with the presentation of elements or their functions but may also introduce security risks to the app or server.

This guidance not only applies to your own JS interop code but also to any JS libraries that the app uses, including anything provided by a third-party framework, such as Bootstrap JS and jQuery.

In a few documentation examples, JS interop is used to mutate an element purely for demonstration purposes as part of an example. In those cases, a warning appears in the text.

For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Asynchronous JavaScript calls

JS interop calls are asynchronous by default, regardless of whether the called code is synchronous or asynchronous. Calls are asynchronous by default to ensure that components are compatible across both Blazor hosting models, Blazor Server and Blazor WebAssembly. On Blazor Server, JS interop calls must be asynchronous because they're sent over a network connection. For apps that exclusively adopt the Blazor WebAssembly hosting model, synchronous JS interop calls are supported.

For more information, see the following articles:

Object serialization

Blazor uses System.Text.Json for serialization with the following requirements and default behaviors:

  • Types must have a default constructor, get/set accessors must be public, and fields are never serialized.
  • Global default serialization isn't customizable to avoid breaking existing component libraries, impacts on performance and security, and reductions in reliability.
  • Serializing .NET member names results in lowercase JSON key names.
  • JSON is deserialized as JsonElement C# instances, which permit mixed casing. Internal casting for assignment to C# model properties works as expected in spite of any case differences between JSON key names and C# property names.

JsonConverter API is available for custom serialization. Properties can be annotated with a [JsonConverter] attribute to override default serialization for an existing data type.

For more information, see the following resources in the .NET documentation:

JSON serializer support for System.DateOnly and System.TimeOnly is planned for ASP.NET Core 7.0, which is scheduled for release by the end of 2022. For more information, see Support DateOnly and TimeOnly in JsonSerializer (dotnet/runtime #53539).

Blazor supports unmarshalled JS interop when a high volume of .NET objects are rapidly serialized or when large .NET objects or many .NET objects must be serialized. For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Location of JavaScript

Load JavaScript (JS) code using any of the following approaches:

Warning

Don't place a <script> tag in a Razor component file (.razor) because the <script> tag can't be updated dynamically by Blazor.

Note

Documentation examples usually place scripts in a <script> tag or load global scripts from external files. These approaches pollute the client with global functions. For production apps, we recommend placing JavaScript into separate JavaScript modules that can be imported when needed. For more information, see the JavaScript isolation in JavaScript modules section.

Load a script in <head> markup

The approach in this section isn't generally recommended.

Place the script (<script>...</script>) in the <head> element markup of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<head>
    ...

    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</head>

Loading JS from the <head> isn't the best approach for the following reasons:

  • JS interop may fail if the script depends on Blazor. We recommend loading scripts using one of the other approaches, not via the <head> markup.
  • The page may become interactive slower due to the time it takes to parse the JS in the script.

Load a script in <body> markup

Place the script (<script>...</script>) inside the closing </body> element markup of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</body>

The {webassembly|server} placeholder in the preceding markup is either webassembly for a Blazor WebAssembly app (blazor.webassembly.js) or server for a Blazor Server app (blazor.server.js).

Load a script from an external JS file (.js)

Place the script (<script>...</script>) with a script src path inside the closing </body> tag after the Blazor script reference.

In wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>

The {webassembly|server} placeholder in the preceding markup is either webassembly for a Blazor WebAssembly app (blazor.webassembly.js) or server for a Blazor Server app (blazor.server.js). The {SCRIPT PATH AND FILE NAME (.js)} placeholder is the path and script file name under wwwroot.

In the following example of the preceding <script> tag, the scripts.js file is in the wwwroot/js folder of the app:

<script src="js/scripts.js"></script>

When the external JS file is supplied by a Razor class library, specify the JS file using its stable static web asset path: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}:

  • The path segment for the current directory (./) is required in order to create the correct static asset path to the JS file.
  • The {PACKAGE ID} placeholder is the library's package ID. The package ID defaults to the project's assembly name if <PackageId> isn't specified in the project file.
  • The {SCRIPT PATH AND FILENAME (.js)} placeholder is the path and file name under wwwroot.
<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}"></script>
</body>

In the following example of the preceding <script> tag:

  • The Razor class library has an assembly name of ComponentLibrary, and a <PackageId> isn't specified in the library's project file.
  • The scripts.js file is in the class library's wwwroot folder.
<script src="./_content/ComponentLibrary/scripts.js"></script>

For more information, see Consume ASP.NET Core Razor components from a Razor class library (RCL).

Inject a script after Blazor starts

Load JS from an injected script in wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server) when the app is initialized:

  • Add autostart="false" to the <script> tag that loads the Blazor script.
  • Inject a script into the <head> element markup that references a custom JS file after starting Blazor by calling Blazor.start().then(...). Place the script (<script>...</script>) inside the closing </body> tag after the Blazor script is loaded.

The following example injects the wwwroot/scripts.js file after Blazor starts:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js" 
        autostart="false"></script>
    <script>
      Blazor.start().then(function () {
        var customScript = document.createElement('script');
        customScript.setAttribute('src', 'scripts.js');
        document.head.appendChild(customScript);
      });
    </script>
</body>

The {webassembly|server} placeholder in the preceding markup is either webassembly for a Blazor WebAssembly app (blazor.webassembly.js) or server for a Blazor Server app (blazor.server.js).

For more information on Blazor startup, see ASP.NET Core Blazor startup.

JavaScript isolation in JavaScript modules

Blazor enables JavaScript (JS) isolation in standard JavaScript modules (ECMAScript specification).

JS isolation provides the following benefits:

  • Imported JS no longer pollutes the global namespace.
  • Consumers of a library and components aren't required to import the related JS.

For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Cached JavaScript files

JavaScript (JS) files and other static assets aren't generally cached on clients during development in the Development environment. During development, static asset requests include the Cache-Control header with a value of no-cache or max-age with a value of zero (0).

During production in the Production environment, JS files are usually cached by clients.

To disable client-side caching in browsers, developers usually adopt one of the following approaches:

For more information, see:

A Blazor app can invoke JavaScript (JS) functions from .NET methods and .NET methods from JS functions. These scenarios are called JavaScript interoperability (JS interop).

This overview article covers general concepts. Further JS interop guidance is provided in the following articles:

Interaction with the Document Object Model (DOM)

Only mutate the Document Object Model (DOM) with JavaScript (JS) when the object doesn't interact with Blazor. Blazor maintains representations of the DOM and interacts directly with DOM objects. If an element rendered by Blazor is modified externally using JS directly or via JS Interop, the DOM may no longer match Blazor's internal representation, which can result in undefined behavior. Undefined behavior may merely interfere with the presentation of elements or their functions but may also introduce security risks to the app or server.

This guidance not only applies to your own JS interop code but also to any JS libraries that the app uses, including anything provided by a third-party framework, such as Bootstrap JS and jQuery.

In a few documentation examples, JS interop is used to mutate an element purely for demonstration purposes as part of an example. In those cases, a warning appears in the text.

For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Asynchronous JavaScript calls

JS interop calls are asynchronous by default, regardless of whether the called code is synchronous or asynchronous. Calls are asynchronous by default to ensure that components are compatible across both Blazor hosting models, Blazor Server and Blazor WebAssembly. On Blazor Server, JS interop calls must be asynchronous because they're sent over a network connection. For apps that exclusively adopt the Blazor WebAssembly hosting model, synchronous JS interop calls are supported.

For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Object serialization

Blazor uses System.Text.Json for serialization with the following requirements and default behaviors:

  • Types must have a default constructor, get/set accessors must be public, and fields are never serialized.
  • Global default serialization isn't customizable to avoid breaking existing component libraries, impacts on performance and security, and reductions in reliability.
  • Serializing .NET member names results in lowercase JSON key names.
  • JSON is deserialized as JsonElement C# instances, which permit mixed casing. Internal casting for assignment to C# model properties works as expected in spite of any case differences between JSON key names and C# property names.

JsonConverter API is available for custom serialization. Properties can be annotated with a [JsonConverter] attribute to override default serialization for an existing data type.

For more information, see the following resources in the .NET documentation:

JSON serializer support for System.DateOnly and System.TimeOnly is planned for ASP.NET Core 7.0, which is scheduled for release by the end of 2022. For more information, see Support DateOnly and TimeOnly in JsonSerializer (dotnet/runtime #53539).

Location of JavaScript

Load JavaScript (JS) code using any of the following approaches:

Warning

Don't place a <script> tag in a Razor component file (.razor) because the <script> tag can't be updated dynamically by Blazor.

Note

Documentation examples place scripts into a <script> tag or load global scripts from external files. These approaches pollute the client with global functions. Placing JavaScript into separate JavaScript modules that can be imported when needed is not supported in Blazor earlier than ASP.NET Core 5.0. If the app requires the use of JS modules for JS isolation, we recommend using ASP.NET Core 5.0 or later to build the app. For more information, use the Version dropdown list to select a 5.0 or later version of this article and see the JavaScript isolation in JavaScript modules section.

Load a script in <head> markup

The approach in this section isn't generally recommended.

Place the script (<script>...</script>) in the <head> element markup of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<head>
    ...

    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</head>

Loading JS from the <head> isn't the best approach for the following reasons:

  • JS interop may fail if the script depends on Blazor. We recommend loading scripts using one of the other approaches, not via the <head> markup.
  • The page may become interactive slower due to the time it takes to parse the JS in the script.

Load a script in <body> markup

Place the script (<script>...</script>) inside the closing </body> element markup of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</body>

The {webassembly|server} placeholder in the preceding markup is either webassembly for a Blazor WebAssembly app (blazor.webassembly.js) or server for a Blazor Server app (blazor.server.js).

Load a script from an external JS file (.js)

Place the script (<script>...</script>) with a script src path inside the closing </body> tag after the Blazor script reference.

In wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>

The {webassembly|server} placeholder in the preceding markup is either webassembly for a Blazor WebAssembly app (blazor.webassembly.js) or server for a Blazor Server app (blazor.server.js). The {SCRIPT PATH AND FILE NAME (.js)} placeholder is the path and script file name under wwwroot.

In the following example of the preceding <script> tag, the scripts.js file is in the wwwroot/js folder of the app:

<script src="js/scripts.js"></script>

When the external JS file is supplied by a Razor class library, specify the JS file using its stable static web asset path: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}:

  • The path segment for the current directory (./) is required in order to create the correct static asset path to the JS file.
  • The {PACKAGE ID} placeholder is the library's package ID. The package ID defaults to the project's assembly name if <PackageId> isn't specified in the project file.
  • The {SCRIPT PATH AND FILENAME (.js)} placeholder is the path and file name under wwwroot.
<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}"></script>
</body>

In the following example of the preceding <script> tag:

  • The Razor class library has an assembly name of ComponentLibrary, and a <PackageId> isn't specified in the library's project file.
  • The scripts.js file is in the class library's wwwroot folder.
<script src="./_content/ComponentLibrary/scripts.js"></script>

For more information, see Consume ASP.NET Core Razor components from a Razor class library (RCL).

Inject a script after Blazor starts

Load JS from an injected script in wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server) when the app is initialized:

  • Add autostart="false" to the <script> tag that loads the Blazor script.
  • Inject a script into the <head> element markup that references a custom JS file after starting Blazor by calling Blazor.start().then(...). Place the script (<script>...</script>) inside the closing </body> tag after the Blazor script is loaded.

The following example injects the wwwroot/scripts.js file after Blazor starts:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js" 
        autostart="false"></script>
    <script>
      Blazor.start().then(function () {
        var customScript = document.createElement('script');
        customScript.setAttribute('src', 'scripts.js');
        document.head.appendChild(customScript);
      });
    </script>
</body>

The {webassembly|server} placeholder in the preceding markup is either webassembly for a Blazor WebAssembly app (blazor.webassembly.js) or server for a Blazor Server app (blazor.server.js).

For more information on Blazor startup, see ASP.NET Core Blazor startup.

Cached JavaScript files

JavaScript (JS) files and other static assets aren't generally cached on clients during development in the Development environment. During development, static asset requests include the Cache-Control header with a value of no-cache or max-age with a value of zero (0).

During production in the Production environment, JS files are usually cached by clients.

To disable client-side caching in browsers, developers usually adopt one of the following approaches:

For more information, see:

A Blazor app can invoke JavaScript (JS) functions from .NET methods and .NET methods from JS functions. These scenarios are called JavaScript interoperability (JS interop).

Further JS interop guidance is provided in the following articles:

Interaction with the Document Object Model (DOM)

Only mutate the Document Object Model (DOM) with JavaScript (JS) when the object doesn't interact with Blazor. Blazor maintains representations of the DOM and interacts directly with DOM objects. If an element rendered by Blazor is modified externally using JS directly or via JS Interop, the DOM may no longer match Blazor's internal representation, which can result in undefined behavior. Undefined behavior may merely interfere with the presentation of elements or their functions but may also introduce security risks to the app or server.

This guidance not only applies to your own JS interop code but also to any JS libraries that the app uses, including anything provided by a third-party framework, such as Bootstrap JS and jQuery.

In a few documentation examples, JS interop is used to mutate an element purely for demonstration purposes as part of an example. In those cases, a warning appears in the text.

For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Asynchronous JavaScript calls

JS interop calls are asynchronous by default, regardless of whether the called code is synchronous or asynchronous. Calls are asynchronous by default to ensure that components are compatible across both Blazor hosting models, Blazor Server and Blazor WebAssembly. On Blazor Server, JS interop calls must be asynchronous because they're sent over a network connection. For apps that exclusively adopt the Blazor WebAssembly hosting model, synchronous JS interop calls are supported.

For more information, see the following articles:

Object serialization

Blazor uses System.Text.Json for serialization with the following requirements and default behaviors:

  • Types must have a default constructor, get/set accessors must be public, and fields are never serialized.
  • Global default serialization isn't customizable to avoid breaking existing component libraries, impacts on performance and security, and reductions in reliability.
  • Serializing .NET member names results in lowercase JSON key names.
  • JSON is deserialized as JsonElement C# instances, which permit mixed casing. Internal casting for assignment to C# model properties works as expected in spite of any case differences between JSON key names and C# property names.

JsonConverter API is available for custom serialization. Properties can be annotated with a [JsonConverter] attribute to override default serialization for an existing data type.

For more information, see the following resources in the .NET documentation:

JSON serializer support for System.DateOnly and System.TimeOnly is planned for ASP.NET Core 7.0, which is scheduled for release by the end of 2022. For more information, see Support DateOnly and TimeOnly in JsonSerializer (dotnet/runtime #53539).

Blazor supports optimized byte array JS interop that avoids encoding/decoding byte arrays into Base64. The app can apply custom serialization and pass the resulting bytes. For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Blazor supports unmarshalled JS interop when a high volume of .NET objects are rapidly serialized or when large .NET objects or many .NET objects must be serialized. For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

JavaScript initializers

JavaScript (JS) initializers execute logic before and after a Blazor app loads. JS initializers are useful in the following scenarios:

  • Customizing how a Blazor app loads.
  • Initializing libraries before Blazor starts up.
  • Configuring Blazor settings.

JS initializers are detected as part of the build process and imported automatically in Blazor apps. Use of JS initializers often removes the need to manually trigger script functions from the app when using Razor class libraries (RCLs).

To define a JS initializer, add a JS module to the project named {NAME}.lib.module.js, where the {NAME} placeholder is the assembly name, library name, or package identifier. Place the file in the project's web root, which is typically the wwwroot folder.

The module exports either or both of the following conventional functions:

  • beforeStart(options, extensions): Called before Blazor starts. For example, beforeStart is used to customize the loading process, logging level, and other options specific to the hosting model.
    • In Blazor WebAssembly, beforeStart receives the Blazor WebAssembly options (options in this section's example) and any extensions (extensions in this section's example) added during publishing. For example, options can specify the use of a custom boot resource loader.
    • In Blazor Server, beforeStart receives SignalR circuit start options (options in this section's example).
    • In BlazorWebViews, no options are passed.
  • afterStarted: Called after Blazor is ready to receive calls from JS. For example, afterStarted is used to initialize libraries by making JS interop calls and registering custom elements. The Blazor instance is passed to afterStarted as an argument (blazor in this section's example).

The following example demonstrates JS initializers for beforeStart and afterStarted. For the filename of the following example:

  • Use the app's assembly name in the filename if the JS initializers are consumed as a static asset in the project. For example, name the file BlazorSample.lib.module.js for a project with an assembly name of BlazorSample. Place the file in the app's wwwroot folder.
  • Use the project's library name or package identifier if the JS initializers are consumed from an RCL. For example, name the file RazorClassLibrary1.lib.module.js for an RCL with a package identifier of RazorClassLibrary1. Place the file in the library's wwwroot folder.
export function beforeStart(options, extensions) {
    console.log("beforeStart");
}

export function afterStarted(blazor) {
    console.log("afterStarted");
}

Note

MVC and Razor Pages apps don't automatically load JS initializers. However, developer code can include a script to fetch the app's manifest and trigger the load of the JS initializers.

For an examples of JS initializers, see the following resources:

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Location of JavaScript

Load JavaScript (JS) code using any of the following approaches:

Warning

Don't place a <script> tag in a Razor component file (.razor) because the <script> tag can't be updated dynamically by Blazor.

Note

Documentation examples usually place scripts in a <script> tag or load global scripts from external files. These approaches pollute the client with global functions. For production apps, we recommend placing JavaScript into separate JavaScript modules that can be imported when needed. For more information, see the JavaScript isolation in JavaScript modules section.

Load a script in <head> markup

The approach in this section isn't generally recommended.

Place the JavaScript (JS) tags (<script>...</script>) in the <head> element markup of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<head>
    ...

    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</head>

Loading JS from the <head> isn't the best approach for the following reasons:

  • JS interop may fail if the script depends on Blazor. We recommend loading scripts using one of the other approaches, not via the <head> markup.
  • The page may become interactive slower due to the time it takes to parse the JS in the script.

Load a script in <body> markup

Place the JavaScript (JS) tags (<script>...</script>) inside the closing </body> element markup of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script>
      window.jsMethod = (methodParameter) => {
        ...
      };
    </script>
</body>

The {webassembly|server} placeholder in the preceding markup is either webassembly for a Blazor WebAssembly app (blazor.webassembly.js) or server for a Blazor Server app (blazor.server.js).

Load a script from an external JavaScript file (.js) collocated with a component

Collocation of JavaScript (JS) files for pages, views, and Razor components is a convenient way to organize scripts in an app.

Collocate JS files using the following filename extension conventions:

  • Pages of Razor Pages apps and views of MVC apps: .cshtml.js. Examples:
    • Pages/Index.cshtml.js for the Index page of a Razor Pages app at Pages/Index.cshtml.
    • Views/Home/Index.cshtml.js for the Index view of an MVC app at Views/Home/Index.cshtml.
  • Razor components of Blazor apps: .razor.js. Example: Pages/Index.razor.js for the Index component at Pages/Index.razor.

Collocated JS files are publicly addressable using the path to the file in the project:

  • Pages, views, and components from a collocated scripts file in the app:

    {PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

    • The {PATH} placeholder is the path to the page, view, or component.
    • The {PAGE, VIEW, OR COMPONENT} placeholder is the page, view, or component.
    • The {EXTENSION} placeholder matches the extension of the page, view, or component, either razor or cshtml.

    Razor Pages example:

    A JS file for the Index page is placed in the Pages folder (Pages/Index.cshtml.js) next to the Index page (Pages/Index.cshtml). In the Index page, the script is referenced at the path in the Pages folder:

    @section Scripts {
      <script src="~/Pages/Index.cshtml.js"></script>
    }
    

    When the app is published, the framework automatically moves the script to the web root. In the preceding example, the script is moved to bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js, where the {TARGET FRAMEWORK MONIKER} placeholder is the Target Framework Moniker (TFM). No change is required to the script's relative URL in the Index page.

    Blazor example:

    A JS file for the Index component is placed in the Pages folder (Pages/Index.razor.js) next to the Index component (Pages/Index.razor). In the Index component, the script is referenced at the path in the Pages folder. The following example is based on an example shown in the Call JavaScript functions from .NET methods in ASP.NET Core Blazor article.

    Pages/Index.razor.js:

    export function showPrompt(message) {
      return prompt(message, 'Type anything here');
    }
    

    In the OnAfterRenderAsync method of the Index component (Pages/Index.razor):

    module = await JS.InvokeAsync<IJSObjectReference>(
        "import", "./Pages/Index.razor.js");
    

    When the app is published, the framework automatically moves the script to the web root. In the preceding example, the script is moved to bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.razor.js, where the {TARGET FRAMEWORK MONIKER} placeholder is the Target Framework Moniker (TFM). No change is required to the script's relative URL in the Index component.

  • For scripts provided by a Razor class library (RCL):

    _content/{PACKAGE ID}/{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

    • The {PACKAGE ID} placeholder is the RCL's package identifier (or library name for a class library referenced by the app).
    • The {PATH} placeholder is the path to the page, view, or component. If a Razor component is located at the root of the RCL, the path segment isn't included.
    • The {PAGE, VIEW, OR COMPONENT} placeholder is the page, view, or component.
    • The {EXTENSION} placeholder matches the extension of page, view, or component, either razor or cshtml.

    In the following Blazor app example:

    • The RCL's package identifier is AppJS.
    • A module's scripts are loaded for the Index component (Index.razor).
    • The Index component is in the Pages folder of the RCL.
    var module = await JS.InvokeAsync<IJSObjectReference>("import", 
        "./_content/AppJS/Pages/Index.razor.js");
    

For more information on RCLs, see Consume ASP.NET Core Razor components from a Razor class library (RCL).

Load a script from an external JavaScript file (.js)

Place the JavaScript (JS) tags (<script>...</script>) with a script source (src) path inside the closing </body> tag after the Blazor script reference.

In wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server):

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="{SCRIPT PATH AND FILE NAME (.js)}"></script>
</body>

The {webassembly|server} placeholder in the preceding markup is either webassembly for a Blazor WebAssembly app (blazor.webassembly.js) or server for a Blazor Server app (blazor.server.js). The {SCRIPT PATH AND FILE NAME (.js)} placeholder is the path and script file name under wwwroot.

In the following example of the preceding <script> tag, the scripts.js file is in the wwwroot/js folder of the app:

<script src="js/scripts.js"></script>

When the external JS file is supplied by a Razor class library, specify the JS file using its stable static web asset path: ./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}:

  • The path segment for the current directory (./) is required in order to create the correct static asset path to the JS file.
  • The {PACKAGE ID} placeholder is the library's package ID. The package ID defaults to the project's assembly name if <PackageId> isn't specified in the project file.
  • The {SCRIPT PATH AND FILENAME (.js)} placeholder is the path and file name under wwwroot.
<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js"></script>
    <script src="./_content/{PACKAGE ID}/{SCRIPT PATH AND FILENAME (.js)}"></script>
</body>

In the following example of the preceding <script> tag:

  • The Razor class library has an assembly name of ComponentLibrary, and a <PackageId> isn't specified in the library's project file.
  • The scripts.js file is in the class library's wwwroot folder.
<script src="./_content/ComponentLibrary/scripts.js"></script>

For more information, see Consume ASP.NET Core Razor components from a Razor class library (RCL).

Inject a script after Blazor starts

Load JS from an injected script in wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server) when the app is initialized:

  • Add autostart="false" to the <script> tag that loads the Blazor script.
  • Inject a script into the <head> element markup that references a custom JS file after starting Blazor by calling Blazor.start().then(...). Place the script (<script>...</script>) inside the closing </body> tag after the Blazor script is loaded.

The following example injects the wwwroot/scripts.js file after Blazor starts:

<body>
    ...

    <script src="_framework/blazor.{webassembly|server}.js" 
        autostart="false"></script>
    <script>
      Blazor.start().then(function () {
        var customScript = document.createElement('script');
        customScript.setAttribute('src', 'scripts.js');
        document.head.appendChild(customScript);
      });
    </script>
</body>

The {webassembly|server} placeholder in the preceding markup is either webassembly for a Blazor WebAssembly app (blazor.webassembly.js) or server for a Blazor Server app (blazor.server.js).

For more information on Blazor startup, see ASP.NET Core Blazor startup.

JavaScript isolation in JavaScript modules

Blazor enables JavaScript (JS) isolation in standard JavaScript modules (ECMAScript specification).

JS isolation provides the following benefits:

  • Imported JS no longer pollutes the global namespace.
  • Consumers of a library and components aren't required to import the related JS.

For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.

Cached JavaScript files

JavaScript (JS) files and other static assets aren't generally cached on clients during development in the Development environment. During development, static asset requests include the Cache-Control header with a value of no-cache or max-age with a value of zero (0).

During production in the Production environment, JS files are usually cached by clients.

To disable client-side caching in browsers, developers usually adopt one of the following approaches:

For more information, see: