Javascript interop in Blazor Web assembly - Part 1

...

Intro

For the past year I have been building a Blazor Web Assembly app. I'm a C# developer who likes to avoid Javascript as much as possible. Unfortunately there are some things you just can't do yet in Web Assembly and therefore Blazor. For some tasks there is no other option than to resort to Javascript. Luckily Blazor provides a way to call out to javascript code and allow it to call back into .NET code. There are lots of articles out there covering how to do this, so this first post will not be saying much new.

It's a necessary starting point for the subsequent articles though. 

As part of its functionality, the app I've been working on deals with video streaming. I use webRTC to capture the input from the users camera and microphone and then connect to an instance of Wowza Streaming engine. This then serves the video to invited guests who can watch live. After the event the recordings are available to view from an AWS file server. The device capture and connection to Wowza use Javascript. When it comes to viewing the recorded event, the format of the video is HLS which can't be viewed by a standard HTML5 video element. Luckily there are some great open source players out there that can handle that format. One such is videoJS.

As it's name implies, VideoJS is a Javascript library.

I'll be needing to use the player functionality on multiple pages so it makes perfect sense to wrap this in a Blazor component. I also plan to use similar functionality in other apps, so it makes sense for me to put it in a component library.

I've had to brush up on Javascript massively over the past few months in order to get these various things working, but I'd kind of like it to not matter if I totally forget everything about it. When I reference my Blazor component I want to do so oblivious to the fact there is any Javascript there at all.

And its this last part that has had me bashing my head against my monitor for over a week. I've watched every pluralsight course on Blazor. I've read every blog and article I can find on interop in Blazor. I've waited until Chris Sainty's excellent book has finally released its chapter on Javascript interop. It didn't tell me how to do what I wanted, but it did give me the missing piece of the puzzle.

 

So in the following series we will look at:

  1. The basics of Javascript Interop - in which we will explore how to call Javascript via interop and call .NET from Javascript in it's most basic manner. 
  2. Import a Javascript Module into web Assembly App - we will use interop to call functionality on a Javascript module. This is the basis of being able to then import any third party library via npm in part 4.
  3. Move the functionality into a Blazor component - we will create a reusable Blazor Component that takes advantage of Javascript isolation so that it can be consumed on multiple pages without those pages needing to know anything about the underlying Javascript.
  4. Import 3rd party Modules via NPM - we should now have a solid grounding of the various functionality we need to take advantage of. Now its time to utilise a third party javascript library using NPM. In order to make this work we will use a Javascript bundler - Snowpack
  5. Extract our Component to a Razor Class Library - We've already created a reusable component, but it's limited to the one app. Now we put that into a class library that means we can reuse it in many apps.

 

So now that we know where we're headed lets take a look at the basics of Javascript interop in Blazor.

 

The Basics Of Javascript interop in Blazor

We'll start off by creating a new Blazor Web Assembly app.

Name it something appropriate.

Target .NET5 or later. Leave all the other settings at their defaults. 

 

We now have the basic Blazor App, which in solutiuon explorer looks like

 

and when run looks something like

 

There are other articles out there that explain the constituent parts of this default app. If we were creating our own fully fledged app we'd remove most of what is generated, but it suits our purposes quite well and provides a useful framework into which we can drop in our Javascript interop examples.

So first off we need some javascript. Add a new folder to the wwwroot folder, Call it js. This is where we'll store our javascript files for now. Add a new javascript file to that folder and call it index.js.

We'll add some very simple javascript to this file. We are just going to show a message box. Simple but effective at showing that we've managed to call javascript from .NET.  

function showJavascriptAlert(message) {
    alert(message);
}

 

Next we'll edit index.razor which creates the content shown in the webpage previously. Lets clear the other stuff from the page and put a button on there that we can use to call our Javascript.

Change it from

 

to

@page "/"
 
 
<h1>Hello, world!</h1>
 
 
 
<button type="button" class="btn btn-info" @onclick="CallSomeJavascript">Call Some Javascript</button>
 
@code
{
 
    [Inject]
    public IJSRuntime JSRuntime { get; set; }
    public async Task CallSomeJavascript()
    {
        
        await JSRuntime.InvokeVoidAsync("showJavascriptAlert", "Congratulations! You called some Javascript from .NET");
    }
 
}

The button calls the function  'CallSomeJavascript' in our code section. The button and function are the same as any other Blazor button, but within the function we call out to our Javascript function. This is done via the IJSRuntime service that has been injected into our class. We could also have specified this as a dependency at the top of the file

@page "/"
@inject IJSRuntime JSRuntime

We would still have to mark this with the [Inject] attribute if we put our code in a separate code file as Blazor doesn't support constructor injection.

Once we have this service available to us, we can use it to call Javascript. In our instance we are calling a javascript function with no return type, so we use InvokeVoidAsync. If we were calling a function that returned a for example a string we would call InvokeAsync<string>.

In both cases the parameters we pass are the same. The first parameter is the javascript function we want to call. The remaining parameters are any parameters we wish to pass to the javascript function.  

If we run the application now we will see the button, but when we call it we will get an arror.

The error is saying it can't find our javascript function. This not surprising as we haven't reference the javascript file. The fix for this (for now) is to add a reference to the javescript file in index.html at the end of the body section.

<body>
    <div id="app">Loading...</div>
 
    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
    <script src="js/index.js"></script>
</body>

if we run the application now, and click the button we get the pop up as desired.

 

Ok - so we have successfully called some javascript from .NET, but lets just reflect on this a moment. The point of this very basic article is to lay the groundwork for the next stages. This approach to the javascript and referencing it like this works fine in a self contained app. But we're after reusability and as we'll see in the later articles this doesn't lend itself to that end very well. We delve deeper into the reasons for that later, but for now lets understand WHY this fix worked. What happened when we put that reference to the javascript file into index.html?

Well, essentially, when the browser renders our page it loads and runs any script files it encounters. So it loaded our file and made our function available on the global window namespace. Until recently Blazor could only call functions that were on the window object. As we'll cover in a later article on Javascript isolation this is no longer the case. Our blazor app is running within the browser hosted within index.html. The browser has a window object that is globally available to all things within it. So our function is now available to all razor pages within our app. And all components, whether within our app directly or imported via a class library. So it can be used by anything, including components that may think they are calling a different 'showJavascriptAlert' function. Naming clashes are a potential issue with this approach. Which is one of the things we will fix when we look at Javascript isolation in the next article in this series.