Edge.js

Run Node.js and .NET in-process

A software play in two acts

{ Act I }

Where Node.js scripts .NET in-process
on Windows, Mac OS, and Linux (NEW!)

{ Act II }

Where .NET scripts Node.js in-process (NEW!)

skip to act I | skip to act II

Brought to you by Tomasz Janczuk / @tjanczuk
(Use space or arrow keys to navigate)

Prologue

Where Node.js meets .NET in-process
and the intrigue begins



{ Tess }
What problems does Edge.js solve?

{ Scott Hanselman }
Ah, whatever problem you have. If you have this problem, this solves it.

.NET welcomes Node.js

Call C# async lambda from Node.js


var edge = require('edge');

var hello = edge.func(function () {/*
    async (input) => { 
        return ".NET welcomes " + input.ToString(); 
    }
*/});

hello('Node.js', function (error, result) {
    if (error) throw error;
    console.log(result);
});
          
see full code

> node sample.js
.NET welcomes Node.js
          

On Windows...

Edge.js on Windows

...On Mac...

Edge.js on MacOS

...and on Linux

Edge.js on Linux

Node.js welcomes .NET

Call Node.js functions from .NET


using EdgeJs;

public static async void Start() 
{
    var func = Edge.Func(@"
        return function (data, cb) {
            cb(null, 'Node.js ' + process.version + ' welcomes ' + data);
        }
    ");

    Console.WriteLine(await func(".NET"));
}
          
read more

> sample.exe
Node.js v0.10.28 welcomes .NET
          

On Windows (Mac/Linux coming)

Scripting Node from C#

One interop model

Async, in-process calling convention between CLR and Node.js

Edge.js interop model

Edge.js connects

Node.js and .NET ecosystems

JavaScript with C#, F#, Python, Lisp, and PowerShell

loose typing with strong typing

IO-bound single threaded with CPU-bound multi-threaded

cool with awesome

on Windows, MacOS, and Linux

in one process

Act I

Where Node.js scripts .NET in-process
on Windows, Mac OS, and Linux (NEW!)

docs

C# welcomes Node.js

Call C# async lambda from Node.js


var edge = require('edge');

var hello = edge.func(function () {/*
    async (input) => { 
        return "CSharp welcomes " + input.ToString(); 
    }
*/});

hello('Node.js', function (error, result) {
    if (error) throw error;
    console.log(result);
});
          
see full code

> node sample.js
CSharp welcomes Node.js
          

Not only C#

F# welcomes Node.js

Call F# async workflows from Node.js


var edge = require('edge');

var hello = edge.func('fs', function () {/*
    fun input -> async { 
        return "FSharp welcomes " + input.ToString() 
    }
*/});

hello('Node.js', function (error, result) {
    if (error) throw error;
    console.log(result);
});
          
how to F# in Node.js

$> node sample.js
FSharp welcomes Node.js
          

Python welcomes Node.js

Script IronPython from Node.js


var edge = require('edge');

var hello = edge.func('py', function () {/*
    def hello(input):
        return "Python welcomes " + input

    lambda x: hello(x)
*/});

hello('Node.js', function (error, result) {
    if (error) throw error;
    console.log(result);
});
          
how to python in Node.js

$> node sample.js
Python welcomes Node.js
          

T-SQL welcomes Node.js

Access MS SQL from Node.js using in-process ADO.NET


var update = require('edge').func('sql', function () {/*
    update Products
    set ProductName = @newName 
    where ProductId = @id
*/});

update({ id: 10, newName: 'New Ikura' }, function (error, result) {
    if (error) throw error;
    console.log(result);
});
          
how to T-SQL in Node.js

PowerShell welcomes Node.js

Call PowerShell scripts asynchronously from Node.js (Windows only)


var edge = require('edge');

var hello = edge.func('ps', function () {/*
    "PowerShell welcomes $inputFromJS"
*/});

hello('Node.js', function (error, result) {
    if (error) throw error;
    console.log(result);
});
          
how to PowerShell in Node.js

c:\projects\edgedemo> node sample.js
PowerShell welcomes Node.js
          

Lisp welcomes Node.js

Call Lisp from Node.js


var edge = require('edge');

var factorial = edge.func('lsharp', function () {/*
  (def fact(n) 
      (if (is n 0) 1 (* n (fact (- n 1)))))
*/});

factorial([5], function (error, result) {
    if (error) throw error;
    console.log(result);
});
          
edge-lsharp extension by @richorama

c:\projects\edgedemo> node sample.js
120
          

FOO welcomes Node.js

Extend Edge.js with other CLR/Mono languages or DSLs


var helloCs = edge.func(...); // C# is the default
var helloFs = edge.func('fs', ...); // F#
var helloPy = edge.func('py', ...); // Python

var helloFo = edge.func('foo', ...); // Your CLR/Mono language or DSL here
            
learn more

Bind to library

Call a method from existing CLR library


var hello = require('edge').func({
    assemblyFile: 'My.Edge.Samples.dll',
    typeName: 'Samples.FooBar.MyType',
    methodName: 'MyMethod' // Func<object,Task<object>>
}});

