using Edge.js to combine node.js with C#


Getting Familiar with Edge.js

To bring .NET and Node.js together, Edge.js has some pre-requisites. It runs on .NET 4.5, so you must have .NET 4.5 installed. As Node.js treats all I/O and Network calls as slower operations, Edge.js assumes that the .NET routine to be called is a slower operation and handles it asynchronously. The .NET function to be called has to be an asynchronous function as well.

The function is assigned to a delegate of type Func<object, Task<object>>. This means, the function is an asynchronous one that can take any type of argument and return any type of value. Edge.js takes care of converting the data from .NET type to JSON type and vice-versa. Because of this process of marshalling and unmarshalling, the .NET objects should not have circular references. Presence of circular references may lead to infinite loops while converting the data from one form to the other.

Hello World using Edge

Edge.js can be added to a Node.js application through NPM. Following is the command to install the package and save it to package.json file:

> npm install edge --save

The edge object can be obtained in a Node.js file as:

var edge = require('edge');

The edge object can accept inline C# code, read code from a .cs or .csx file, and also execute the code from a compiled dll. We will see all of these approaches.

To start with, let’s write a “Hello world” routine inline in C# and call it using edge. Following snippet defines the edge object with inline C# code:

var helloWorld = edge.func(function () {

/*async(input) => {

return "Hurray! Inline C# works with edge.js!!!";

}*/

});

The asynchronous and anonymous C# function passed in the above snippet is compiled dynamically before calling it. The inline code has to be passed as a multiline comment. The method edge.func returns a proxy function that internally calls the C# method. So the C# method is not called till now. Following snippet calls the proxy:

helloWorld(null, function(error, result) {

if (error) {

console.log("Error occured.");

console.log(error);

return;

}

console.log(result);

});

In the above snippet, we are passing a null value to first parameter of the proxy as we are not using the input value. The callback function is similar to any other callback function in Node.js accepting error and result as parameters.

We can rewrite the same Edge.js proxy creation by passing the C# code in the form of a string instead of a multiline comment. Following snippet shows this:

var helloWorld = edge.func(

'async(input) => {'+

'return "Hurray! Inline C# works with edge.js!!!";'+

'}'

);

We can pass a class in the snippet and call a method from the class as well. By convention, name of the class should be Startup and name of the method should be Invoke. The Invoke method will be attached to a delegate of type Func<object, Task<object>>. The following snippet shows usage of class:

var helloFromClass = edge.func(function () {

/*

using System.Threading.Tasks;

public class Startup

{       

public async Task<object> Invoke(object input)

{

return "Hurray! Inline C# class works with edge.js!!!";

}

} */

});

It can be invoked the same way we did previously:

helloFromClass(10, function (error, result) {

if(error){

console.log("error occured...");

console.log(error);

return;

}

console.log(result);

});

A separate C# file

Though it is possible to write the C# code inline, being developers, we always want to keep the code in a separate file for better organization of the code. By convention, this file should have a class called Startup with the method Invoke. The Invoke method will be added to the delegate of type Func<object, Task<object>>.

Following snippet shows content in a separate file, Startup.cs:

using System.Threading.Tasks;

public class Startup

{

public async Task<object> Invoke(object input)

{

return new Person(){

Name="Alex",

Occupation="Software Professional",

Salary=10000,

City="Tokyo"

};

}

}

public class Person{

public string Name { get; set; }

public string Occupation { get; set; }

public double Salary { get; set; }

public string City { get; set; }

}

Performing CRUD Operations on SQL Server

Now that you have a basic idea of how Edge.js works, let’s build a simple application that performs CRUD operations on a SQL Server database using Entity Framework and call this functionality from Node.js. As we will have a considerable amount of code to setup Entity Framework and perform CRUD operations in C#, let’s create a class library and consume it using Edge.js.

Creating Database and Class Library

As a first step, create a new database named EmployeesDB and run the following commands to create the employees table and insert data into it:

CREATE TABLE Employees(

Id INT IDENTITY PRIMARY KEY,

Name VARCHAR(50),

Occupation VARCHAR(20),

Salary INT,

City VARCHAR(50)

);

INSERT INTO Employees VALUES

('Ravi', 'Software Engineer', 10000, 'Hyderabad'),

('Rakesh', 'Accountant', 8000, 'Bangalore'),

('Rashmi', 'Govt Official', 7000, 'Delhi');

Open Visual Studio, create a new class library project named EmployeesCRUD and add a new Entity Data Model to the project pointing to the database created above. To make the process of consuming the dll in Edge.js easier, let’s assign the connection string inline in the constructor of the context class. Following is the constructor of context class that I have in my class library:

public EmployeesModel()

: base("data source=.;initial catalog=EmployeesDB;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework;")

{

}

