edge.JS

Eintrag zuletzt aktualisiert am: 18.01.2014

edge.js ist eine Brückentechnik zwischen node.js und Microsoft .NET Framework, die es node.js-Programmen ermöglicht, in Form von .NET-Assemblies vorliegende DLLs zuzugreifen oder Quellcode in verschiedenen .NET-Sprachen (z.B. F#, Visual Basic .NET, Python und die Windows PowerShell) nach einer Ad-Hoc-Kompilierung im gleichen Prozess aufzurufen

Details zu edge.js

Wenn für eine node,js-Website die Daten aus einem bereits bestehenden System, das mit Microsoft .NET entwickelt ist, kommen, stellt sich die Frage der Integration. Der typische Weg ist, das .NET-Backend in einem eigenen Prozess zu hosten, den node.js über HTTP-basierte Webservices (SOAP oder REST) aufruft. In .NET würde man diese Webservices über die Windows Communication Foundation (SOAP und REST, HTTP und viele andere Protokolle) oder das ASP.NET Web API (nur HTTP/REST) bereitstellen. Allerdings muss man sich im Klaren sein, dass die zusätzliche Prozessgrenze erheblich Leistung frisst. Bei edge.js handelt es sich um eine node.js-Erweiterungsbibliothek, die es node.js-Programmen ermöglicht, in Form von .NET-Assemblies vorliegende DLLs zuzugreifen oder Quellcode in verschiedenen .NET-Sprachen (z.B. F#, Visual Basic .NET, Python und die Windows PowerShell) nach einer Ad-Hoc-Kompilierung im gleichen Prozess aufzurufen. Da die Integration asynchron erfolgt, ist .NET Framework 4.5 die Voraussetzung. Autor von edge.js ist Tomasz Janczuk, ein ehemaliger Development Manager bei Microsoft im Windows Azure Team.

Listing 1 und 2 zeigt eine auf edge.js basierende Integration zwischen JavaScript und C#. Der JavaScript-Code erzeugt mit edge.func() eine Proxy-Funktion für eine .NET-Methode. Dabei sind Assemblyname, Typname und Methodenname anzugeben. Diese im Bezeichner csFunktion abgelegte Funktion wird dann im Folgenden aufgerufen mit einem Nutzdatenobjekt, dass drei Attribute besitzt: Eine Zeichenkette, ein Array von JavaScript-Objekten und den Zeiger auf eine JavaScript-Funktion, die .NET zurückrufen kann. Als zweiten Parameter der csFunktion muss man in JavaScript beim Aufruf der .NET-Methode eine Callback-Funktion mit zwei Parametern (Fehlerobjekt und Ergebnis der Funktion) angeben. Wenn das Fehlerobjekt-Objekt den Wert null hat, ist kein Fehler aufgetreten.

Die C#-Seite empfängt das JavaScript-Nutzdatenobjekt als System.Dynamic.ExpandoObject mit Schnittstelle „Idictionary<string,object>“. In Listing 12 werden daher zuerst die drei Attribute „Aufrufer“, „Produktliste“ und „Callback“ aus dem Dictionary geholt. Anschließend erfolgt die Weiterverarbeitung. Die Produktliste wird als ein Array von ExpandoObject empfangen; daher müssen die C#-Produkt-Objekte in der Iteration über die Eingabemenge erst konstruiert werden. Die Console.WriteLine()-Arufrufe, die beim Debugging in dem node.js-Konsolenfenster landen, sind natürlich nur zu Testzwecken vorhanden. Wie das Beispiel anhand der Klasse Ergebnis zeigt, kann .NET auch komplexe Datentypen zurückgeben. Voraussetzung ist ihre Serialisierbarkeit mit JSON und das Fehlen zirkulärer Referenzen. Die Callback-Funktion empfängt C# als Typ Func<object,Task<object>>, der mit await aufgerufen werden kann. Wichtig zu beachten ist, dass in JavaScript die Callback-Funktion aktiv wieder C# aufrufen muss. Dafür empfängt sie im zweiten Parameter (hier „callbackEnde“ genannt) den Rücksprungpunkt.

Der JavaScript-Code erzeugt Produkt-Objekte, die an C# übergeben und dort ausgewertet werden. Während der Auswertung sendet C# den Status („Produkt x empfangen!“) an JavaScript zurück. JavaScript gibt diese Zwischenzustände und das Ergebnisobjekt in der HTML-Seite aus. Bei der Verwendung von edge.js in Visual Studio ist das Debugging inzwischen über die Technikgrenze hinweg möglich.

Listing 1: node.js-Server, der via edge auf Methode in einer .NET-Assembly zugreift

var http = require('http');
var url = require('url');
var edge = require('edge');

var anfrageBearbeiter = function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});

