Calling .NET Code from Your JavaScript Project
.NET has had a long and varied history. Back in the 1990s when the .NET framework first came about, no one could have imagined it would’ve been ported to so many environments. We have Mono, Xamarin, and .NET Core, all targeting different environments and sometimes stepping on each other’s toes. But with the release of .NET 5, Microsoft made an attempt to introduce one universal framework that runs everywhere.
In today’s blog post, I’ll show how you can run your .NET code in the browser, without making any calls to a server, proving that .NET running everywhere isn’t just a pipe dream. So if you’ve ever wanted to port your .NET project to the web but didn’t want to run a server, keep reading.
How Does .NET Run in the Browser?
Alongside .NET 6 came a new framework (yes another one) called Blazor. The following is how Microsoft describes the framework.
Blazor is a framework for building interactive client-side web UI with .NET
Using this new framework, we can build dynamic web applications with .NET. That’s handy if you’re already a .NET/C# aficionado, as there’s no need to learn JavaScript or the latest and greatest new JavaScript framework.
I’m not going to introduce the programming concepts of Blazor here because that’s not important for this post, but what is important is how it operates.
Blazor can either operate in Blazor Server mode or Blazor WebAssembly mode. Server, as the name suggests, means we’re running some code on a backend somewhere and serving it to the client. But WebAssembly is more interesting. In short, it means full client-side execution.
Here at PSPDFKit, we’ve been using WebAssembly for a while. PSPDFKit for Web uses WebAssembly to provide the exact same PDF engine in a web browser as you’d have on iOS or Android. WebAssembly allows you to take compiled languages such as C, C++, and Rust and compile them to a format that can be run by the browser engine at near native speeds. This means you can take well-established C++ projects and bring them to the web with little work.
That’s what Blazor WebAssembly has done for .NET.
When you run Blazor in WebAssembly mode, it runs a WebAssembly version of the .NET runtime in the browser, and your .NET code is interpreted right there, in the browser, at runtime!
But hold on, wait one minute. Didn’t I say that Blazor was a client-side web application framework? What if I want to integrate some .NET code into an already existing JavaScript project?
Calling .NET Code from an Existing JavaScript Project
Because Blazor is an all-in-one web app framework, it handles everything related to serving a webpage, including generating the HTML, CSS, and JavaScript bindings to call through to .NET code. That’s great if you’re starting a new web project, but many of us aren’t, and we don’t have that luxury. So, we need to find a way to adapt Blazor to expose .NET functions to JavaScript.
In announcements for .NET 7, there were mentions of being able to run Blazor code without the UI, but in our initial tests, the build proved to be suboptimal in terms of size and speed. Instead, we’re going to share a method of hooking into a standard Blazor build running in WebAssembly mode and executing exposed .NET methods from JavaScript.
So let’s get into some code.
If you dig into a vanilla Blazor project like the getting started example published by Microsoft, you’ll see a single entry point for Blazor run as script in HTML:
<script src="_framework/blazor.webassembly.js"></script>
This script will fetch all the required WebAssembly files and .NET dependencies, instantiate the .NET runtime, and produce JavaScript functions that can communicate with .NET code. The problem is that this all takes place internally, so we don’t know what we can and can’t call from JavaScript.
For our solution, we still want to load the .NET runtime and all its dependencies, but we then want to expose functionality directly in JavaScript.
Stack Overflow and @Elringus to the rescue (thank you for the pointers)!
Start with a simple Blazor template.
Using the template, create a C# function to call by adding the following to Program.cs
in the Program
class:
namespace MyTemplate { public class Program { ... [JSInvokable] public static async Task<string> SayHello (string name) { return $"Hello {name}."; } ... } }
The [JSInvokable]
indicates that it’s a function we want to expose to JavaScript. Next, we want to create a script that can be called from the browser and which loads Blazor and binds to our newly exposed SayHello
function. Create a script named initModule.js
in the wwwroot
folder of your project with the following contents:
// Invokes the generated Blazor JS to load and instantiate the WASM module and dependencies. const script = document.createElement('script'); script.type = 'text/javascript'; script.src = '_framework/blazor.webassembly.js'; document.body.appendChild(script); // Create a callable JS function that calls through to the exposed C#. window.sayHello = async function (name) { return await DotNet.invokeMethodAsync( 'MyTemplate', 'SayHello', name, ); };
Now, if you have the .NET 6 or 7 SDK installed, you can run dotnet publish -c Release
, and it’ll compile and produce everything you need in bin/Release/net6.0/publish/wwwroot
.
To use the published assets in your JavaScript project, you just have to serve bin/Release/net6.0/publish/wwwroot
and invoke the initModule.js
script. Then you’ll be able to call the sayHello
binding in JavaScript:
... <script src="initModule.js"></script> <button onclick="sayHello()">Click Me!</button> ...
And that’s it! Every time the button is clicked, the .NET function SayHello
is called. From here, you can expand the implementation to perform more complex operations, all in .NET!
Conclusion
Having the opportunity to run .NET code on the client side opens up a lot of opportunity. Now operations can be performed on a customers’ browser, side stepping tricky security risks involved with servers. It can also reduce duplication in your company by reusing .NET packages developed for other environments, exposing functionality written for .NET directly to a frontend web project.
For now, this solution works, but in the future, we hope for it to be optimized. For example, the UI code is loaded when Blazor downloads all its dependencies, but in our example, we aren’t utilizing any UI functionality, and it’d be nice to omit this overhead. Looking to .NET 7, I’m hoping that the mention of using Blazor without a UI comes through and that we can use an off-the-shelf solution for running .NET in the browser!
When Nick started tinkering with guitar effects pedals, he didn’t realize it’d take him all the way to a career in software. He has worked on products that communicate with space, blast Metallica to packed stadiums, and enable millions to use documents through Nutrient, but in his personal life, he enjoys the simplicity of running in the mountains.