Blazor JavaScript Interop
This document explains how to use JavaScript interop in Blazor applications. Blazor allows you to invoke JavaScript functions from your .NET code and call .NET methods from JavaScript. This is crucial for leveraging existing JavaScript libraries, interacting with browser APIs not directly exposed to Blazor, or when migrating existing JavaScript-heavy applications.
Calling JavaScript from .NET
To call JavaScript from your Blazor components, you use the IJSRuntime
service. This service is automatically available in Blazor components.
Using IJSRuntime.InvokeAsync
The primary method for invoking JavaScript is InvokeAsync<T>(string identifier, params object?[]? args)
. The identifier
is the name of the JavaScript function to call.
Example: Calling a simple JavaScript function
First, define a JavaScript function in your wwwroot/index.html
or a separate .js
file linked to your application.
function greet(name) {
alert(`Hello, ${name}!`);
return `Greeting sent to ${name}`;
}
function sum(a, b) {
return a + b;
}
Then, in your Blazor component (e.g., a .razor
file):
@page "/js-interop-example"
@inject IJSRuntime JSRuntime
JavaScript Interop Example
Sum Result: @sumResult
@code {
private int sumResult = 0;
private async Task CallGreet()
{
await JSRuntime.InvokeVoidAsync("greet", "Blazor User");
}
private async Task CallSum()
{
int result = await JSRuntime.InvokeAsync<int>("sum", 5, 10);
sumResult = result;
StateHasChanged();
}
}
In the example above:
InvokeVoidAsync
is used when the JavaScript function doesn't return a value (likealert
).InvokeAsync<T>
is used when the JavaScript function returns a value. The type parameterT
specifies the expected return type.
Stream JavaScript from .NET
You can also stream data from .NET to JavaScript, which is useful for large data sets.
@inject IJSRuntime JSRuntime
@inject HttpClient Http
<!-- ... -->
@code {
private async Task DownloadFile()
{
var stream = await Http.GetStreamAsync("sample-data.pdf");
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
ms.Seek(0, SeekOrigin.Begin); // Rewind the stream
// Assuming a JavaScript function 'downloadFileFromStream' exists
await JSRuntime.InvokeAsync("downloadFileFromStream", DotNetStreamReference.Create(ms), "MyDocument.pdf");
}
}
async function downloadFileFromStream(dotNetStreamReference, fileName) {
const arrayBuffer = await dotNetStreamReference.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = fileName ?? 'download';
document.body.appendChild(anchorElement);
anchorElement.click();
document.body.removeChild(anchorElement);
URL.revokeObjectURL(url);
}
Calling .NET from JavaScript
To call .NET methods from JavaScript, you use the DotNet.invokeMethodAsync
and DotNet.invokeMethod
functions provided by Blazor's JavaScript interop infrastructure.
Using DotNet.invokeMethodAsync
This is the recommended way to call .NET methods from JavaScript, as it's asynchronous and handles potential exceptions more gracefully.
First, you need to get a reference to the .NET object you want to invoke. This is typically done by passing a DotNetObjectReference
to JavaScript.
@page "/js-to-dotnet-example"
@inject IJSRuntime JSRuntime
@implements IDisposable
<h3>JavaScript to .NET Example</h3>
<button @onclick="TriggerJsCall">Trigger JavaScript Call</button>
<p>Message from .NET: @messageFromDotNet</p>
@code {
private string messageFromDotNet = "Waiting for JavaScript...";
private DotNetObjectReference<JsToDotNetExample> objRef;
protected override void OnInitialized()
{
objRef = DotNetObjectReference.Create(this);
// Pass the object reference to JavaScript
// Assume a JS function 'registerDotNetReference' exists
JSRuntime.InvokeVoidAsync("registerDotNetReference", objRef);
}
// This method will be called from JavaScript
[JSInvokable("ReceiveMessage")]
public void ReceiveMessage(string message)
{
messageFromDotNet = $"Received: {message} at {DateTime.Now}";
StateHasChanged();
}
private async Task TriggerJsCall()
{
// Assume a JS function 'requestMessageFromJs' exists
await JSRuntime.InvokeVoidAsync("requestMessageFromJs");
}
public void Dispose()
{
objRef?.Dispose();
}
}
In your JavaScript:
let dotNetObjectRef;
function registerDotNetReference(dotNetRef) {
dotNetObjectRef = dotNetRef;
console.log("DotNet reference registered.");
}
function requestMessageFromJs() {
if (dotNetObjectRef) {
const message = "Hello from JavaScript!";
console.log(`Invoking .NET method 'ReceiveMessage' with: ${message}`);
dotNetObjectRef.invokeMethodAsync("ReceiveMessage", message);
} else {
console.error("DotNet reference not available.");
}
}
// Example of a JS function that could trigger the .NET call
function triggerDotNetCall() {
if (dotNetObjectRef) {
dotNetObjectRef.invokeMethodAsync("ReceiveMessage", "This is a direct call from JS.");
}
}
DotNet.invokeMethod
This synchronous version should be used with caution as it can block the browser's UI thread. It's generally preferred to use the asynchronous version.
Handling Exceptions
JavaScript interop calls can throw exceptions. It's good practice to wrap your interop calls in try-catch
blocks in both C# and JavaScript to handle potential errors gracefully.
Best Practices
- Minimize Interop Calls: Frequent, small interop calls can impact performance. Batch calls or use streaming where appropriate.
- Use Asynchronous Calls: Prefer
InvokeAsync
andDotNet.invokeMethodAsync
to avoid blocking the UI. - Pass Only Necessary Data: Avoid passing large, complex objects if only a few properties are needed.
- Manage Object References: Ensure you dispose of
DotNetObjectReference
instances when they are no longer needed to prevent memory leaks. - Consider Blazor Server vs. WebAssembly: The performance characteristics and underlying mechanisms of JavaScript interop can differ slightly between Blazor Server and Blazor WebAssembly.