Add a new class to the project and name it EmployeesOperations.cs. This file will contain the methods to interact with Entity Framework and perform CRUD operations on the table Employees. As a best practice, let’s implement the interface IDisposable in this class and dispose the context object in the Dispose method. Following is the basic setup in this class:

public class EmployeesOperations : IDisposable

{

EmployeesModel context;

public EmployeesOperations()

{

context = new EmployeesModel();

}

public void Dispose()

{

context.Dispose();

}

}

As we will be calling methods of this class directly using Edge.js, the methods have to follow signature of the delegate that we discussed earlier. Following is the method that gets all employees:

public async Task<object> GetEmployees(object input)

{

return await context.Employees.ToListAsync();

}

There is a challenge with the methods performing add and edit operations, as we need to convert the input data from object to Employee type. This conversion is not straight forward, as the object passed into the .NET function is a dynamic expando object. We need to convert the object into a dictionary object and then read the values using property names as keys. Following method performs this conversion before inserting data into the database:

public async Task<object> AddEmployee(object emp)

{

var empAsDictionary = (IDictionary<string, object>)emp;

var employeeToAdd = new Employee() {

Name = (string)empAsDictionary["Name"],

City = (string)empAsDictionary["City"],

Occupation = (string)empAsDictionary["Occupation"],

Salary = (int)empAsDictionary["Salary"]

};

var addedEmployee = context.Employees.Add(employeeToAdd);

await context.SaveChangesAsync();

return addedEmployee;

}

The same rule applies to the edit method as well. It is shown below:

public async Task<object> EditEmployee(object input)

{

var empAsDictionary = (IDictionary<string, object>)input;

var id = (int)empAsDictionary["Id"];

var employeeEntry = context.Employees.SingleOrDefault(e => e.Id == id);

employeeEntry.Name = (string)empAsDictionary["Name"];

employeeEntry.Occupation = (string)empAsDictionary["Occupation"];

employeeEntry.Salary = (int)empAsDictionary["Salary"];

employeeEntry.City = (string)empAsDictionary["City"];

context.Entry(employeeEntry).State = System.Data.Entity.EntityState.Modified

return await context.SaveChangesAsync();

}

We will compose REST APIs using Express.js and call the above functions inside them. Before that, we need to make the compiled dll of the above class library available to the Node.js application. We can do it by building the class library project and copying the result dlls into a folder in the Node.js application.

Creating Node.js Application

Create a new folder in your system and name it ‘NodeEdgeSample’. Create a new folder ‘dlls’ inside it and copy the binaries of the class library project into this folder. You can open this folder using your favorite tool for Node.js. I generally use WebStorm and have started using Visual Studio Code these days.

Add package.json file to this project using “npm init” command (discussed in Understanding NPM article) and add the following dependencies to it:

"dependencies": {

"body-parser": "^1.13.2",

"edge": "^0.10.1",

"express": "^4.13.1"

}

Run NPM install to get these packages installed in the project. Add a new file to the project and name it ‘server.js’. This file will contain all of the Node.js code required for the application. First things first, let’s get references to all the packages and add the required middlewares to the Express.js pipeline. Following snippet does this:

var edge = require('edge');

var express = require('express');

var bodyParser = require('body-parser');

var app = express();

app.use('/', express.static(require('path').join(__dirname, 'scripts')));

app.use(bodyParser.urlencoded({ extended: true }));

app.use(bodyParser.json());

Now, let’s start adding the required Express REST APIs to the application. As already mentioned, the REST endpoints will interact with the compiled dll to achieve their functionality. The dll file can be referred using theedge.func function. If type and method are not specified, it defaults class name as Startup and method name asInvoke. Otherwise, we can override the class and method names using the properties in the object passed intoedge.func.

Following is the REST API that returns list of employees:

app.get('/api/employees', function (request, response) {

var getEmployeesProxy = edge.func({

assemblyFile: 'dlls\\EmployeeCRUD.dll',

typeName: 'EmployeeCRUD.EmployeesOperations',

methodName: 'GetEmployees'

});

getEmployeesProxy(null, apiResponseHandler(request, response));

});

The function apiResponseHandler is a curried generic method for all the three REST APIs. This function returns another function that is called automatically once execution of the .NET function is completed. Following is the definition of this function:

function apiResponseHandler(request, response) {

return function(error, result) {

if (error) {

response.status(500).send({error: error});

return;

}

response.send(result);

};

}

Implementation of REST APIs for add and edit are similar to the one above. The only difference is, they pass an input object to the proxy function.

app.post('/api/employees', function (request, response) {

var addEmployeeProxy = edge.func({

assemblyFile:"dlls\\EmployeeCRUD.dll",

typeName:"EmployeeCRUD.EmployeesOperations",

methodName: "AddEmployee"

});

addEmployeeProxy(request.body, apiResponseHandler(request, response));

});