res.write("<html>");
res.write("<body><h1>Kopplung node.JS und .NET via edge</h1>");

// Aufzurufende C#-Methode festlegen
var csFunktion = edge.func(
{
assemblyFile: dirname + "/DOTNETAssembly.dll",
typeName: 'edgeJSDemos.Produktmanager',
methodName: 'Invoke' // Func<object,Task<object>>
});

// Test-Produkte generieren
var Produkte = [];
for(var i = 0; i<= 10; i++)
{
var Produkt = {
Name: 'Produkt ' + i,
Preis: 0.99+i*i
};
Produkte.push(Produkt);
}

// C# aufrufen
csFunktion( { // Nutzdaten
Aufrufer: "Beispiel 6a",
Produktliste: Produkte,
Callback: function(daten, callbackEnde) { res.write("."); callbackEnde(null, null); } },
// Ergebnismethode
function (error, result) {
// Ergebnis von C# auswerten
if (error) {
res.write("Fehler: " + error);
}
else {
res.write("Summe: " + result.Summe + "<br>");
res.write("Durchschnitt: " + result.Durchschnitt + "<br>");
res.write("</body>");
res.write("</html>");
res.end();
}
});

};


// Server starten
var server = http.createServer(anfrageBearbeiter);

server.listen(1337, function() {
console.log("Server aufrufen mit http://localhost:1337?name=<Name einsetzen>");
});

Listing 2: C#-Programmcode zu Listing 1

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace edgeJSDemos
{
public class ProduktManager
{
class Produkt
{
public string Name { get; set; }
public double Preis { get; set; }
}

class Ergebnis
{
public double Summe { get; set; }
public double Durchschnitt { get; set; }
}

public async Task<object> Invoke(System.Dynamic.ExpandoObject input)
{
Console.WriteLine("C# beginnt…");

var liste = new List<Produkt>();

Console.WriteLine("Empfangener Datentyp: " + input.GetType().FullName);

// zugriff auf die Eigenschaften des Nutzdatenobjekts
var NutzDaten = (Idictionary<string, object>)input;
var Aufrufer = (string)NutzDaten["Aufrufer"];
var Produktliste = (object[])NutzDaten["Produktliste"];
var Callback = (Func<object, Task<object>>)NutzDaten["Callback"];

Console.WriteLine("Aufrufer {0} lieferte {1} Produkte.", Aufrufer, Produktliste.Length);

// Iteration über alle Produkte
foreach (var obj in Produktliste)
{
Idictionary<string, object> payload = (Idictionary<string, object>)obj;
Console.WriteLine("Empfangener Datentyp: " + payload);
// Produktobjekte erzeugen
var p = new Produkt()
{
Name = (string)payload["Name"],
Preis = (double)payload["Preis"]
};
liste.Add(p);
// Nur Testausgabe
Console.WriteLine("C#: " + p.Name + " kostet " + p.Preis);

await Callback("Produkt " + p.Name + " empfangen!");
}

// Auswertung erstellen
var e = new Ergebnis()
{
Summe = Math.Round(liste.Sum(x => x.Preis), 2),
Durchschnitt = Math.Round(liste.Average(x => x.Preis), 2)
};

Console.WriteLine("C# ist fertig!");

return e;
}
}
}