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;
}
}
}