hello('Node.js', function (error, result) { ... });
            
how to integrate CLR code

Bind to code

Compile source code on the fly


var hello = edge.func(
    'async (input) => { return "C# welcomes " + input.ToString(); }'
);
            
how to integrate CLR code

Bind to multiline code

Embed multi-line source code inline


var hello = edge.func(function () {/*
    async (input) => { 
        return "C# welcomes " + input.ToString(); 
    }
*/});
            
how to integrate CLR code

Bind to source file

Compile source code from a file


var hello = edge.func('mysample.cs');
            
how to integrate CLR code

Multiple languages

Compile code for many CLR languages or DSLs through extensibility


var helloCs = edge.func(...); // C# is the default
var helloFs = edge.func('fs', ...); // F#
var helloPy = edge.func('py', ...); // Python
var helloPs = edge.func('ps', ...); // PowerShell
var helloPs = edge.func('sql', ...); // T-SQL via ADO.NET
...
            
how to support other CLR languages or DSLs

Two C# representations

Source code may be an async lambda expression or class library


var helloLambda = edge.func(function () {/*
    async (input) => { return "C# welcomes " + input.ToString(); }
*/});

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

  public class Startup {
    public async Task<object> Invoke(object input) {
      return ".NET welcomes " + input.ToString();
    }
  }
*/});
            
how to integrate CLR code

Dynamics

Use C# dynamics to access objects passed from JavaScript


var hello = edge.func(function () {/*
    async (dynamic input) => { 
        return input.nested.text + " work!"; 
    }
*/});

var input = {
    nested: {
        text: "Dynamics"
    }
};

hello(input, function (error, result) { ... });
            

References and namespaces

Reference libraries and import namespaces within script


var accessSql = edge.func(function () {/*
    #r "System.Data.dll"

    using System.Data;
    
    async (input) => {
        ...
    }
*/});
            

Sync vs async

Call CLR functions synchronously or asynchronously


var hello = edge.func(...);

// Make asynchronous call
hello('Node.js', function (error, result) {
  ...
});

// Make synchronous call
// Requires CLR function to return completed Task<object>
var result = hello('Node.js', true);
            
how to integrate CLR code

Debugging

Use Visual Studio to debug C# code running in node.exe

Edge.js debugging

Data from Node.js to C#

Pass data from Node.js to C#


var hello = require('edge').func('My.Sample.dll');

var payload = {
  anInteger: 1,
  aNumber: 3.1415,
  aString: 'foobar',
  aBool: true,
  anObject: { first: 'Tomasz', last: 'Janczuk' },
  anArray: [ 'a', 1, true ],
  aBuffer: new Buffer(1024)
}

hello(payload, function (error, result) { ... });
          
see full code

Data from C# to Node.js

Pass data from C# to Node.js


var getData = require('edge').func(function () {/*
  async (input) => {
    return new {
      anInteger = 1,
      aNumber = 3.1415,
      aBool = true,
      anObject = new { a = "b", c = 12 },
      anArray = new object[] { "a", 1, true },
      aPerson = new Person("Tomasz", "Janczuk"),
      aBuffer = new byte[1024]
    };
  }
*/});

getData(null, function (error, result) { ... });
          
see full code

Export Node.js function to C#

Node.js function is also data