app.put('/api/employees/:id', function (request, response) {

var editEmployeeProxy = edge.func({

assemblyFile:"dlls\\EmployeeCRUD.dll",

typeName:"EmployeeCRUD.EmployeesOperations",

methodName: "EditEmployee"

});

editEmployeeProxy(request.body, apiResponseHandler(request, response));

});

Consuming APIs on a Page

The final part of this tutorial is to consume these APIs on an HTML page. Add a new HTML page to the application and add bootstrap CSS and Angular.js to this file. This page will list all the employees and provide interfaces to add new employee and edit details of an existing employee. Following is the mark-up on the page:

<!doctype html>

<html>

<head>

<title>Edge.js sample</title>

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"/>

</head>

<body ng-app="edgeCrudApp">

<div class="container" ng-controller="EdgeCrudController as vm">

<div class="text-center">

<h1>Node-Edge-.NET CRUD Application</h1>

<hr/>

<div class="col-md-12">

<form name="vm.addEditEmployee">

<div class="control-group">

<input type="text" ng-model="vm.employee.Name" placeholder="Name" />

<input type="text" ng-model="vm.employee.Occupation" placeholder="Occupation" />

<input type="text" ng-model="vm.employee.Salary" placeholder="Salary" />

<input type="text" ng-model="vm.employee.City" placeholder="City" />

<input type="button" class="btn btn-primary" ng-click="vm.addOrEdit()" value="Add or Edit" />

<input type="button" class="btn" value="Reset" ng-click="vm.reset()" />

</div>

</form>

</div>

<br/>

<div class="col-md-10">

<table class="table">

<thead>

<tr>

<th style="text-align: center">Name</th>

<th style="text-align: center">Occupation</th>

<th style="text-align: center">Salary</th>

<th style="text-align: center">City</th>

<th style="text-align: center">Edit</th>

</tr>

</thead>

<tbody>

<tr ng-repeat="emp in vm.employees">

<td>{{emp.Name}}</td>

<td>{{emp.Occupation}}</td>

<td>{{emp.Salary}}</td>

<td>{{emp.City}}</td>

<td>

<button class="btn" ng-click="vm.edit(emp)">Edit</button>

</td>

</tr>

</tbody>

</table>

</div>

</div>

</div>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>

<script src="app.js"></script>

</body>

</html>

Add a new folder to the application and name it ‘scripts’. Add a new JavaScript file to this folder and name it ‘app.js’. This file will contain the client side script of the application. Since we are building an Angular.js application, the file will have an Angular module with a controller and a service added to it. Functionality of the file includes:

  • Getting list of employees on page load
  • Adding an employee or, editing employee using the same form
  • Resetting the form to pristine state once the employee is added or, edited

Here’s the code for this file:

(function(){

var app = angular.module('edgeCrudApp', []);

app.controller('EdgeCrudController', function (edgeCrudSvc) {

var vm = this;

function getAllEmployees(){

edgeCrudSvc.getEmployees().then(function (result) {

vm.employees = result;

}, function (error) {

console.log(error);

});

}

vm.addOrEdit = function () {

vm.employee.Salary = parseInt(vm.employee.Salary);

if(vm.employee.Id) {

edgeCrudSvc.editEmployee(vm.employee)

.then(function (result) {

resetForm();

getAllEmployees();

}, function (error) {

console.log("Error while updating an employee");

console.log(error);

});

}

else{

edgeCrudSvc.addEmployee(vm.employee)

.then(function (result) {

resetForm();

getAllEmployees();

}, function (error) {

console.log("Error while inserting new employee");

console.log(error);

});

}

};

vm.reset= function () {

resetForm();

};

function resetForm(){

vm.employee = {};

vm.addEditEmployee.$setPristine();

}

vm.edit = function(emp){

vm.employee = emp;

};

getAllEmployees();

});

app.factory('edgeCrudSvc', function ($http) {

var baseUrl = '/api/employees';

function getEmployees(){

return $http.get(baseUrl)

.then(function (result) {

return result.data;

}, function (error) {

return error;

});

}

function addEmployee(newEmployee){

return $http.post(baseUrl, newEmployee)

.then(function (result) {

return result.data;

}, function (error) {

return error;

});

}

function editEmployee(employee){

return $http.put(baseUrl + '/' + employee.Id, employee)

.then(function (result) {

return result.data;

}, function (error) {

return error;

});

}

return {

getEmployees: getEmployees,

addEmployee: addEmployee,

editEmployee: editEmployee

};

});

}());

Save all the files and run the application. You should be able to add and edit employees. I am leaving the task of deleting employee as an assignment to the reader.

Conclusion

In general, it is challenging to make two different frameworks talk to each other. Edge.js takes away the pain of integrating two frameworks and provides an easier and cleaner way to take advantage of good features of .NET and Node.js together to build great applications. It aligns with the Node.js event loop model and respects execution model of the platform as well. Let’s thank Tomasz Jancjuk for his great work and use this tool effectively!

Download the entire source code of this article (Github)

Happy Coding Smile

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s