UNPKG

edge-js

Version:

Edge.js: run .NET and Node.js in-process on Windows, Mac OS, and Linux

1,379 lines (1,013 loc) 81.8 kB
# Edge.js: .NET and Node.js in-process <!-- [![Build status][appveyor-image]][appveyor-url] --> [![Actions Status][github-img]][github-url] [![CircleCI](https://dl.circleci.com/status-badge/img/gh/agracio/edge-js.svg?style=shield )](https://dl.circleci.com/status-badge/redirect/gh/agracio/edge-js/tree/master) [![Git Issues][issues-img]][issues-url] [![Closed Issues][closed-issues-img]][closed-issues-url] <!-- [![NPM Downloads][downloads-img]][downloads-url] --> <!-- ![Node version](https://img.shields.io/node/v/edge-js.svg) --> <!-- [![deps status][dependencies-img]][dependencies-url] --> <!--[![MIT license][license-img]][license-url] --> <!-- [![Codacy Badge][codacy-img]][codacy-url] --> ----- ### This library is based on https://github.com/tjanczuk/edge all credit for original work goes to Tomasz Janczuk. ------ ## Overview **Edge.js allows you to run Node.js and .NET code in one process on Windows, macOS, and Linux** You can call .NET functions from Node.js and Node.js functions from .NET. Edge.js takes care of marshaling data between CLR and V8. Edge.js also reconciles threading models of single-threaded V8 and multi-threaded CLR. Edge.js ensures correct lifetime of objects on V8 and CLR heaps. The CLR code can be pre-compiled or specified as C#, F#, Python (IronPython), or PowerShell source: Edge.js can compile CLR scripts at runtime. Edge can be extended to support other CLR languages or DSLs. ![Edge.js interop model](https://f.cloud.github.com/assets/822369/234085/b305625c-8768-11e2-8de0-e03ae98e7249.PNG) Edge.js provides an asynchronous, in-process mechanism for interoperability between Node.js and .NET. You can use this mechanism to: * script Node.js from a .NET application on Windows using .NET Framework * script C# from a Node.js application on Windows, macOS, and Linux using .NET Framework/.NET Core * use CLR multi-threading from Node.js for CPU intensive work [more...](http://tomasz.janczuk.org/2013/02/cpu-bound-workers-for-nodejs.html) * write native extensions to Node.js in C# instead of C/C++ * integrate existing .NET components into Node.js applications * access MS SQL from Node.js using ADO.NET * script F# from Node.js * script Powershel from Node.js * script Python (IronPython) from Node.js Read more about the background and motivations of the project [here](http://tomasz.janczuk.org/2013/02/hosting-net-code-in-nodejs-applications.html). ## Updates * Support for new versions of Node.Js. * Support for .NET Core 3.1 - 9.x on Windows/Linux/macOS. * Fixes AccessViolationException when running Node.js code from C# [PR #573](https://github.com/tjanczuk/edge/pull/573). * Fixes StackOverflowException [PR #566](https://github.com/tjanczuk/edge/pull/566) that occurs when underlying C# code throws complex exception. * Fixes issues [#469](https://github.com/tjanczuk/edge/issues/469), [#713](https://github.com/tjanczuk/edge/issues/713) * Other PRs: [PR #725](https://github.com/tjanczuk/edge/pull/725), [PR #640](https://github.com/tjanczuk/edge/pull/640) * Multiple bug fixes and improvements to the original code. ---- ### NPM package [`edge-js`](https://www.npmjs.com/package/edge-js) ### NuGet package [`EdgeJs`](https://www.nuget.org/packages/EdgeJs) ---- ## Electron For use with Electron **[`electron-edge-js`](https://github.com/agracio/electron-edge-js)** ## VS Code extensions VS Code uses Electron shell, to write extensions for it using Edge.js use **[`electron-edge-js`](https://github.com/agracio/electron-edge-js)** ## Quick start Sample app that shows how to work with .NET Core using inline code and compiled C# libraries. https://github.com/agracio/edge-js-quick-start ## Pre-requisites - Windows: [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-microsoft-visual-c-redistributable-version) ## Node.js Support ### edge-js support policy - Windows supports 4 latest LTS or candidate LTS releases (even numbered). - Windows supports up to 1 "Current" (odd numbered) release and drops it when superseeded by new LTS candidate. - macOS comes precompiled with same releases as Windows. When using Node.js version that is not pre-compiled `edge-js` binaries will be compiled during `npm install` using `node-gyp`. - Linux will will always compile `edge-js` binaries during `npm install` using `node-gyp`. ### Windows | Version | x86 | x64 | arm64 | |---------|--------------------|--------------------|--------------------| | 18.x | :heavy_check_mark: | :heavy_check_mark: | :x: | | 20.x | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | 22.x | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | 24.x | :x: | :heavy_check_mark: | :heavy_check_mark: | ### macOS binaries pre-compiled for | Version | x64 | arm64 (M1+) | |---------|--------------------|--------------------| | 18.x | :heavy_check_mark: | :heavy_check_mark: | | 20.x | :heavy_check_mark: | :heavy_check_mark: | | 22.x | :heavy_check_mark: | :heavy_check_mark: | | 24.x | :heavy_check_mark: | :heavy_check_mark: | #### Supports | Version | x64 | arm64 (M1+) | |-------------|--------------------|--------------------| | 16.x - 24.x | :heavy_check_mark: | :heavy_check_mark: | ### Linux | Version | x64 | arm64 | |-------------|--------------------|--------------------| | 14.x - 24.x | :heavy_check_mark: | :heavy_check_mark: | Other Linux architectures might work but have not been tested. ## Scripting CLR from Node.js and Node.js from CRL <table> <tr><th>Script CLR from Node.js </th><th>Script Node.js from CLR</th></tr> <tr><td> | | .NET 4.5 | Mono 6.x | CoreCLR | |---------|--------------------|---------------------|--------------------| | Windows | :heavy_check_mark: | :heavy_check_mark:* | :heavy_check_mark: | | Linux | :x: | :heavy_check_mark:* | :heavy_check_mark: | | macOS | :x: | :heavy_check_mark: | :heavy_check_mark: | </td><td> | | .NET 4.5 | Mono | CoreCLR | |---------|--------------------|------|---------| | Windows | :heavy_check_mark: | :x: | :x: | | Linux | :x: | :x: | :x: | | macOS | :x: | :x: | :x: | </td></tr> </table> ## Mono Mono is no longer actively supported. Existing code will remain In Edge.Js but focus will be on .NET Core. `*` Edge.js does not have a flag to force it to run in Mono environment on Windows, it would only be possible to run if you have Windows without .NET Framework present. `*` On Linux there is a known bug that will throw exception when executing C# code under certain conditions. Use one of the following options. 1. Set `LD_PRELOAD` env variable before running Edge.js with Mono ```bash EXPORT LD_PRELOAD="libmono-2.0.so libmonosgen-2.0.so libstdc++.so.6" ``` 2. Set `LD_PRELOAD` in your Node.js app entry point using Javascript ```js Object.assign(process.env, { // Work around Mono problem: undefined symbol: mono_add_internal_call_with_flags LD_PRELOAD: 'libmono-2.0.so libmonosgen-2.0.so libstdc++.so.6', }); ``` Source: https://github.com/mono/mono/issues/17079#issuecomment-1195918648 Edge.js uses this code in test setup: https://github.com/agracio/edge-js/blob/8bab03aca2abaea66e78c3af2f29e35ac2fcb8cf/tools/test.js#L19-L24 ## Node.js application packaging When packaging your application using Webpack make sure that `edge-js` is specified as external module. ### Webpack ```js module.exports = { target: 'node', externals: { 'edge-js': 'commonjs2 edge-js', 'edge-cs': 'commonjs2 edge-cs', }, node: { __dirname: true, __filename: true, }, } ``` ### Next.js `next.config.js` ```js const nextConfig = { serverExternalPackages: ['edge-js'], } module.exports = nextConfig ``` `next.config.ts` ```typescript const nextConfig: NextConfig = { serverExternalPackages: ['edge-js'], }; export default nextConfig; ``` ## Node.js single executable application packaging **[`edge-js-pkg`](https://github.com/agracio/edge-js-pkg)** ## Additional languages support ### SQL scripting Provides simple access to SQL without the need to write separate C# code. | Framework | Platform | NPM Package | Language code | Documentation | |-----------|----------|-------------|---------------|----------------------------------------------------------------------| | .NET 4.5 | Windows | `edge-sql` | `sql` | [Script SQL in Node.js](https://github.com/agracio/edge-sql) :link: | | CoreCLR | Any | `edge-sql` | `sql` | [Script SQL in Node.js](https://github.com/agracio/edge-sql) :link: | ### Python (IronPython) scripting **NOTE** This functionality requires IronPython 3.4 | Framework | Platform | NPM Package | Language code | Documentation | |-----------|----------|-------------|---------------|-----------------------------------------------------------------------| | .NET 4.5 | Windows | `edge-py` | `py` | [Script Python in Node.js](https://github.com/agracio/edge-py) :link: | | CoreCLR | Any | `edge-py` | `py` | [Script Python in Node.js](https://github.com/agracio/edge-py) :link: | ### PowerShell scripting **NOTE** CoreCLR requires dotnet 8 | Framework | Platform | NPM Package | Language code | Documentation | |-----------|----------|-------------|---------------|---------------------------------------------------------------------------| | .NET 4.5 | Windows | `edge-ps` | `ps` | [Script PowerShell in Node.js](https://github.com/agracio/edge-ps) :link: | | CoreCLR | Windows | `edge-ps` | `ps` | [Script PowerShell in Node.js](https://github.com/agracio/edge-ps) :link: | ### F# scripting | Framework | Platform | NPM Package | Language code | Documentation | |-----------|----------|-------------|---------------|-------------------------------------------------------------------| | .NET 4.5 | Windows | `edge-fs` | `fs` | [Script F# in Node.js](https://github.com/agracio/edge-fs) :link: | | CoreCLR | Windows | `edge-fs` | `fs` | [Script F# in Node.js](https://github.com/agracio/edge-fs) :link: | --------- ## How to use ### [Scripting CLR from Node.js](#scripting-clr-from-nodejs) - full documentation ### [Scripting Node.js from CLR](#how-to-integrate-nodejs-code-into-clr-code) - full documentation ### Scripting CLR from Node.js sample app https://github.com/agracio/edge-js-quick-start ---- ### Short guide - [Inline C# code](#inline-c-code) - [Passing parameters](#passing-parameters) - [Using C# class](#using-c-class) - [Using compiled dll](#using-compiled-assembly) - [Using CoreCLR](#coreclr) - [Executing synchronously without function callback](#executing-synchronously-without-function-callback) - [Using promises/async](#promises) - [Scripting Node.js from CLR](#scripting-nodejs-from-clr) - [Docker](#docker) ### Inline C# code #### ES5 ```js var edge = require('edge-js'); var helloWorld = edge.func(function () {/* async (input) => { return ".NET Welcomes " + input.ToString(); } */}); helloWorld('JavaScript', function (error, result) { if (error) throw error; console.log(result); }); ``` #### ES6 with templated strings ```js var edge = require('edge-js'); var helloWorld = edge.func(` async (input) => { return ".NET Welcomes " + input.ToString(); } `); helloWorld('JavaScript', function (error, result) { if (error) throw error; console.log(result); }); ``` ### Passing parameters ```js var edge = require('edge-js'); var helloWorld = edge.func(function () {/* async (dynamic input) => { return "Welcome " + input.name + " " + input.surname; } */}); helloWorld({name: 'John', surname: 'Smith'}, function (error, result) { if (error) throw error; console.log(result); }); ``` ### Using C# class ```js var getPerson = edge.func({ source: function () {/* using System.Threading.Tasks; using System; public class Person { public Person(string name, string email, int age) { Id = Guid.NewGuid(); Name = name; Email = email; Age = age; } public Guid Id {get;set;} public string Name {get;set;} public string Email {get;set;} public int Age {get;set;} } public class Startup { public async Task<object> Invoke(dynamic input) { return new Person(input.name, input.email, input.age); } } */} }); getPerson({name: 'John Smith', email: 'john.smith@myemailprovider', age: 35}, function(error, result) { if (error) throw error; console.log(result); }); ``` **When using inline C# class code must include** ```cs public class Startup { public async Task<object> Invoke(object|dynamic input) { // code // return results } } ``` ### Using compiled assembly ```cs // People.cs using System; namespace People { public class Person { public Person(string name, string email, int age) { Id = Guid.NewGuid(); Name = name; Email = email; Age = age; } public Guid Id {get;} public string Name {get;} public string Email {get;} public int Age {get;} } } // EdgeJsMethods.cs using System.Threading.Tasks; using People; namespace EdgeJsMethods { class Methods { public async Task<object> GetPerson(dynamic input) { return await Task.Run(() => new Person(input.name, input.email, input.age)); } } } ``` ```js var edge = require('edge-js'); var getPerson = edge.func({ assemblyFile: myDll, // path to compiled .dll typeName: 'EdgeJsMethods.Methods', methodName: 'GetPerson' }); getPerson({name: 'John Smith', email: 'john.smith@myemailprovider', age: 35}, function(error, result) { if (error) throw error; console.log(result); }); ``` ### Edge.js C# method must have the following signature ```cs public async Task<object> MyMethod(object|dynamic input) { //return results sync/async; } ``` #### CoreCLR * If not set Edge.js will run as .NET 4.5 on Windows. * On macOS and Linux Edge.js will default to Mono if it is installed otherwise will run as CoreCLR. * Can be set using `js` code below or as an environment variable `SET EDGE_USE_CORECLR=1` or `EXPORT EDGE_USE_CORECLR=1` depending on your platform. * Must be set before `var edge = require('edge-js');` ```js // set this variable before // var edge = require('edge-js'); process.env.EDGE_USE_CORECLR=1 var edge = require('edge-js'); var helloWorld = edge.func(function () {/* async (input) => { return ".NET Welcomes " + input.ToString(); } */}); helloWorld('JavaScript', function (error, result) { if (error) throw error; console.log(result); }); ``` ### Executing synchronously without function callback If your C# implementation will complete synchronously you can call this function as any synchronous JavaScript function as follows: ```js var edge = require('edge-js'); var helloWorld = edge.func(function () {/* async (input) => { return ".NET Welcomes " + input.ToString(); } */}); var result = helloWorld('JavaScript', true); ``` Calling C# asynchronous implementation as a synchronous JavaScript function will fail ```js var edge = require('edge-js'); var helloWorld = edge.func(function () {/* async (input) => { return await Task.Run(() => ".NET Welcomes " + input.ToString()); } */}); // sync call will throw exception var result = helloWorld('JavaScript', true); ``` ### Promises ```js var func = edge.func(function () {/* async (dynamic input) => { return "Welcome " + input.name + " " + input.surname; } */}); function helloWorld(){ return new Promise<T>((resolve, reject) =>{ func({name: 'John', surname: 'Smith'}, function (error, result) { if(error) reject(error); else resolve(result); }); }); } ``` ---- ### Scripting Node.js from CLR ```cs using System; using System.Threading.Tasks; using EdgeJs; class Program { public static async Task Start() { var func = Edge.Func(@" return function (data, callback) { callback(null, 'Node.js welcomes ' + data); } "); Console.WriteLine(await func(".NET")); } static void Main(string[] args) { Start().Wait(); } } ``` ### More examples in tests [DoubleEdge.cs](https://github.com/agracio/edge-js/blob/master/test/double/double_test/DoubleEdge.cs) ---- ### Docker **Dockerfile: [Dockerfile](https://github.com/agracio/edge-js/blob/master/Dockerfile)** **Docker Hub image: [agracio/ubuntu-node-netcore](https://hub.docker.com/repository/docker/agracio/ubuntu-node-netcore)** * Based on Ununtu 22.04 * User directory **`devvol`** #### Pre-installed packages * Node.js 20 * dotnet 8 * git * build tools * sudo, curl, wget * node-gyp #### Using container * Run interactive starting in `devvol`, set `EDGE_USE_CORECLR=1` at container level * Git clone `edge-js` and enter cloned repo directory * `npm install` * Run tests ``` docker run -w /devvol -e EDGE_USE_CORECLR=1 -it agracio/ubuntu-node-netcore:latest git clone https://github.com/agracio/edge-js.git && cd edge-js npm i npm test ``` --------- <br/>Edge.js readme ============================== ### :exclamation: Some of the original Edge.js documentation is outdated :exclamation: ## What problems does Edge.js solve? > Ah, whatever problem you have. If you have this problem, this solves it. *[--Scott Hanselman (@shanselman)](https://twitter.com/shanselman/status/461532471037677568)* ## Before you dive in Read the [Edge.js introduction on InfoQ](http://www.infoq.com/articles/the_edge_of_net_and_node). Listen to the [Edge.js podcast on Herdingcode](http://herdingcode.com/herding-code-166-tomasz-janczuk-on-edge-js/). ## Contents [Scripting CLR from Node.js](#scripting-clr-from-nodejs) &nbsp;&nbsp;&nbsp;&nbsp;[What you need](#what-you-need) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Windows](#windows-setup) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Linux](#linux-setup) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[OSX](#osx-setup) &nbsp;&nbsp;&nbsp;&nbsp;[How to: C# hello, world](#how-to-c-hello-world) &nbsp;&nbsp;&nbsp;&nbsp;[How to: integrate C# code into Node.js code](#how-to-integrate-c-code-into-nodejs-code) &nbsp;&nbsp;&nbsp;&nbsp;[How to: specify additional CLR assembly references in C# code](#how-to-specify-additional-clr-assembly-references-in-c-code) &nbsp;&nbsp;&nbsp;&nbsp;[How to: marshal data between C# and Node.js](#how-to-marshal-data-between-c-and-nodejs) &nbsp;&nbsp;&nbsp;&nbsp;[How to: call Node.js from C#](#how-to-call-nodejs-from-c) &nbsp;&nbsp;&nbsp;&nbsp;[How to: export C# function to Node.js](#how-to-export-c-function-to-nodejs) &nbsp;&nbsp;&nbsp;&nbsp;[How to: script Python in a Node.js application](#how-to-script-python-in-a-nodejs-application) &nbsp;&nbsp;&nbsp;&nbsp;[How to: script PowerShell in a Node.js application](#how-to-script-powershell-in-a-nodejs-application) &nbsp;&nbsp;&nbsp;&nbsp;[How to: script F# in a Node.js application](#how-to-script-f-in-a-nodejs-application) &nbsp;&nbsp;&nbsp;&nbsp;[How to: script Lisp in a Node.js application](#how-to-script-lisp-in-a-nodejs-application) &nbsp;&nbsp;&nbsp;&nbsp;[How to: script T-SQL in a Node.js application](#how-to-script-t-sql-in-a-nodejs-application) &nbsp;&nbsp;&nbsp;&nbsp;[How to: support for other CLR languages](#how-to-support-for-other-clr-languages) &nbsp;&nbsp;&nbsp;&nbsp;[How to: exceptions](#how-to-exceptions) &nbsp;&nbsp;&nbsp;&nbsp;[How to: app.config](#how-to-appconfig) &nbsp;&nbsp;&nbsp;&nbsp;[How to: debugging](#how-to-debugging) &nbsp;&nbsp;&nbsp;&nbsp;[Performance](#performance) &nbsp;&nbsp;&nbsp;&nbsp;[Building on Windows](#building-on-windows) &nbsp;&nbsp;&nbsp;&nbsp;[Building on OSX](#building-on-osx) &nbsp;&nbsp;&nbsp;&nbsp;[Building on Linux](#building-on-linux) &nbsp;&nbsp;&nbsp;&nbsp;[Running tests](#running-tests) [Scripting Node.js from CLR](#scripting-nodejs-from-clr) &nbsp;&nbsp;&nbsp;&nbsp;[What you need](#what-you-need-1) &nbsp;&nbsp;&nbsp;&nbsp;[How to: Node.js hello, world](#how-to-nodejs-hello-world) &nbsp;&nbsp;&nbsp;&nbsp;[How to: integrate Node.js into CLR code](#how-to-integrate-nodejs-code-into-clr-code) &nbsp;&nbsp;&nbsp;&nbsp;[How to: use Node.js built-in modules](#how-to-use-nodejs-built-in-modules) &nbsp;&nbsp;&nbsp;&nbsp;[How to: use external Node.js modules](#how-to-use-external-nodejs-modules) &nbsp;&nbsp;&nbsp;&nbsp;[How to: handle Node.js events in .NET](#how-to-handle-nodejs-events-in-net) &nbsp;&nbsp;&nbsp;&nbsp;[How to: expose Node.js state to .NET](#how-to-expose-nodejs-state-to-net) &nbsp;&nbsp;&nbsp;&nbsp;[How to: use Node.js in ASP.NET application](#how-to-use-nodejs-in-aspnet-web-applications) &nbsp;&nbsp;&nbsp;&nbsp;[How to: debug Node.js code running in a CLR application](#how-to-debug-nodejs-code-running-in-a-clr-application) &nbsp;&nbsp;&nbsp;&nbsp;[Building Edge.js NuGet package](#building-edgejs-nuget-package) &nbsp;&nbsp;&nbsp;&nbsp;[Running tests of scripting Node.js in C#](#running-tests-of-scripting-nodejs-in-c) [Use cases and other resources](#use-cases-and-other-resources) [Contribution and derived work](#contribution-and-derived-work) ## Scripting CLR from Node.js If you are writing a Node.js application, this section explains how you include and run CLR code in your app. It works on Windows, MacOS, and Linux. ### What you need Edge.js runs on Windows, Linux, and OSX and requires supported version of Node.js 8.x, 7.x, 6.x, as well as .NET Framework 4.5 (Windows), Mono 4.2.4 (OSX, Linux), or .NET Core 1.0.0 Preview 2 (Windows, OSX, Linux). **NOTE** there is a known issue with Mono after 4.2.4 that will be addressed in Mono 4.6. #### Windows setup * Supported Node.js version * [.NET 4.6.2](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net462) and/or [.NET Core](https://www.microsoft.com/net/core) * [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-microsoft-visual-c-redistributable-version) * to use Python, you also need [IronPython 3.4 or later](https://ironpython.net/download/) * to use F#, read [Dave Thomas blog post](https://web.archive.org/web/20160323224525/http://7sharpnine.com/posts/i-node-something/) If you have both desktop CLR and .NET Core installed, read [using .NET Core](#using-net-core) for how to configure Edge to use one or the other. #### Linux setup * Supported Node.js version * Mono and/or [.NET Core](https://www.microsoft.com/net/core) or Mono * Follow [Linux setup instructions](#building-on-linux) #### OSX setup * Supported Node.js version * Mono and/or [.NET Core](https://www.microsoft.com/net/core) * Follow [OSX setup instructions](#building-on-osx) ### How to: C# hello, world Follow setup instructions [for your platform](#what-you-need). Install edge: ``` npm install edge-js ``` In your server.js: ```javascript var edge = require('edge-js'); var helloWorld = edge.func(function () {/* async (input) => { return ".NET Welcomes " + input.ToString(); } */}); helloWorld('JavaScript', function (error, result) { if (error) throw error; console.log(result); }); ``` Run and enjoy: ``` $>node server.js .NET welcomes JavaScript ``` If you want to use .NET Core as your runtime and are running in a dual runtime environment (i.e. Windows with .NET 4.5 installed as well or Linux with Mono installed), you will need to tell edge to use .NET Core by setting the `EDGE_USE_CORECLR` environment variable: ``` $>EDGE_USE_CORECLR=1 node server.js .NET Welcomes JavaScript ``` ### How to: integrate C# code into Node.js code Edge provides several ways to integrate C# code into a Node.js application. Regardless of the way you choose, the entry point into the .NET code is normalized to a `Func<object,Task<object>>` delegate. This allows Node.js code to call .NET asynchronously and avoid blocking the Node.js event loop. Edge provides a function that accepts a reference to C# code in one of the supported representations, and returns a Node.js function which acts as a JavaScript proxy to the `Func<object,Task<object>>` .NET delegate: ```javascript var edge = require('edge-js'); var myFunction = edge.func(...); ``` The function proxy can then be called from Node.js like any asynchronous function: ```javascript myFunction('Some input', function (error, result) { //... }); ``` Alternatively, if you know the C# implementation will complete synchronously given the circumstances, you can call this function as any synchronous JavaScript function as follows: ```javascript var result = myFunction('Some input', true); ``` The `true` parameter instead of a callback indicates that Node.js expects the C# implementation to complete synchronously. If the CLR function implementation does not complete synchronously, the call above will result in an exception. One representation of CLR code that Edge.js accepts is C# source code. You can embed C# literal representing a .NET async lambda expression implementing the `Func<object, Task<object>>` delegate directly inside Node.js code: ```javascript var add7 = edge.func('async (input) => { return (int)input + 7; }'); ``` In another representation, you can embed multi-line C# source code by providing a function with a body containing a multi-line comment. Edge extracts the C# code from the function body using regular expressions: ```javascript var add7 = edge.func(function() {/* async (input) => { return (int)input + 7; } */}); ``` Or if you use ES6 you can use [template strings](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/template_strings) to define a multiline string: ```javascript var add7 = edge.func(` async (input) => { return (int)input + 7; } `); ``` If your C# code is more involved than a simple lambda, you can specify entire class definition. By convention, the class must be named `Startup` and it must have an `Invoke` method that matches the `Func<object,Task<object>>` delegate signature. This method is useful if you need to factor your code into multiple methods: ```javascript var add7 = edge.func(function() {/* using System.Threading.Tasks; public class Startup { public async Task<object> Invoke(object input) { int v = (int)input; return Helper.AddSeven(v); } } static class Helper { public static int AddSeven(int v) { return v + 7; } } */}); ``` If your C# code grows substantially, it is useful to keep it in a separate file. You can save it to a file with `*.csx` or `*.cs` extension, and then reference from your Node.js application: ```javascript var add7 = edge.func(require('path').join(__dirname, 'add7.csx')); ``` If you integrate C# code into your Node.js application by specifying C# source using one of the methods above, edge will compile the code on the fly. If you prefer to pre-compile your C# sources to a CLR assembly, or if your C# component is already pre-compiled, you can reference a CLR assembly from your Node.js code. In the most generic form, you can specify the assembly file name, the type name, and the method name when creating a Node.js proxy to a .NET method: ```javascript var clrMethod = edge.func({ assemblyFile: 'My.Edge.Samples.dll', typeName: 'Samples.FooBar.MyType', methodName: 'MyMethod' // This must be Func<object,Task<object>> }); ``` If you don't specify methodName, `Invoke` is assumed. If you don't specify typeName, a type name is constructed by assuming the class called `Startup` in the namespace equal to the assembly file name (without the `.dll`). In the example above, if typeName was not specified, it would default to `My.Edge.Samples.Startup`. The assemblyFile is relative to the working directory. If you want to locate your assembly in a fixed location relative to your Node.js application, it is useful to construct the assemblyFile using `__dirname`. If you are using .NET Core, assemblyFile can also be a project name or NuGet package name that is specified in your `project.json` or `.deps.json` dependency manifest. You can also create Node.js proxies to .NET functions specifying just the assembly name as a parameter: ```javascript var clrMethod = edge.func('My.Edge.Samples.dll'); ``` In that case the default typeName of `My.Edge.Samples.Startup` and methodName of `Invoke` is assumed as explained above. ### How to: specify additional CLR assembly references in C# code When you provide C# source code and let edge compile it for you at runtime, edge will by default reference only mscorlib.dll and System.dll assemblies. If you're using .NET Core, we automatically reference the most recent versions of the System. Runtime, System.Threading.Tasks, and the compiler language packages, like Microsoft.CSharp. In applications that require additional assemblies you can specify them in C# code using a special hash pattern, similar to Roslyn. For example, to use ADO.NET you must reference System.Data.dll: #### NOTE: `#r` and `references: [ 'MyDll.dll' ]` references only work when using .NET Framework 4.5 ```javascript var add7 = edge.func(function() {/* #r "System.Data.dll" using System.Data; using System.Threading.Tasks; public class Startup { public async Task<object> Invoke(object input) { // ... } } */}); ``` If you prefer, instead of using comments you can specify references by providing options to the `edge.func` call: ```javascript var add7 = edge.func({ source: function() {/* using System.Data; using System.Threading.Tasks; public class Startup { public async Task<object> Invoke(object input) { // ... } } */}, references: [ 'System.Data.dll' ] }); ``` If you are using .NET Core and are using the .NET Core SDK and CLI, you must have a `project.json` file (specification [here](https://github.com/aspnet/Home/wiki/Project.json-file)) that specifies the dependencies for the application. This list of dependencies must also include the [Edge.js runtime package](https://www.nuget.org/packages/EdgeJs/) and, if you need to be able to dynamically compile your code, the package(s) for the compilers that you plan to use, like [Edge.js.CSharp](https://www.nuget.org/packages/Edge.js.CSharp/). You must have run the `dotnet restore` (to restore the dependencies) and `dotnet build` (to build your project and generate the dependency manifest) commands in that project's directory to generate a `.deps.json` file under `bin/[configuration]/[framework]`, i.e. `bin/Release/netstandard1.6/MyProject.deps.json`. This `.deps.json` file must either be in the current working directory that `node` is executed in or you must specify its directory by setting the `EDGE_APP_ROOT` environment variable. For example, if for a `netstandard1.6` project in the `c:\DotNet\MyProject` directory, you would run something like: ``` set EDGE_APP_ROOT=c:\DotNet\MyProject\bin\Release\netstandard1.6 node app.js ``` When calling a compiled assembly, Edge.js supports running with only the .NET runtime installed (and not the SDK or CLI). In this case a `appname.runtimeconfig.json` has to be created when building the project an present in your `EDGE_APP_ROOT` directory. `<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>`should be present under `<PropertyGroup>` in your `.csproj`file to create this file. Edge.js also supports running published .NET Core applications on servers that do not have the .NET Core SDK and CLI or .NET runtime installed, which is a common scenario in production environments. To do so, the `.csproj` for your application should meet the following requirements: 1. It should target the `netcoreapp2.x` or `netstandard2.0` framework moniker. 2. It should reference `Microsoft.NETCore.DotNetHost` and `Microsoft.NETCore.DotNetHostPolicy`. This is required so that the publish process can provide all the native libraries required to create a completely standalone version of your application. 3. `<PreserveCompilationContext>true</PreserveCompilationContext>` and `<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>` should be present under `<PropertyGroup>`. You can add an empty `Main()` implementation to your project to accommodate it; this method will not be called, but is just a requirement in order for `dotnet publish` to generate a completely standalone app. On your development machine, you would run `dotnet publish -r [target runtime for your production server]` (i.e. `dotnet publish -r ubuntu.14.04-x64`) to aggregate the package assemblies and native libraries necessary to run your application. You can copy the contents of the publish directory up to your SDK- and CLI-less server and use them directly in Edge.js by setting the `EDGE_APP_ROOT` environment variable to the directory on the server that you copied the published application to. ### How to: marshal data between C# and Node.js Edge.js can marshal any JSON-serializable value between .NET and Node.js (although JSON serialization is not used in the process). Edge also supports marshalling between Node.js `Buffer` instance and a CLR `byte[]` array to help you efficiently pass binary data. You can call .NET from Node.js and pass in a complex JavaScript object as follows: ```javascript var dotNetFunction = edge.func('Edge.Sample.dll'); var payload = { anInteger: 1, aNumber: 3.1415, aString: 'foo', aBoolean: true, aBuffer: new Buffer(10), anArray: [ 1, 'foo' ], anObject: { a: 'foo', b: 12 } }; dotNetFunction(payload, function (error, result) { }); ``` In .NET, JavaScript objects are represented as dynamics (which can be cast to `IDictionary<string,object>` if desired), JavaScript arrays as `object[]`, and JavaScript `Buffer` as `byte[]`. Scalar JavaScript values have their corresponding .NET types (`int`, `double`, `bool`, `string`). Here is how you can access the data in .NET: ```c# using System.Threading.Tasks; public class Startup { public async Task<object> Invoke(dynamic input) { int anInteger = (int)input.anInteger; double aNumber = (double)input.aNumber; string aString = (string)input.aString; bool aBoolean = (bool)input.aBoolean; byte[] aBuffer = (byte[])input.aBuffer; object[] anArray = (object[])input.anArray; dynamic anObject = (dynamic)input.anObject; return null; } } ``` Similar type marshalling is applied when .NET code passes data back to Node.js code. In .NET code you can provide an instance of any CLR type that would normally be JSON serializable, including domain specific types like `Person` or anonymous objects. For example: ```c# using System.Threading.Tasks; public class Person { public int anInteger = 1; public double aNumber = 3.1415; public string aString = "foo"; public bool aBoolean = true; public byte[] aBuffer = new byte[10]; public object[] anArray = new object[] { 1, "foo" }; public object anObject = new { a = "foo", b = 12 }; } public class Startup { public async Task<object> Invoke(dynamic input) { Person person = new Person(); return person; } } ``` In your Node.js code that invokes this .NET method you can display the result object that the callback method receives: ```javascript var edge = require('edge-js'); var getPerson = edge.func(function () {/* using System.Threading.Tasks; public class Person { public int anInteger = 1; public double aNumber = 3.1415; public string aString = "foo"; public bool aBoolean = true; public byte[] aBuffer = new byte[10]; public object[] anArray = new object[] { 1, "foo" }; public object anObject = new { a = "foo", b = 12 }; } public class Startup { public async Task<object> Invoke(dynamic input) { Person person = new Person(); return person; } } */}); getPerson(null, function (error, result) { if (error) throw error; console.log(result); }); ``` Passing this .NET object to Node.js generates a JavaScript object as follows: ``` $>node sample.js { anInteger: 1, aNumber: 3.1415, aString: 'foo', aBoolean: true, aBuffer: <Buffer 00 00 00 00 00 00 00 00 00 00>, anArray: [ 1, 'foo' ], anObject: { a: 'foo', b: 12 } } ``` When data is marshalled from .NET to Node.js, no checks for circular references are made. They will typically result in stack overflows. Make sure the object graph you are passing from .NET to Node.js is a tree and does not contain any cycles. **WINDOWS ONLY** When marshalling strongly typed objects (e.g. Person) from .NET to Node.js, you can optionally tell Edge.js to observe the [System.Web.Script.Serialization.ScriptIgnoreAttribute](http://msdn.microsoft.com/en-us/library/system.web.script.serialization.scriptignoreattribute.aspx). You opt in to this behavior by setting the `EDGE_ENABLE_SCRIPTIGNOREATTRIBUTE` environment variable: ``` set EDGE_ENABLE_SCRIPTIGNOREATTRIBUTE=1 ``` Edge.js by default does not observe the ScriptIgnoreAttribute to avoid the associated performance cost. ### How to: call Node.js from C# In addition to marshalling data, edge can marshal proxies to JavaScript functions when invoking .NET code from Node.js. This allows .NET code to call back into Node.js. Suppose the Node.js application passes an `add` function to the .NET code as a property of an object. The function receives two numbers and returns the sum of them via the provided callback: ```javascript var edge = require('edge-js'); var addAndMultiplyBy2 = edge.func(function () {/* async (dynamic input) => { var add = (Func<object, Task<object>>)input.add; var twoNumbers = new { a = (int)input.a, b = (int)input.b }; var addResult = (int)await add(twoNumbers); return addResult * 2; } */}); var payload = { a: 2, b: 3, add: function (data, callback) { callback(null, data.a + data.b); } }; addAndMultiplyBy2(payload, function (error, result) { if (error) throw error; console.log(result); }); ``` The .NET code implements the addAndMultiplyBy2 function. It extracts the two numbers passed from Node.js, calls back into the `add` function exported from Node.js to add them, multiplies the result by 2 in .NET, and returns the result back to Node.js: ``` $>node sample.js 10 ``` The Node.js function exported from Node.js to .NET must follow the prescriptive async pattern of accepting two parameters: payload and a callback. The callback function accepts two parameters. The first one is the error, if any, and the second the result of the operation: ```javascript function (payload, callback) { var error; // must be null or undefined in the absence of error var result; // do something callback(error, result); } ``` The proxy to that function in .NET has the following signature: ```c# Func<object,Task<object>> ``` Using TPL in CLR to provide a proxy to an asynchronous Node.js function allows the .NET code to use the convenience of the `await` keyword when invoking the Node.js functionality. The example above shows the use of the `await` keyword when calling the proxy of the Node.js `add` method. ### How to: export C# function to Node.js Similarly to marshalling functions from Node.js to .NET, Edge.js can also marshal functions from .NET to Node.js. The .NET code can export a `Func<object,Task<object>>` delegate to Node.js as part of the return value of a .NET method invocation. For example: ```javascript var createHello = 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" ``` This mechanism in conjunction with a closure can be used to expose CLR class instances or CLR state in general to JavaScript. For example: ```javascript var createCounter = edge.func(function () {/* async (input) => { var k = (int)input; return (Func<object,Task<object>>)(async (i) => { return ++k; }); } */}); var counter = createCounter(12, true); // create counter with 12 as initial state console.log(counter(null, true)); // prints 13 console.log(counter(null, true)); // prints 14 ``` ### How to: script Python in a Node.js application **NOTE** This functionality requires IronPython and has been tested on Windows only. Edge.js enables you to run Python and Node.js in-process. In addition to [platform specific prerequisites](#what-you-need) you need [IronPython 3.4](http://ironpython.codeplex.com/releases/) to proceed. #### Hello, world Install edge and edge-py modules: ``` npm install edge-js npm install edge-py ``` In your server.js: ```javascript var edge = require('edge-js'); 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); }); ``` Run and enjoy: ``` $>node py.js Python welcomes Node.js ``` #### The interop model Your Python script must evaluate to a lambda expression that accepts a single parameter. The parameter represents marshalled input from the Node.js code. The return value of the lambda expression is passed back as the result to Node.js code. The Python script can contain constructs (e.g. Python functions) that are used in the closure of the lambda expression. The instance of the script with associated state is created when `edge.func` is called in Node.js. Each call to the function referes to that instance. The simplest *echo* Python script you can embed in Node.js looks like this: ```python lambda x: x ``` To say hello, you can use something like this: ```python lambda: x: "Hello, " + x ``` To maintain a running sum of numbers: ```python current = 0 def add(x): global current current = current + x return current lambda x: add(x) ``` #### Python in its own file You can reference Python script stored in a *.py file instead of embedding Python code in a Node.js script. In your hello.py file: ```python def hello(input): return "Python welcomes " + input lambda x: hello(x) ``` In your hello.js file: ```javascript var edge = require('edge-js'); var hello = edge.func('py', 'hello.py'); hello('Node.js', function (error, result) { if (error) throw error; console.log(result); }); ``` Run and enjoy: ``` $>node hello.js Python welcomes Node.js ``` #### To sync or to async, that is the question In the examples above Python script was executing asynchronously on its own thread without blocking the singleton V8 thread on which the Node.js event loop runs. This means your Node.js application remains responsive while the Python code executes in the background. If you know your Python code is non-blocking, or if you know what you are doing, you can tell Edge.js to execute Python code on the singleton V8 thread. This will improve performance for non-blocking Python scripts embedded in a Node.js application: ```javascript var edge = require('edge-js'); var hello = edge.func('py', { source: function () {/* def hello(input): return "Python welcomes " + input lambda x: hello(x) */}, sync: true }); console.log(hello('Node.js', true)); ``` The `sync: true` property in the call to `edge.func` tells Edge.js to execute Python code on the V8 thread as opposed to creating a new thread to run Python script on. The `true` parameter in the call to `hello` requests that Edge.js does in fact call the `hello` function synchronously, i.e. return the result as opposed to calling a callback function. ### How to: script PowerShell in a Node.js application **NOTE** This functionality only works on Windows. Edge.js enables you to run PowerShell and Node.js in-process on Windows. [Edge-PS](https://github.com/dfinke/edge-ps) connects the PowerShell ecosystem with Node.js. You need Windows, [Node.js](http://nodejs.org), [.NET 4.5](http://www.microsoft.com/en-us/download/details.aspx?id=30653), and [PowerShell 3.0](http://www.microsoft.com/en-us/download/details.aspx?id=34595) to proceed. ### Hello, world Install edge and edge-ps modules: ``` npm install edge-js npm install edge-ps ``` In your server.js: ```javascript var edge = require('edge-js'); var hello = edge.func('ps', function () {/* "PowerShell welcomes $inputFromJS on $(Get-Date)" */}); hello('Node.js', function (error, result) { if (error) throw error; console.log(result[0]); }); ``` Run and enjoy: ``` C:\testEdgeps>node server PowerShell welcomes Node.js on 05/04/2013 09:38:40 ``` #### Tapping into PowerShell's ecosystem Rather than embedding PowerShell directly, you can use PowerShell files, dot source them and even use *Import-Module*. What you can do in native PowerShell works in Node.js. #### Interop PowerShell and Python Here you can reach out to IronPython from PowerShell from within Node.js on Windows. This holds true for working with JavaScript frameworks and C#. ```javascript var edge = require('edge-js'); var helloPowerShell = edge.func('ps', function () {/* "PowerShell welcomes $inputFromJS" */}); var helloPython = edge.func('py', function () {/* def hello(input): return "Python welcomes " + input lambda x: hello(x) */}); helloPython('Node.js', function(error, result){ if(error) throw error; helloPowerShell(result, function(error, result){ if(error) throw error; console.log(result[0]); }); }); ``` ### How to: script F# in a Node.js application **NOTE** This functionality has not been tested on non-Windows platforms. This section is coming up. In the meantime please refer to [Dave Thomas blog post](http://7sharpnine.com/posts/i-node-something/). This has been validated on Windows only. ```javascript var edge = require('edge-js'); var helloFs = edge.func('fs', function () {/* fun input -> async { return "F# welcomes " + input.ToString() } */}); helloFs('Node.js', function (error, result) { if (error) throw error; console.log(result); }); ``` ### How to: script Lisp in a Node.js application **NOTE** This functionality has not been tested on non-Windows platforms. The [edge-lsharp](https://github.com/richorama/edge-lsharp) extension uses [LSharp](https://github.com/RobBlackwell/LSharp) to compile and run Lisp to .NET. Install edge and edge-lsharp modules: ``` npm install edge-js npm install edge-lsharp ``` In your server.js: ```javascript var edge = require('edge-js'); var fact = edge.func('lsharp', function(){/* ;; Factorial (def fact(n) (if (is n 0) 1 (* n (fact (- n 1))))) */}); fact([5], function(err, answer){ console.log(answer); // = 120 }); ``` An LSharp filename can be passed in instead of the Lisp string/comment: ```js var edge = require('edge-js'); var lisp = edge.func('lsharp', 'lisp-func.ls'); lisp(['arg1', 'arg2'], function(err, result){ }); ``` In Lisp you can specify either a function (as shown in the first example) or just return a value: ```js var edge = require('edge-js'); var lisp = edge.func('lsharp', '(+ 2 3)'); lisp([], function(err, answer){