var addAndMultiplyBy2 = require('edge').func( ... );

var payload = {
  a: 2,
  b: 3,
  timesTwo: function(input, callback) {
    callback(null, input * 2);
  }
};

addAndMultiplyBy2(payload, function (error, result) { ... });
          
see full code

Call Node.js function from C#

Node.js function is also data, continued


var addAndMultiplyBy2 = require('edge').func(function () {/*
  using ...;

  async (dynamic data) => {
    int sum = (int)data.a + (int)data.b;
    var timesTwo = (Func<object,Task<object>>)data.timesTwo;
    return await timesTwo(sum);
  }
*/});
          
see full code

Return C# function to Node.js

C# function is also data


var createHello = require('edge').func(function () {/*
    async (input) => {
        return (Func<object,Task<object>>)(async (i) => { 
            Console.WriteLine("Hello from .NET"); 
            return null; 
        });
    }
*/});

var hello = createHello(null, true); 
hello(null, true); // prints out "Hello from .NET"
          
see full example

Manage C# state from Node.js

Call Node.js proxy to C# closure over CLR data


var createCounter = require('edge').func(function () {/*
    async (input) => {
        var k = (int)input; // CLR state 
        return (Func<object,Task<object>>)
          (async (i) => { return ++k; });
    }
*/});

// create counter with initial state of 12
var counter = createCounter(12, true); 
console.log(counter(null, true)); // prints 13
console.log(counter(null, true)); // prints 14
          
see full example

Performance

Latency of Edge.js call is 32x smaller than localhost, cross-process call over HTTP

performance of Edge.js

details

Let's build something...

Script T-SQL in Node.js via ADO.NET


var update = require('edge').func('sql', function () {/*
    update Products
    set ProductName = @newName 
    where ProductId = @id
*/});

update({ id: 10, newName: 'New Ikura' }, function (error, result) {
    if (error) throw error;
    console.log(result);
});
          
how to T-SQL in Node.js

Full ADO.NET in Node.js


#r "System.Data.dll"
#r ...

using System.Data;
using ...

