NodeServices: Where Javascript and .NET Meet Back on the Other Side

Javascript is everywhere. It's in the browser, on the server, and it's climbing through your windows and snatching your people up. And because it's a fairly trivial language to pick up (but impossible to master), it can be extremely useful when you want to do something on the server.

This was and still is the real appeal of technologies like Node.js, however it's quite easy to do within the realm of .NET. This post is going to provide a very basic overview of how to target and execute some arbitrary Javascript code within .NET without ever touching the browser.

What is/are NodeServices?

I'll let the creator, Steve Sanderson, explain it as follows:

  • NodeServices provides a fast and robust way for .NET code to run JavaScript on the server inside a Node.js environment. You can use this to consume arbitrary functionality from NPM packages at runtime in your ASP.NET Core app.

  • Most applications developers don't need to use this directly, but you can do so if you want to implement your own functionality that involves calling Node.js code from .NET at runtime.

You can find the project itself on GitHub as well, which goes far beyond just interacting with just Node in .NET.

Setting Up NodeServices

This particular example of NodeServices depends on .NET Core 2.0, which you will want to install from here if you want to follow along. The steps may work with earlier versions, but if you run into issues, consider trying 2.0.

In this example, we will build a simple Web API that will rely on NodeServices to perform some operations. So to get started, we'll first need to create a new project:

dotnet new webapi

If you aren't a command line person, Visual Studio 2017 Update 3 should have the necessary tools to create .NET Core 2.0 applications through the traditional GUI.

Configuring NodeServices

Getting started with NodeServices is dead simple. You basically just need to include the Microsoft.AspNetCore.NodeServices NuGet package within your application via the following command:

dotnet add package Microsoft.AspNetCore.NodeServices

You should then see it within your project definition as well:

<ItemGroup>
    <!-- Others omitted for brevity -->
    <PackageReference Include="Microsoft.AspNetCore.NodeServices" Version="2.0.0" />
</ItemGroup>

Then, you'll need to configure the necessary middleware to handle using the service within your application in the ConfigureServices() method of your Startup.cs file:

public void ConfigureServices(IServiceCollection services)
{
    // This will configure the NodeServices service
    services.AddNodeServices();
}

After configuring the services, you'll now be able to use dependency-injection to handle injecting this into your application at the controller level:

public class ValuesController : Controller
{
        private readonly INodeServices _nodeServices;

        public ExampleController(INodeServices nodeServices)
        {
            _nodeServices = nodeServices;
        }

        // Other actions here
}

Or at a single method/action level with the [FromServices] attribute:

public async Task<IActionResult> Example([FromServices] INodeServices nodeServices)
{
       // TODO: Cool stuff
}

Now that we have the actual Node Services wired up, let's look at what has to be done on the Javascript side of things get the two sides to play nicely with one another.

Actually Using NodeServices

Since we are going to be calling Javascript code from within .NET, we will first need to define the actual code that we want to call. So to get started, we will create a new Scripts folder at the root of the current project and a new file called Add.js within it:

This Add.js file is going to function as a single module that will export a function which will be called by our C# code. For the sake of simplicity, we will start by simply adding two numbers together and returning the result via a callback:

module.exports = function(a, b, callback) { 
  let result = a + b;
  callback(result); 
};

Jumping back to the .NET side of things, we can configure our service to point to our new Javascript file using the InvokeAsync<T>() method, which expects the path to the file being invoked and a type to indicate the expected return type:

public async Task<long> Add(int x = 11, int y = 31)
{
    return await _nodeServices.InvokeAsync<long>("Scripts/Add.js", x, y);
}

After running your application and hitting that endpoint, you'll quickly see the expected result and that no client-side Javascript was harmed during the creation of the response:

Now, if you are familiar with Javascript, then you know that it can do all sorts of crazy things, especially when you use really dangerous functions like eval(). We'll create another file called Eval.js to add to our existing Scripts folder that looks like this:

module.exports = function (callback, x) {
    let result = eval(x);
    callback(null, result);
};

For demonstration purposes, let's create another method that accepts some arbitrary text and evaluates it within our Web API controller:

public async Task<string> Eval(string expression = "6 * 7")
{
    return await _nodeServices.InvokeAsync<string>("Scripts/Eval.js", expression);
}

We can see this in action below:

NodeServices really shines in scenarios where you might not be able to find the perfect NuGet package that you are looking for, but one exists on npm or somewhere else in the vast Javascript ecosystem. Simply grab the code that you need, or pull down the actual npm package itself (along with its required dependencies) and use it just as you would expect.

Let's see how something like that might work if we decide to use a fancy npm package to generate QR codes using a bit of text. First, we'll need to install the appropriate npm package:

npm install qr-image

Again - if you aren't a command line fan, you can download it directly from the source on GitHub or using some other package manager of your choice.

Once you have the QR package downloaded, you can create a new file within your Scripts folder called QR.js. Since we are now in a Node world, you'll just need to wire up the appropriate dependencies via a require() statement that points to your package and add the following code:

let qr = require('./qr-image');
module.exports = function (callback, text) {
    var result = qr.imageSync(text, { type: 'png' });

    var data = [];
    result.forEach(i => {
        data.push(i);
    });

    callback(null, data);
};

This does the following:

  • Wires up our QR package for use.
  • Uses the string data passed in to generate a QR code image.
  • Reads the image data into a byte[] that our C# code will consume.

At this point, we can write the corresponding method with our API:

public async Task<IActionResult> QR(string text = "42")
{
    var data = await _nodeServices.InvokeAsync<byte[]>("Scripts/QR.js", text);
    return File(data, "image/png");
}

And when we hit that method, we see that the values return as expected:

This is really just the tip of the iceberg, but it demonstrates just how easy it is to integrate the entire Node ecosystem within .NET to build all sorts of applications.

Give it a shot!

As mentioned earlier in the post, the npm and Node ecosystems are huge and there are tons of useful packages that you can now pull down and integrate into your ASP.NET applications with ease.

If you don't want to walk through all of these steps by hand, feel free to pull down the code from this demo or check out Steve Sanderson's additional examples found on the NodeServices repo as well: