Learn how YOU can use both C# and JavaScript in your .NET Core Blazor app with the JavaScript interop
Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
Blazor is the new framework from Microsoft that will make it possible to build applications for the Web using nothing but C#. It's easy at first look to think - Great I don't need to learn JavaScript. To be honest, I think this is the wrong way to look at it. Having a huge ecosystem of libraries written in JavaScript is an opportunity. At the end of the day, it's about solving the client's problem and doing so in a fast and efficient way. With Blazor we know have the opportunity to use the best of two worlds. The .NET Apis and the JavaScript ecosystem.
TLDR; This article will show how to use something called the JavaScript interop which allows us to call JavaScript code from Blazor. Learn to be the developer that leverages two powerful ecosystems .NET APIs and the JavaScript ecosystem.
In this article, we assume that you as a reader knows what Blazor is. It's recommended that if you are completely new to Blazor you first have a read through this introductory article: {% link https://dev.to/dotnet/blazor-the-future-of-net-web-apps-a-first-look-m8 %}
References
JavaScript and Blazor doc This link describes everything this article has gone through but also covers how you would from JavaScript call your Blazor component and additionally it talks about lifecycles
Introduction to Blazor doc This is a good introduction page to Blazor
Introduction to Razor templating with Blazor New to Razor templating and especially what tags exist in Blazor? Then this page is for you
Part one on Blazor, start here if you don't know anything about Blazor Want to know how to deploy your Blazor app? This goes through both a server hosted Blazor app as well as a WebAssembly version
WHY
As we mentioned at the beginning of this article. In my opinion, it's not good to limit ourselves to only use the .NET APIs. At the end of the day, it's about getting the job done. For that reason learning how to execute JavaScript from within Blazor is a skill worth having, especially if it means that we can leverage other existing libraries from for example NPM. Another reason for wanting to execute JavaScript from within Blazor might be that we need to use a specific Browser capability.
WHAT
This article covers something called the JavaScript interop. At our disposal, we have an abstraction called IJSRuntime
and on it, we execute the method InvokeAsync<T>()
. The method expects the name of the function you want to execute and a list of serialized JSON parameters. A typical call looks something like this:
var result = await JSRuntime.InvokeAsync<string>("methodName", input);
What happens in the above code is that the method methodName()
is being invoked and the parameter input
is being turned into a string.
IJSRuntime
Different ways to use the You can call the JavaScript interop from different places. Either:
- From the component, If you want to use it from within a component you just need an inject statement at the top of the component like so:
@inject IJSRuntime JSRuntime
- From the a C# class, if you want to use the interop from within a class you need to inject it into the class's constructor like so:
class Something
{
Something(IJSRuntime jsRuntime)
{
}
}
2
3
4
5
6
7
DEMO
Ok, so what are we building? Well, let's do the following:
- Scaffold a project, we need to create a Blazor project. We can do that from the command line
- Invoke javascript functions, we will create a Blazor component where we will add some code to showcase different ways of calling the JavaScript code using the interop functionality.
- Download and use a library from NPM, we will leverage the NPM ecosystem by downloading an NPM library and call that from our Blazor Component
Scaffold a project
There are two things that needs to be installed for us to be able to create a Blazor project:
- Blazor templates, we can easily install these from the command line with the command
- .NET Core, latest and greatest
You need to download .Net Core 3.0. Check out this link to find the correct distribution for your OS
https://dotnet.microsoft.com/download/dotnet-core/3.0
You want to be picking the latest and greatest on the above page cause it will give you the latest features of Blazor and usually Blazor templates rely on the latest possible version of .NET Core.
Additionally, you need templates so you can scaffold a Blazor app. Pop open a terminal and type
dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.1.0-preview2.19528.8
Invoke JavaScript functions
So the first question is, of course, how can we be calling JavaScript functions and with what? We know the first part of the answer. We should be using IJSRuntime
and the method InvokeAsync()
. Next, we need to know where to place our JavaScript?
The answer is inside of a script file and we need to refer to this script file by placing a script
tag in the directory wwwwroot
and the file index.html
.
-| wwwroot/
---| index.html
2
Let's say then that we create a file library.js
in wwwroot
so we know have:
-| wwwroot/
---| index.html
---| library.js
2
3
Then we need to open up index.html
and add our script tag like so:
<!-- index.html -->
<script src="library.js"></script>
2
What about the content of library.js
then? Well here it is:
// library.js
function add(lhs, rhs) {
return lhs+rhs;
}
2
3
4
5
At this point, let's go to our Pages
directory and create a new component Jsdemo.razor
, like so:
-| Pages
---| Jsdemo.razor
2
give it the following content:
@page "/jsdemo"
@inject IJSRuntime JSRuntime
<h2>JS Demo</h2>
Result : @result
<button @onclick="Add">Add</button>
@code {
int result = 0;
public async void Add()
{
result = await JSRuntime.InvokeAsync<int>("add",1,2);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
There are many things that go on here:
- We call
JSRuntime.InvokeAsync<int>("add",1,2)
, with the first arg beingadd
, the name of the function. Followed by1,2
, which are the arguments to the function. - Then we notice
<int>
, this sets up the return type of the function - Looking at the full function:
public async void Add()
{
result = await JSRuntime.InvokeAsync<int>("add",1,2);
}
2
3
4
we see that we call await
to wait for the response, which also means we need to mark our Add()
function with async
to make the compiler happy.
An example with more complex parameters
Ok, we want to ensure that it still works by invoking functions with parameters that are arrays and even objects.
Let's add two functions to our library.js
and update it's content to the following:
// library.js
function add(lhs, rhs) {
return lhs+rhs;
}
function commonElements(arr1, arr2) {
return arr1.filter(a => arr2.find(b => b === a)).join(',');
}
2
3
4
5
6
7
8
9
So how to call it? Well, just like we did before using JSRuntime.InvokeAsync<int>("name-of-method",arg...)
.
Let's go update our Blazor component Jsdemo.razor
to this:
@page "/jsdemo"
@inject IJSRuntime JSRuntime
<h2>JS Demo</h2>
Result : @result
Common elements result:
@stringResult
<button @onclick="Add">Add</button>
<button @onclick="Common">Common elements</button>
@code {
int result = 0;
string stringResult = "";
int[] arr1 = new int [2]{1,2};
int[] arr2 = new int [2]{2,3};
public async Common()
{
stringResult = await JSRuntime.InvokeAsync<string>("commonElements",arr1,arr2);
}
public async void Add()
{
result = await JSRuntime.InvokeAsync<int>("add",1,2);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Note above how we add some markup for this new result:
Common elements result:
@stringResult
<button @onclick="Common">Common elements</button>
2
3
4
We also need to create some new input parameters, two arrays:
int[] arr1 = new int [2]{1,2};
int[] arr2 = new int [2]{2,3};
2
Lastly we add the method Common()
, like so:
public async Common()
{
stringResult = await JSRuntime.InvokeAsync<int>("commonElements",1,2);
}
2
3
4
As you can see there is really no difference between having primitives our arrays as inputs. The main reason is that everything gets serialized to JSON on the .NET side and is de-serialized right back when the JavaScript function is being invoked.
### Calling NPM code
Ok, so let's talk about using JavaScript libraries. After all one of the strengths of Blazor lies in the ability to use both ecosystems, both .NET and NPM. So how do we do that?
Well, we need to consider the following:
- Large libraries, some libraries are really big out there, like
Lodash
. Fortunately, there is a way to import only the functions we are going to need, the rest can be omitted through a process called tree shaking - If we are using only a part of a library like the above scenario we need a way to extract the code we need, so we need a tool like
browserify
orwebpack
to create a bundle of a subset of code
Ok then, we understand what we need to consider. Now let's do just that, let's extract a function from the library lodash
. Let's list the steps we need to take:
- Create a directory where our bundle and downloaded libraries will live
- Download the NPM library
- Set up up a tool like
browserify
to make it possible to create bundle with anNPM command
- Create the bundle with
browserify
and include the resulting bundle as a script tag - Try out code from our bundle
Create
Let's create a directory under wwwroot
called npm-libs
, you can call it what you want.
It should now looks like this:
-| wwwroot
---| npm-libs/
2
We will treat this is as a Node.js project and with Node.js projects you want to initialize it using npm init
, like so:
npm init -y
This will give us a nice Node.js project with some good defaults and most of all a package.json
file.
-| wwwroot
---| npm-libs/
------| package.json
2
3
We will use this package.json
file as a manifest file that tells us about the libraries we need and commands we can use to build our bundle.
Download
Inside of our npm-libs
we now run the npm install
command to give us the library we want, in this case, lodash
:
npm install lodash
This means that our file structure now contains a node_modules
directory, with our downloaded library, like so:
-| wwwroot
---| npm-libs/
------| package.json
------| node_modules/
2
3
4
Set up
Next, we need to install our bundling tool browserify
:
npm install -g browserify
We are now ready to define a command to run browserify
,it should look like so:
browserify -d index.js > bundle.js
The above will take the file index.js
, walk the tree over all its dependencies and produce a bundle, that we call bundle.js
. Note also how we include -d
, this is for source maps. Source maps mean our modules will be remembered for how they looked prior to bundling. We should lose the -d
when in production because the source maps makes the bundle bigger.
Let's put this command in the scripts
section of package.json
so we now have:
"build": "browserify -d index.js > bundle.js"
Ok then, the next step is to create our index.js
like so:
-| wwwroot
---| npm-libs/
------| index.js
------| package.json
------| node_modules/
2
3
4
5
and give it the following content:
// index.js
var intersect = require('lodash/fp/intersection');
window.intersect = function(arg1, arg2) {
let result = intersect(arg1, arg2);
return result.join(',');
};
2
3
4
5
6
7
8
What we are doing above is asking for a subset of lodash
by only loading the function intersection
:
var intersect = require('lodash/fp/intersection');
this means that when this tree-shakes, it will only include the intersection
code and our bundle will be at a minimum.
Next, we assign the intersection
function to the window
property and expose it so our C# code can call it.
window.intersect = function(arg1, arg2) {
let result = intersect(arg1, arg2);
return result.join(',');
};
2
3
4
At this point we run:
npm run build
This should produce a bundle.js
. We should also add a reference to our bundle.js
in our index.html
file, like so:
<script src="bundle.js"></script>
Try it out
Lastly, we want to call this JavaScript code from our Blazor component. So we add the following code to our @code
section, like so:
public async void Intersect()
{
intersectResult = await JSRuntime.InvokeAsync<string>("intersect",arr1, arr2);
Console.WriteLine(intersectResult);
}
2
3
4
5
and the following to our markup:
<button @onclick="Intersect">Intersect</button>
Intersect:
@intersectResult
2
3
4
Full code of our Blazor component
Let's show the full code in case you got lost somewhere:
@page "/jsdemo"
@inject IJSRuntime JSRuntime
<h2>JS Demo</h2>
Result : @result
<button @onclick="Click">Press</button>
<button @onclick="Add">Add</button>
<button @onclick="Intersect">Intersect</button>
Intersect:
@intersectResult
@code {
int result = 0;
string intersectResult = "replace me";
int[] arr1 = new int [2]{1,2};
int[] arr2 = new int [2]{2,3};
public async void Intersect()
{
intersectResult = await JSRuntime.InvokeAsync<string>("intersect",arr1, arr2);
Console.WriteLine(intersectResult);
}
public async void Add()
{
result = await JSRuntime.InvokeAsync<int>("add",1,2);
}
void Click()
{
JSRuntime.InvokeAsync<string>("alert","hello");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Summary
And that's all, that's what we wanted to achieve. We tried out different ways of calling our code, with primitive parameters, without it. We even showed how we could download a JavaScript library from NPM and make that part of our project.
I hope this was educational and that you are helped for the following scenarios:
- Occasional usage, Calling JavaScript code occasionally
- Leveraging existing libraries, you might have existing libraries you've written and don't want to reinvent the wheel or maybe there's a library on NPM that just does what you want it to.