public class Startup {
  public async Task<object> Invoke(string command) {
    string connectionString = ...;

    using (var conn = new SqlConnection(connectionString)) {
      using (var command = new SqlCommand(commandString, conn)) {
        await connection.OpenAsync();
        using (var reader = await command.ExecuteReaderAsync() {
    ...
         
full article by David Neal (@reverentgeek)

Multi-threading in Node.js process

Run CPU-bound background work on CLR thread pool


var heavyLifting = require('edge').func(function () {/*
  async (input) => { 
    // we are on V8 thread here
    return await Task.Run<object>(async () => {
      // we are on CLR thread pool thread here
      await Task.Delay(5000); // simulate CPU bound  
      return ".NET welcomes " + input.ToString();
    });
  }
*/});

heavyLifting('Node.js', function (error, result) { ... });
         
see full code

Windows authentication

Use Windows authentication to authenticate calls to your Node.js HTTP server


var authenticate = require('edge').func(function() {/*
  class Startup {
    [DllImport("advapi32.dll")] static extern bool LogonUser(...);

    public async Task<object> Invoke(dynamic i) {
      IntPtr t;
      return Startup.LogonUser(i.user, null, i.password, 3, 0, out t)) 
    }
  }
*/});

authenticate({ 
  user: 'tjanczuk@redmond.corp.microsoft.com',
  password: 'foobar' 
}, function (error, result) { ... });
         
see full code

ZIP compression

Compress files and folders using .NET 4.5 ZIP compression


var zip = require('edge').func(function() {/*
  class Startup {
    public async Task<object> Invoke(dynamic p) {
      await Task.Run(async () => {
        System.IO.Compression.ZipFile.CreateFromDirectory(
          (string)p.src, (string)p.dest);
      });
      return null;
    }
  }
*/});

zip({ src: '..\\mydir', dest: '..\\mydir.zip' }, function (error) { 
  ...
});
         
see full code

Image conversion

Convert images between JPG, BMP, TIFF, PNG, and GIF


var convertImageToJpg = require('edge').func(function() {/*
  #r "System.Drawing.dll"
  using System.Drawing;

  async (src) => {
    await Task.Run(async () => {
      Image.FromFile(src).Save((string)src + ".jpg", ImageFormat.Jpeg);
    });
    return null;
  }
*/});

convertImageToJpg('.\\edge.png', function (error) { ... });
         
see full code

Express.js handler in C#

Plug in .NET OWIN apps as connect middleware or express handlers


var owin = require('connect-owin')
    , express = require('express');

var app = express();
app.get('/net', owin('Edge.Samples.dll'));
app.get('/node', function (req, res) {
    res.send(200, 'Hello from JavaScript!');
});

app.listen(3000);
         
bbaia/connect-owin

Call legacy SOAP services from Node.js

Use WCF to call SOAP services


var edge = require('edge');

var kg2pound = edge.func('soap.csx');

kg2pound(123, function (error, result) { ... });
         
see full code

Call legacy SOAP services from Node.js, continued

Use WCF proxy to call a SOAP service; soap.csx:


// ...

public class Startup {
    public async Task<object> Invoke(double kilograms) {
        var client = new ConvertWeightsSoapClient(
            new BasicHttpBinding(),
            new EndpointAddress(
                "http://www.webservicex.net/ConvertWeight.asmx"));

        return await client.ConvertWeightAsync(
              kilograms, WeightUnit.Kilograms, WeightUnit.PoundsTroy);
    }
}
         
see full code

Intermission

Before Act II begins, you may enjoy a moment with art
Salvador Dali, Sleep, 1937

Act II

Where .NET scripts Node.js in-process (NEW!)

docs

Script Node.js from .NET

Asynchronously call Node.js functions in .NET


using EdgeJs;

var func = Edge.Func(@"
    return function (data, cb) {
        cb(null, 'Node.js ' + process.version + ' welcomes ' + data);
    }
");

Console.WriteLine(await func(".NET"));
         
more

...In console applications...

Script Node.js in .NET console applications

more

...In ASP.NET web applications...

Script Node.js in .NET console applications

more

Use built-in modules

Core Node.js modules are included with Edge.js


using EdgeJs;

async void Start() {
    var createHttpServer = Edge.Func(@"
        var http = require('http');

        return function (port, cb) {
            http.createServer(function (req, res) {
                res.end('Hello, world! ' + new Date());
            }).listen(port, cb);
        };
    ");

    await createHttpServer(8080);
}
         
more

Use NPM modules

Additional Node.js modules can be installed from NPM

> npm install ws
...
ws@0.4.31 node_modules\ws

using EdgeJs;

var createWebSocketServer = Edge.Func(@"
    var WebSocketServer = require('ws').Server;

    return function (port, cb) {
        var wss = new WebSocketServer({ port: port });
        wss.on('connection', function (ws) { /* ... */ });
        cb();
    };
");
         
more

Expose Node.js state

Wrap Node.js state in a closure


using EdgeJs;

var increment = Edge.Func(@"
    var current = 0;

    return function (data, callback) {
        current += data;
        callback(null, current);
    }
");

Console.WriteLine(await increment(4)); // outputs 4
Console.WriteLine(await increment(7)); // outputs 11
         
more

Export Node.js functions


using EdgeJs;

var createServer = Edge.Func(@"
    var http = require('http');

    return function (port, cb) {
        var server = http.createServer(...);
        cb(null, function (data, cb) {
            server.close(); cb();
        });
    }
");

var closeServer = (Func<object,Task<object>>)await createServer(8080);
// ...
await closeServer(null);
         
more

.NET handlers for Node.js events


var onMessage = async (msg) => { return ((string)msg).ToUpper(); };

var createWebSocketServer = Edge.Func(@"
  var WebSocketServer = require('ws').Server;

  return function (params, cb) {
    var wss = new WebSocketServer({ port: params.port });
    wss.on('connection', function (ws) { 
      ws.on('message', function (msg) {
          params.onMessage(msg, function (error, res) { ws.send(res); });
      });
    });
    cb();
  };");

await createWebSocketServer(new { port = 8080, onMessage = onMessage });
         
more

Edge.js

Brought to you by Tomasz Janczuk / @tjanczuk

github.com/tjanczuk/edge

npm install edge

Collaboration welcome

What will you build?

Fork me on GitHub
Follow @tjanczuk