Edge.js

Node.js Meets .NET – Edge.js

With so much JavaScript development on the client side, it only makes sense that developers and organizations would want to develop server side applications using JavaScript as well.  Node.js has become a popular choice for building server side applications using JavaScript because of its asynchronous event-driven model and many libraries and extensions that are available.

But sometimes JavaScript is not the best choice for what you need to do, even in a Node.js application.

There is package for Node.js that allows you to invoke .NET code in-process from a Node.js application – Edge.js.

Edge.js is supported on Windows, Linux and OS X with .NET Framework 4.5 or Mono 3.4.0.  Edge.js works with a variety of .NET languages including C#, F#, IronPython and LISP.  It also works with T-SQL and Windows PowerShell.  It was created by Tomasz Janczuk.

Here are some reasons for using .NET with Node.js:

  • Integrating/reusing existing .NET components
  • Accessing a SQL Server database using ADO.NET
  • Using CLR threading for CPU intensive processing
  • Writing native extensions to Node.js using a .NET language instead of C/C++.
  • Use your imagination…You’ll come up with something

Installation is easy, just use the node package manager to run the command:

npm install edge

This installs the core Edge.js module with support for C#.  Additional Edge.js modules are required for other languages:

  • T-SQL:  edge-sql
  • F#:  edge-fs
  • LISP:  edge-lsharp
  • Windows Powershell:  edge-ps
  • IronPython:  edge-py

Functions

The first thing you will probably want to do is create a JavaScript proxy to some C# code.  In Edge.js, functions in C# are defined as Func <object, task<object>> where the first parameter is the input and the second parameter is the callback function. The edge.func() function will create the JavaScript proxy to the your C# code that can be executed synchronously or asynchronously from your Node.js code.

I’ll show how to do this with C# code inline with JavaScript, using a multi-line lamdba expression and a Startup class with an Invoke method.  The downside of these methods are that you are writing code inside the comment block, so you don’t get the benefits of intellisense and syntax checking.  You can also reference a pre-compiled assembly or C# in a separate file.  Both examples below show a reference to the Linq assembly but you can reference any external assemblies you need.

Multi-line Lambda

var edge = require('edge');

var add = edge.func(
    {
        source: function() {/*
            async (dynamic input) =>
	     {
	         return (int)input.a + (int)input.b;
	     }
	  */},
	  references: ['System.Linq.dll']
    });

add({ a: 5, b: 10}, function (error, result) {
	console.log(result);
});

Startup Class With Invoke Method

var edge = require('edge');

var add = edge.func(
    {
        source: function() {/*
            using System.Threading.Tasks;

            public class Startup
            {
                public async Task<object> Invoke(dynamic input)</object>
                {
                    int a = (int)input.a;
                    int b = (int)input.b;
                    return MathHelper.Add(a, b);
                }
            }

            static class MathHelper
            {
                public static int Add(int a, int b)
                {
                    return a + b;
                }
            }
        */},
        references: ['System.Linq.dll']
    });

add({ a: 5, b: 10}, function (error, result) {
    console.log(result);
});

Asynchronous vs. Synchronous Execution

// asynchronous execution with a callback
add({a: 10, b:20 }, function(error, result) {
});

// synchronous execution with a return value
var result = add({a: 10, b:20 }, true)

Data Marshalling

Edge.js can marshal any JSON data between Node.js and .NET.  JavaScript objects are represented as dynamics in .NET that can be cast to an IDictionary<string, object>.  Arrays in JavaScript become object arrays in .NET.  For binary data, a Node Buffer will get translated into a .NET byte array.  When marshalling data from .NET back to Node.js, classes and anonymous types that are JSON serializable will both get converted to JavaScript objects.  Be careful to avoid circular references in your object graphs as these will most likely result in a stack overflow.

This example shows how you might pass data to a legacy component that would save an order.  The Invoke method also returns an asynchronous function so the .NET code will run on a separate thread and not block Node.js.

var edge = require('edge');

var submitOrder = edge.func({
    source: function() {/*
        using System.Threading.Tasks;
        using System.Collections.Generic;

        public class Order
        {
            public string orderNumber { get; set; }
            public int customerId { get; set; }
            public double total { get; set; }
            public List<OrderItem> items { get; set; }
        }

        public class OrderItem
        {
            public string name { get; set; }
            public double price { get; set; }
            public int quantity { get; set; }
        }

        public class Startup
        {
            public async Task<object> Invoke(dynamic input)
            {
                // returning the async function here
                // allows the .NET code to run on a
                // separate thread that doesn't block Node.js
                return await Task.Run<object>(async () => {

                    var order = new Order
                    {
                        orderNumber = System.DateTime.Now.Ticks.ToString(),
                        customerId = (int)input.customerId,
                        items = new List<OrderItem>()
                    };

                    dynamic [] items = input.items;
                    foreach (var item in items)
                    {
                        var orderItem = new OrderItem
                        {
                            name = (string)item.name,
                            price = (double)item.price,
                            quantity = (int)item.quantity
                        };

                        order.items.Add(orderItem);
                        order.total += orderItem.price;
                    }

                    // ... invoke legacy code to save order here

                    return order;
                });
            }
        }
    */},
    references: ['System.Collections.dll']
});

var order = {
    customerId: 1001,
    items: [
        { name: 'XBox One Console', price: 329.95, quantity: 1 },
        { name: 'Madden NFL 15', price: 49.95, quantity: 1}
    ]
};

submitOrder(order, function (error, result) {
    if (error)
        console.log(error);
    else
        console.log(result);
});

On Windows, you can debug .NET code in your Node.js applications.  To debug C# code from a Node.js application, you first need to set the environment variable EDGE_CS_DEBUG=1.  After starting node.exe, you simply attach your .NET managed code debugger to the node.exe process.

Should you have a need to script Node.js code from a .NET app, there is also an Edge.js NuGet package that enables this.

Final Thoughts

As you can see, there is a lot of flexibility with using Edge.js as a interop layer between Node.js and .NET.  If you are building Node.js apps and have existing .NET code you want to reuse (or find something you think would just be a lot easier/quicker to code in .NET than JavaScript), you probably want to give Edge.js a try.

For more information and examples, see https://github.com/tjanczuk/edge/ and http://tjanczuk.github.io/edge

Reference: Node.js Meets .NET – Edge.js from our WCG partner Keyhole Software at the Keyhole Software blog.

Keyhole Software

Keyhole is a midwest-based consulting firm with a tight-knit technical team. We work primarily with Java, JavaScript and .NET technologies, specializing in application development. We love the challenge that comes in consulting and blog often regarding some of the technical situations and technologies we face.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button