Communicating between .NET and JavaScript in Blazor with in-browser samples
Niels Swimberghe - - .NET
Follow me on Twitter, buy me a coffee
The success of Blazor relies heavily upon how well it can integrate with the existing rich JavaScript ecosystem. The way Blazor allows you to integrate, is by enabling you to call JavaScript functions from Blazor and .NET functions from JavaScript. This is also referred to as 'JavaScript interoperability'.
The JavaScript interoperability API's provided by Microsoft live under Microsoft.JSInterop namespace.
Call JavaScript from Blazor #
To invoke JavaScript functions from Blazor, you need an instance of the IJSRuntime
interface.
You can obtain an instance using ASP.NET's built-in dependency injection. If you want to have the IJSRuntime
injected into a '.razor' component, use the @inject
directive at the top of your file like this:
@inject IJSRuntime js
You can now use the js
variable throughout the component.
Alternatively, you can use constructor injection like this:
public class Demo { private readonly IJSRuntime js; public Demo(IJSRuntime js) { this.js = js; } }
Or you can mark properties using the Inject
attribute:
public class Demo { [Inject] public IJSRuntime JS { get; set; } }
Once you have an instance of IJSRuntime
, you can call JavaScript functions using its methods:
- InvokeAsync
- This method will invoke the specified JavaScript function. It requires 1 parameter; A string to specify which JavaScript function to run. After the first parameter, you can pass as many extra parameters as you need to. Those subsequent parameters will be passed along to the JavaScript function.
You also must specify the return type you are expecting to receive from the JavaScript function using the generic type parameter. Blazor will try to convert the JavaScript return value to the requested .NET type. - InvokeVoidAsync
- As the name gives away, this method functions like
InvokeAsync
, but it doesn't return the JavaScript return value.
Note: Even though the JavaScript return value isn't returned to you, it is still converted to a .NET type and then discarded. This behavior will change in future versions so that the JavaScript return value isn't unnecessarily converted.
Here's a basic example of how to use the methods:
@inject IJSRuntime JS @code { protected override async Task OnInitializedAsync() { string name = await JS.InvokeAsync<string>("prompt", "What is your name?"); await JS.InvokeVoidAsync("alert", $"Hello {name}!"); } }
The sample above will call the window.prompt
function which shows a dialog with an input box. Once you fill out your name and click "OK", the text entered into the prompt is returned to .NET.
The entered text is then passed along to the window.alert
function which show a simple dialog without returning any values.
Being able to call JavaScript function from .NET is essential, but this implementation has caveats:
- You can only call functions that are available in the global scope. With other words, only functions defined on the
window
object or an objected nested within thewindow
object. - It's common for a function to return an object which you use to call a subsequent function, but in .NET 3.1 Blazor, that is not easily done.
.NET 5 will introduceIJSObjectReference
which refers to an object in JavaScript. Once you have aIJSObjectReference
instance, you can call the sameInvokeAsync
method on it. - Since you can only call functions, you can also not use the
new
operator to create instances of classes/prototypes. As a workaround, you can create a dedicated JavaScript function to create those objects using thenew
operator. - You may want to pass complete JavaScript statements such as "document.getElementById('nameInput').value", but that is not possible and will cause errors. If Blazor were to support this, it would be exposed to JavaScript injection attacks. If you really do want this type of behavior, you could use the
eval
function in JavaScript like this sample, though it is very risky and not recommended.
For more details on .NET to JavaScript interop, check out Microsoft's "Call JavaScript functions from .NET methods in ASP.NET Core Blazor".
.NET to JavaScript Samples #
Call JavaScript functions with simple parameters from .NET #
The InvokeVoidAsync
on IJSRuntime
allows you to call JavaScript functions without expecting any return value.
These functions have to be available on the global window
variable.
You can use Blazor's dependency injection using the @inject
to get an instance of IJSRuntime
.
The first parameter of InvokeVoidAsync
is the JavaScript function you want to invoke. You can pass as many subsequent parameters and they will be passed to the JavaScript function.ElementReference
's passed to InvokeVoidAsync
will automatically be converted to the HTMLElement
.
Live Sample:
Blazor Source:
@implements IDisposable @inject IJSRuntime js <button type="button" class="btn btn-primary" onclick="jsToDotNetSamples.printPersonFromDotNet()">Print person from .NET class instance</button> <br><br> <code id="personCodeBlock"></code> @code{ private JsToDotNetBridge jsToDotNetBridge = new JsToDotNetBridge(); private DotNetObjectReference<JsToDotNetBridge> jsToDotNetBridgeReference; protected override Task OnInitializedAsync() { jsToDotNetBridgeReference = DotNetObjectReference.Create(jsToDotNetBridge); js.InvokeVoidAsync("jsToDotNetSamples.setDotNetReference", jsToDotNetBridgeReference); return base.OnInitializedAsync(); } public void Dispose() { jsToDotNetBridgeReference?.Dispose(); } // class embedded for demo purposes, move this class out of your razor component public class JsToDotNetBridge { [JSInvokable] public object GetPerson() { var obj = new { FistName = "Jon", LastName = "Doe", Age = 27, BirthDate = DateTime.Now.AddYears(-27), LikesDotNet = true }; return obj; } } }
JavaScript Source:
window.dotNetToJsSamples = {
setText: function(node, text)
{
node.textContent = text;
}
};
Call JavaScript functions with a .NET object as parameter from .NET #
In addition to simple .NET types, you can also pass .NET objects when invoking JavaScript functions using InvokeVoidAsync
.
Live Sample:
Blazor Source:
@inject IJSRuntime js <button type="button" class="btn btn-primary" @onclick="PrintDotNetObject">Pass .NET object to JavaScript</button> <br><br> <code @ref="codeBlock"></code> @code{ private ElementReference codeBlock; private async Task PrintDotNetObject() { var obj = new { FistName = "Jon", LastName = "Doe", Age = 27, BirthDate = DateTime.Now.AddYears(-27), LikesDotNet = true, }; await js.InvokeVoidAsync("dotNetToJsSamples.printDotNetObject", codeBlock, obj); } }
JavaScript Source:
window.dotNetToJsSamples = { printDotNetObject: function(node, objectFromDotNet) { node.textContent = JSON.stringify(objectFromDotNet); } };
Call 'eval' JavaScript functions from .NET #
eval
evaluates the string parameter as JavaScript and then executes the JavaScript. This is very powerful, but also very dangerous! If possible, avoid eval
at all cost. Using eval
could expose your applications to script injection. Read more about eval
at MDN's documenation, including why you should not use it.
Since eval
is a function available on the window
object, you can also call it from .NET. Use at your own risk!
Live Sample:
Blazor Source:
@inject IJSRuntime js <button type="button" class="btn btn-primary" @onclick="ShowEvalWarningAsync">Run JavaScript from .NET using EVAL</button> <br><br> <div class="eval-warning alert alert-danger font-weight-bold" role="alert">[WARNING MESSAGE GOES HERE]</div> @code{ private async Task ShowEvalWarningAsync() { var warning = "Using <code>eval</code> is a huge security risk." + "<a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#Never_use_eval!\">" + " Learn more about the risks of using <code>eval</code> at MDN." + "</a>"; // add null; add the end to prevent .NET from trying to serialize return value to object var scriptToEval = string.Format("$('.eval-warning').html('{0}');null;", warning); await js.InvokeVoidAsync("eval", scriptToEval); } }
Call JavaScript functions with return value from .NET #
In addition to the method InvokeVoidAsync
on IJSRuntime
, there's another method InvokeAsync
.InvokeAsync
let's you invoke a JavaScript function and receive its return value in .NET.
Example: InvokeAsync<string>("computeStringInJavaScript")
.
These JavaScript functions can also return objects which will be converted into .NET objects.
Live Sample:
Blazor Source:
@inject IJSRuntime js <textarea class="form-control" placeholder="Enter message" @ref="textAreaReference"></textarea> <br> <button type="button" class="btn btn-primary" @onclick="PrintMessageAsync"> Get return value from JavaScript function invoked from .NET </button> <br> <br> <div class="alert alert-primary" role="alert"> Message: @message </div> @code{ private ElementReference textAreaReference; private string message; private async Task PrintMessageAsync() { message = await js.InvokeAsync<string>("dotNetToJsSamples.getValue", textAreaReference); } }
JavaScript Source:
window.dotNetToJsSamples = { getValue: function(node) { return node.value; } };
Call JavaScript functions returning a Promise from .NET #
In addition to returning simple values and objects from JavaScript functions, these functions can also return a Promise
.
A Promise
in JavaScript is the equivalent of a Task
in .NET, though they function slightly different.
When calling a JavaScript function from .NET which returns a promise, you can simply await
the Task
returned by InvokeAsync
.
So from the perspective of your .NET code, there's no difference between a function returning something immediately or using a Promise
.
Live Sample:
Blazor Source:
@using System.Text.Json @inject IJSRuntime js <button type="button" class="btn btn-primary" @onclick="GetWeatherDataUsingJavaScript"> Invoke JavaScript function with Promise return value from .NET </button> <br><br> <code> @serializedObject </code> @code{ private string serializedObject; private async Task GetWeatherDataUsingJavaScript() { var weatherDataObject = await js.InvokeAsync<Object>("dotNetToJsSamples.getWeatherData"); serializedObject = JsonSerializer.Serialize(weatherDataObject); } }
JavaScript Source:
window.dotNetToJsSamples = { getWeatherData: function(){ return fetch('/sample-data/weather.json') .then(response => response.json()); } };
Call .NET methods from JavaScript #
You can also call .NET methods from JavaScript in Blazor. You can call both static .NET methods and instance .NET methods.
Before you can call .NET methods from JavaScript, you need to mark the methods with the JSInvokable
attribute.
[JSInvokable] public static void ConsoleWriteLine(string message) { Console.WriteLine(message); } [JSInvokable("otherConsoleWriteLine")] public static void NamedConsoleWriteLine(string message) { Console.WriteLine(message); } [JSInvokable] public void InstanceConsoleWriteLine(string message) { Console.WriteLine(message); }
You can pass in a string to the attribute to explicitly configure the identifier
of the .NET method. If you don't explicitly configure the identifier
, you can simply use the name of the method.
Warning: Within a single assembly, you cannot have multiple static methods marked with the JSInvokable
attribute that have the same identifier (implicitly by method name or explicitly by passing the identifier
parameter).
If you do have colliding identifiers for static methods, errors will be thrown when attempting to invoke those methods from JavaScript.
Call static .NET methods from JavaScript #
Blazor's JavaScript adds a new object named DotNet
as a global variable which has two functions to invoke static .NET methods:
- invokeMethod
- This method invokes the specified static .NET method and returns the .NET value converted as a JavaScript value. The first parameter is the name of the assembly where the static .NET method resides. The second parameter is the identifier/method name of the static .NET method. Any subsequent parameters will be converted to .NET values and passed to the .NET method.
- invokeMethodAsync
- This method invokes static .NET methods and returns a JavaScript
Promise
. When thePromise
is fulfilled, thePromise
result will be the return value from the .NET method.
The first parameter is the name of the assembly where the static .NET method resides. The second parameter is the identifier/method name of the static .NET method. Any subsequent parameters will be converted to .NET values and passed to the .NET method.
Here's a basic example of how to use the methods:
DotNet.invokeMethod('BlazorJavaScriptInterop', 'ConsoleWriteLine', 'Hello from JavaScript') DotNet.invokeMethod('BlazorJavaScriptInterop', 'otherConsoleWriteLine', 'Hello from JavaScript 2')
The sample above will call the static ConsoleWriteLine
and NamedConsoleWriteLine
.NET method shown prior. The result will be the following console output:
Hello from JavaScript Hello from JavaScript 2
Call .NET instance methods from JavaScript #
You can also call .NET instance methods, but you need a reference to the object before you can invoke its functions. You can create a DotNetObjectReference
instance by calling DotNetObjectReference.Create
. You need to pass in the object you want a reference to as a parameter. When a DotNetObjectReference
is returned to JavaScript from a .NET method or passed to a JavaScript function, it is converted to a JavaScript object which you can use to call the methods of the .NET object.
The JavaScript object converted from a DotNetObjectReference
instance has the same two functions as `DotNet` to call .NET methods:
- invokeMethod
- This method behaves the same as
DotNet.invokeMethod
, but instead of calling a static .NET method, it will invoke the .NET method of the referenced .NET object. - invokeMethodAsync
- This method behaves the same as
DotNet.invokeMethodAsync
, but instead of calling a static .NET method, it will invoke the .NET method of the referenced .NET object.
For more details on JavaScript to .NET interop, check out Microsoft's "Call .NET methods from JavaScript functions in ASP.NET Core Blazor".
JavaScript to .NET Samples #
Call static .NET method from JavaScript #
Blazor will add a global variable DotNet
to the JavaScript window
object.
Using DotNet.invokeMethod
and DotNet.invokeMethodAsync
you can invoke public static .NET methods that have been marked with the JSInvokable
attribute.
The JSInvokable
attribute accepts an argument to specify which name you can use to invoke the method from JavaScript.
To see the result of this sample, open the browser DevTools and observe the console output.
Live Sample:
To see the result of this sample, open the browser DevTools and observe the console output.
Blazor Source:
<button type="button" class="btn btn-primary" onclick="DotNet.invokeMethod('BlazorJavaScriptInterop', 'ConsoleWriteLine', 'Hello from JavaScript')"> Call static .NET ConsoleWriteLine function from JavaScript </button> <br> <br> <button type="button" class="btn btn-primary" onclick="DotNet.invokeMethod('BlazorJavaScriptInterop', 'otherConsoleWriteLine', 'Hello from JavaScript 2')"> Call static .NET NamedConsoleWriteLine function named 'otherConsoleWriteLine' from JavaScript </button> @code{ [JSInvokable] public static void ConsoleWriteLine(string message) { Console.WriteLine(message); } [JSInvokable("otherConsoleWriteLine")] public static void NamedConsoleWriteLine(string message) { Console.WriteLine(message); } }
Call .NET class instance methods from JavaScript #
The sample below does the following:
- Create an instance of
JsToDotNetBridge
. This class has a methodGetPerson
marked with aJSInvokable
attribute. The method returns an anonymous object representing a person. - Create a
DotNetObjectReference
for theJsToDotNetBridge
instance usingDotNetObjectReference.Create(jsToDotNetBridge);
. - Invoke the JavaScript function
jsToDotNetSamples.setDotNetReference
which accepts the object reference and stores it. - When the button below is clicked,
jsToDotNetSamples.printPersonFromDotNet
will be called which invokes theJsToDotNetBridge.GetPerson
instance method. - The method returns an object which is converted into JSON to print below the button.
Note: You have to make sure to dispose of DotNetObjectReference
. In the sample, the Blazor component implements the IDisposable
interface and disposes of the object reference in the Dispose
method.
Live Sample:
Blazor Source:
@implements IDisposable @inject IJSRuntime js <button type="button" class="btn btn-primary" onclick="jsToDotNetSamples.printPersonFromDotNet()">Print person from .NET class instance</button> <br><br> <code id="personCodeBlock"></code> @code{ private JsToDotNetBridge jsToDotNetBridge = new JsToDotNetBridge(); private DotNetObjectReference<JsToDotNetBridge> jsToDotNetBridgeReference; protected override Task OnInitializedAsync() { jsToDotNetBridgeReference = DotNetObjectReference.Create(jsToDotNetBridge); js.InvokeVoidAsync("jsToDotNetSamples.setDotNetReference", jsToDotNetBridgeReference); return base.OnInitializedAsync(); } public void Dispose() { jsToDotNetBridgeReference?.Dispose(); } // class embedded for demo purposes, move this class out of your razor component public class JsToDotNetBridge { [JSInvokable] public object GetPerson() { var obj = new { FistName = "Jon", LastName = "Doe", Age = 27, BirthDate = DateTime.Now.AddYears(-27), LikesDotNet = true }; return obj; } } }
JavaScript Source:
window.jsToDotNetSamples = { dotNetReference: null, setDotNetReference: function(dotNetReference) { this.dotNetReference = dotNetReference; }, printPersonFromDotNet: function(){ var person = this.dotNetReference.invokeMethod("GetPerson"); document.getElementById('personCodeBlock').textContent = JSON.stringify(person); } };
Call .NET Blazor component instance methods from JavaScript #
Live Sample:
The previous sample created a dedicated class with instance methods to be called from JavaScript.
You can simplify this by creating a DotNetObjectReference
of the Blazor component class.
Simply define instance methods on your Blazor component and mark them with the JSInvokable
attribute.
Instead of creating a new object and passing it to DotNetObjectReference.Create
, simply pass this
to create a reference to the Blazor component instance.
After creating the reference, pass it as a parameter when invoking JavaScript and have the JavaScript function store the object reference.
Blazor Source:
@implements IDisposable @inject IJSRuntime js <button type="button" class="btn btn-primary" onclick="jsToDotNetSamples.printPersonFromDotNet()">Print person from .NET Blazor component instance method</button> <br><br> <code id="personCodeBlock"></code> @code{ private DotNetObjectReference<JsToDotNetSample3> dotNetObjectReference; protected override Task OnInitializedAsync() { dotNetObjectReference = DotNetObjectReference.Create(this); js.InvokeVoidAsync("jsToDotNetSamples.setDotNetReference", dotNetObjectReference); return base.OnInitializedAsync(); } [JSInvokable] public object GetPerson() { var obj = new { FistName = "Jon", LastName = "Doe", Age = 27, BirthDate = DateTime.Now.AddYears(-27), LikesDotNet = true }; return obj; } public void Dispose() { dotNetObjectReference.Dispose(); } }
JavaScript Source:
window.jsToDotNetSamples = { dotNetReference: null, setDotNetReference: function(dotNetReference) { this.dotNetReference = dotNetReference; }, printPersonFromDotNet: function(){ var person = this.dotNetReference.invokeMethod("GetPerson"); document.getElementById('personCodeBlock').textContent = JSON.stringify(person); } };
Call Async Task .NET methods from JavaScript #
Live Sample:
In addition to calling .NET methods with simple return values and objects, you can also call .NET methods which return a Task
using invokeMethodAsync
.
When invoking a .NET method returning a Task
, the return value in JavaScript will be a Promise
.
The sample below does the following:
- A
DotNetObjectReference
referring to the Blazor component instance is created and passed to JavaScript. - JavaScript receives the object reference and stores it for future use.
- When the button below is clicked, JavaScript will call the
GetWeatherAsync
instance method on the object reference. GetWeatherAsync
will use the .NETHttpClient
to make an HTTP request to sample-data/weather.json.- The JSON result is deserialized to a .NET object which is then returned.
- In JavaScript, a
Promise
was returned byinvokeMethodAsync
. - When the .NET function is done requesting the weather data, the object is passed to
then
callback. - Inside of the callback, the object is deserialized and printed below the button.
Blazor Source:
@implements IDisposable @inject IJSRuntime js @inject NavigationManager navigationManager <button type="button" class="btn btn-primary" onclick="jsToDotNetSamples.getWeatherDataFromDotNet()">Get WeatherData from .NET async task method</button> <br><br> <code id="weatherCodeBlock"></code> @code{ private static HttpClient httpClient = new HttpClient(); private DotNetObjectReference<JsToDotNetSample4> dotNetObjectReference; protected override Task OnInitializedAsync() { dotNetObjectReference = DotNetObjectReference.Create(this); js.InvokeVoidAsync("jsToDotNetSamples.setDotNetReference", dotNetObjectReference); return base.OnInitializedAsync(); } [JSInvokable] public async Task<object> GetWeatherAsync() { var rootUrl = navigationManager.BaseUri; return await httpClient.GetFromJsonAsync<object>($"{rootUrl}/sample-data/weather.json"); } public void Dispose() { dotNetObjectReference?.Dispose(); } }
JavaScript Source:
window.jsToDotNetSamples = { dotNetReference: null, setDotNetReference: function(dotNetReference) { this.dotNetReference = dotNetReference; }, getWeatherDataFromDotNet: function(node){ this.dotNetReference.invokeMethodAsync('GetWeatherAsync') .then(weatherData => { document.getElementById('weatherCodeBlock').textContent = JSON.stringify(weatherData); }); } };
Support multiple Blazor component instances with JavaScript state #
For some Blazor components, you can be certain that there will only ever be one instance of that component at a single time. In that case, you can simply store the JavaScript state in a single object and not worry about any collision. But for many Blazor components, the component needs to support multiple instances at the same time and their state in JavaScript should not overwrite each other.
To learn how to support multiple instances of components, this sample will use Bootstrap's Carousel component which requires some initial setup to reference some CSS and JavaScript files.
Carousel Component Sample #
The main trick is to store each component's JavaScript state in an object with a unique component ID as the key, and the component's state as the value.
Subsequently, for any JavaScript function you want to call, you have to pass this unique component ID so that the JavaScript function can retrieve the correct state and execute its functionality.
The sample below illustrates how you can have multiple Carousel
components on a single page, each with their own state in JavaScript.
Here's how it works:
- First, you need to create a safe unique ID for the component instace; You can do this using this line:
$"id_{Guid.NewGuid().ToString().Replace("-", "")}";
- Create a JavaScript function that you can call from Blazor to initialize your component and its JavaScript state.
blazorCarousel.init
takes in the component ID, aDotNetObjectReference
, and anElementReference
.- The component ID will be used as the key to store state on the
blazorCarousel.instances
object. - The
DotNetObjectReference
will be used to call back into the .NET Blazor component when events occur. - The
ElementReference
will be used to call Bootstrap's Carousel function.
blazorCarousel.init
does the following:- Initialize the Bootstrap carousel by calling the jQuery Bootstrap
carousel()
function on the jQuery wrappedcarouselNode
. - Hook up a JavaScript callback function to the Carousel 'slide' event. The callback calls into the
OnSlide
.NET method passing in the event data. - Hook up a JavaScript callback function to the Carousel 'slid' event. The callback calls into the
OnSlid
.NET method passing in the event data. - Store state for the Carousel component on the
blazorCarousel.instances
object, using the component ID as key and the state as value.
The state is holding on to theDotNetReference
and jQuery wrappedcarouselNode
.
- The component ID will be used as the key to store state on the
- In the
OnAfterRenderAsync
, specifically onfirstRender
, the JavaScriptblazorCarousel.init
function is called. - When the
Carousel
Blazor component is removed from the application, theDispose
method will be called. Inside ofDispose
, the JavaScriptblazorCarousel.dispose
function is called doing the following:- The jQuery Bootstrap dispose is called like this
.carousel('dispose')
. This is important to not cause memory leaks. - The instance state is removed from the
blazorCarousel.instances
object using thedelete
keyword.
Note:
IAsyncDisposable
would be a better interface to implement since invoking the JavaScript will result in aTask
. Unfortunately, IAsyncDisposable is not supported in .NET 3.1 Blazor, but it will be in future versions. - The jQuery Bootstrap dispose is called like this
Live Sample:
Blazor Source:
@implements IDisposable @using Microsoft.Extensions.Logging @inject IJSRuntime js @inject ILogger<Carousel> logger <div class="carousel slide" data-ride="carousel" id="@carouselId" @ref="carouselReference"> <div class="carousel-inner"> @for (int i = 1; i <= 10; i++) { <div class="carousel-item @(i == 1 ? "active": "")"> <svg class="bd-placeholder-img bd-placeholder-img-lg d-block w-100" width="800" height="400"> <!-- SVG image content goes here --> </svg> </div> } </div> <a class="carousel-control-prev" data-target="#@carouselId" role="button" data-slide="prev"> <span class="carousel-control-prev-icon" aria-hidden="true"></span> <span class="sr-only">Previous</span> </a> <a class="carousel-control-next" data-target="#@carouselId" role="button" data-slide="next"> <span class="carousel-control-next-icon" aria-hidden="true"></span> <span class="sr-only">Next</span> </a> </div> @code{ // prefix id with 'id_' to make sure id's don't start with a number or other invalid character // add Guid to ensure unique-ness but remove '-' as they are invalid characters for JS property names private string carouselId = $"id_{Guid.NewGuid().ToString().Replace("-", "")}"; private ElementReference carouselReference; private DotNetObjectReference<Carousel> dotNetObjectReference; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { dotNetObjectReference = DotNetObjectReference.Create(this); await js.InvokeVoidAsync("blazorCarousel.init", carouselId, dotNetObjectReference, carouselReference); } } [JSInvokable] public void OnSlide(string direction, int from, int to) { logger.LogInformation($"Carousel is sliding from {from} to {to}."); } [JSInvokable] public void OnSlid(string direction, int from, int to) { logger.LogInformation($"Carousel slid from {from} to {to}."); } public void Dispose() { // IAsyncDisposable not supported yet in .NET Core 3.1 Task.Run(() => js.InvokeVoidAsync("blazorCarousel.dispose", carouselId)); dotNetObjectReference.Dispose(); } }
JavaScript Source:
var blazorCarousel = { instances: {}, init: function (carouselId, dotNetReference, carouselNode) { var carousel = { dotNetReference: dotNetReference, $carouselNode: $(carouselNode) }; carousel.$carouselNode.carousel(); carousel.$carouselNode.on('slide.bs.carousel', function (event) { dotNetReference.invokeMethod("OnSlide", event.direction, event.from, event.to); }); carousel.$carouselNode.on('slid.bs.carousel', function (event) { dotNetReference.invokeMethod("OnSlid", event.direction, event.from, event.to); }); this.instances[carouselId] = carousel; }, dispose: function (carouselId) { this.instances[carouselId].$carouselNode.carousel('dispose'); delete this.instances[carouselId]; } };
Summary #
With Blazor, .NET Core introduced new API's to support JavaScript interop. Using IJSRuntime
you can call JavaScript functions from .NET. With the global JavaScript variable DotNet
, you can invoke static .NET methods marked with the JSInvokable
attribute. By passing a DotNetObjectReference
to JavaScript, JavaScript can call the .NET methods on the referenced .NET object.
The interop capabilities have some caveats:
- You can only invoke global JavaScript functions and you cannot chain subsequent function calls in .NET Core 3.1. .NET 5 introduces the concept of
IJSObjectReference
which resolves this limitation. - You cannot create objects from classes/prototypes using the
new
keyword. Instead, you must create a dedicated global function to create those objects for you. - You can have a reference to a .NET object in JavaScript using
DotNetObjectReference
, but not a reference to JavaScript object in .NET. .NET 5 introduces the concept ofIJSObjectReference
which resolves this limitation.
If you need to support multiple instances of a component at the same time, you have to make sure the JavaScript state doesn't collide with each other. Develop your JavaScript so that the state of each instance is stored separately. You can generate an ID for each component and then store the ID as the key in a JavaScript object and store the state as the value.