Cutting Edge

Ein Blick auf ClearScript

Dino Esposito

Laden Sie die Codebeispiele herunter

Dino EspositoVor Jahren faszinierte mich die Idee, das gesamte VBScript-Modul der Active Server Pages in einer Visual Basic-Anwendung zu hosten. Schließlich gelang es mir, einen eindrucksvollen Machbarkeitsnachweis für ein Tool zu erbringen, mit dem ein Unternehmen redaktionelle Inhalte auf CD vertreiben wollte. Es ging um die Frage, wie bestehende Active Server Pages ohne Anbindung an lokale oder Remotewebserver wiederverwendet werden können.

Wir sprechen von den späten 1990er Jahren, als es noch kein Microsoft .NET Framework und kein HTML5 gab. Nur wenige waren damals ernsthaft daran interessiert, die Tiefen von Dynamic HTML zu ergründen, dennoch hätte das Hosten des Skriptmoduls nicht einfacher sein können. Ich musste nur einen Verweis auf ein ActiveX-Steuerelement hinzufügen, meine ActiveX-Objekte in der Skriptumgebung veröffentlichen, und los ging's.

Kürzlich fragte mich ein Kunde, wie er Textdateien am schnellsten aus SQL Server-Abfragen extrahieren könne. Da diese Problematik außerhalb meines Themenbereichs liegt, war ich schon versucht, die Frage mit einem Achselzucken abzutun. Ich wusste jedoch, dass sich dieser Kunde auf Datenbankoperationen spezialisiert hatte, und vermutete deshalb mehr hinter der Frage.

Tatsächlich generierte der Kunde regelmäßig Textdateien (überwiegend im CSV- und XML-Format) auf der Basis von Datenbanktabellen, die auf einer Instanz von SQL Server gehostet waren. Dies erzeugte Unmut beim Datenbankteam, da die meisten Anforderungen von der Geschäftsleitung mit der für sie typischen Dringlichkeit kamen. Es gab keine wiederkehrende Logik, durch die wiederholbare Routinen – zumindest in einer SQL Server-Umgebung – hätten implementiert werden können.

Schließlich suchte der Kunde nach einem Tool, das Fachanwender mit einer einfachen Skriptsprache wie VBScript programmieren könnten. Die Lösung sollte den kontrollierten Lesezugriff auf die Datenbanken erlauben und natürlich in der Lage sein, auf einfache Weise Textdateien zu erstellen. Das erinnerte mich an die glorreichen Zeiten von ActiveX und VBScript, und mit gemischten Gefühlen sah ich einer relativ neuen Entwicklung entgegen – einer Bibliothek mit dem Namen ClearScript (clearscript.codeplex.com).

Einbinden von ClearScript in WPF (Windows Presentation Foundation)

Mit ClearScript können Sie einer .NET-Anwendung Skriptfunktionen hinzufügen (sofern .NET Framework 4 oder höher verwendet wird). ClearScript unterstützt VBScript, JavaScript und V8. V8 ist ein von Google entwickeltes und in Chrome integriertes Open-Source-JavaScript-Modul. Google V8 verfügt über ein hochleistungsfähiges JavaScript-Modul und fügt sich perfekt in Multithreading- und asynchrone Arbeitsabläufe ein.

Das Einbinden von ClearScript in eine .NET-Anwendung bewirkt letztendlich, dass sich JavaScript- oder VBScript-Ausdrücke an das Modul weitergeben lassen, von wo sie weiterverarbeitet und ausgeführt werden können. Vor allem aber sind die Anwender nicht auf reine Skriptobjekte wie Arrays, JSON-Objekte und primitive Datentypen beschränkt. Es können auch externe JavaScript-Bibliotheken und per Skript verwaltete .NET-Objekte integriert werden.

Nach der Integration von ClearScript in eine Anwendung müssen Sie nur noch die Bibliothek davon in Kenntnis setzen, dass die Objekte für die Skripterstellung zur Verfügung stehen. Sie veröffentlichen also eigene Objekte im Kontext von ClearScript und erlauben autorisierten Benutzern das Laden und Ausführen bestehender bzw. die Erstellung neuer Skripts.

Wenn Sie den Benutzern Spielraum geben möchten, eigenen Code ohne großen Modifizierungsaufwand hinzuzufügen, dann ist ClearScript die erforderliche Lösung, reicht unter Umständen jedoch nicht aus. ClearScript ist nur ein Puzzleteilchen im ganzen System. Sie können dem Benutzer die Möglichkeit geben, seine eigenen Skripts zu verwalten. Außerdem sollten Sie einige Ad-hoc-Objekte erstellen, die allgemeine Aufgaben wie die Dateierstellung vereinfachen.

Im Folgenden stelle ich Ihnen meine Lösung vor, mit der der Kunde Text- und XML-Berichte generieren kann, während er die von einem Web-API-Back-End bereitgestellten Dienste nutzt. Die größte funktionale Anforderung besteht darin, dem Benutzer die Erstellung von Textdateien zu ermöglichen. Für den Machbarkeitsnachweis benötigte ich eine Shellanwendung zum Hosten von ClearScript. Also entschied ich mich für eine WPF (Windows Presentation Foundation)-Anwendung mit einem Textfeld für die manuelle Eingabe von Skriptcode. Sukzessiv wurden dann ein Standardeingabeordner und Bedienelemente zum Öffnen/Importieren bestehender Skriptdateien hinzugefügt. Abbildung 1 zeigt die WPF-Beispielanwendung in Aktion.

A Sample Windows Presentation Foundation Application Hosting the ClearScript Engine
Abbildung 1 – Eine WPF (Windows Presentation Foundation)-Anwendung, die das ClearScript-Modul hostet

Es sei noch einmal darauf hingewiesen, dass ClearScript ein Open-Source-Konzept ist, das sich direkt vom Projekt aus referenzieren lässt. Man muss lediglich Assemblys verknüpfen. Als Alternative können Sie NuGet-Pakete von Drittanbietern verwenden, wie in Abbildung 2 dargestellt.

You Can Install ClearScript via NuGet
Abbildung 2 – ClearScript kann auch über NuGet installiert werden.

Initialisieren von ClearScript

Bevor Sie das Skriptmodul programmgesteuert verwenden können, sind einige vorbereitende Schritte nötig. Wenn diese Arbeit getan ist, brauchen Sie jedoch nur noch den in Abbildung 3 abgebildeten Code auszuführen, um das Skript auszulösen.

Abbildung 3 Code zum Auslösen von Skriptcode

public void Confirm()
{
  try
  {
    SonoraConsole.ScriptEngine.Execute(Command);
    OutputText = SonoraConsole.Output.ToString();
  }
  catch(Exception e)
  {
    OutputText = e.Message;
  }
}

Die Confirm-Methode gehört zur Präsentationsklasse, die die Hauptansicht der Beispielanwendung unterstützt. Sie lösen die Methode durch Klicken auf die Schaltfläche "Run" (siehe Abbildung 1) aus. Die SonoraConsole-Klasse, die in der Auflistung referenziert wird, ist mein eigener Wrapper für zentrale Klassen der ClearScript-Bibliothek.

Die Initialisierung des ClearScript-Moduls wird ausgeführt, während die Anwendung gestartet wird. Sie ist an das Startup-Ereignis der XAML-Anwendungsklasse gebunden:

public partial class App : Application
{
  void Application_Startup(Object sender, StartupEventArgs e)
  {
    SonoraConsole.Initialize();
  }
}

Im Hinblick auf Komplexität und Finesse sind der Initialisierung keine Grenzen gesetzt, sie muss jedoch mindestens das Skriptmodul der ausgewählten Programmiersprache initialisieren. Die Instanz des Skriptmoduls muss für die anderen Teile der Anwendung verfügbar gemacht werden. Ein möglicher Lösungsansatz sieht so aus:

public class SonoraConsole
{
   public static void Initialize()
   {
     ScriptEngine = new VBScriptEngine()
   }
   public static ScriptEngine ScriptEngine { get; private set; }
   ...
}

Möglicherweise möchten Sie aus der Konfigurationsdatei die Skriptsprache auslesen, die in der Anwendung aktiviert werden soll. Ein mögliches Konfigurationsschema ist:

<appSettings>
  <add key="language" value="vb" />
</appSettings>

Nachdem Sie eine Instanz des ausgewählten Skriptmoduls eingerichtet haben, können Sie jeden gültigen JavaScript- (oder VBScript)-Code ausführen. Ausgestattet mit diesen Grundfunktionen, können Sie es mit den Anforderungen der realen Welt aufnehmen.

Hinzufügen skriptfähiger Objekte

Alle ClearScript-Module machen eine programmierbare Schnittstelle verfügbar, über die Sie der Laufzeitumgebung skriptfähige Objekte hinzufügen können. Dafür bietet sich insbesondere die AddHostObject-Methode an:

ScriptEngine.AddHostObject("out", new SonoraOutput(settings));
ScriptEngine.AddHostObject("xml", new XmlFacade());

Diese Methode erfordert zwei Parameter: Der erste Parameter entspricht dem öffentlichen Namen, über den Skripter auf das veröffentlichte Objekt verweisen. Der zweite Parameter steht für die Objektinstanz. Anhand des vorangehenden Codeausschnitts erkennen Sie, dass Sie in jedem JavaScript oder VBScript den Namen "out" verwenden können, um öffentliche Methoden der SonoraOutput-Schnittstelle aufzurufen. Das folgende JavaScript-Beispiel bezieht sich auf die Anwendung in Abbildung 1:

var x = 4;
out.print(x + 1);

In JavaScript ist es allgemein üblich, die Member gemäß der camelCase-Konvention zu benennen. Dagegen ist bei der .NET-Programmierung die PascalCase-Konvention stärker verbreitet und die empfohlene Lösung. In meiner Implementierung der SonoraOutput-Klasse habe ich mich bewusst für die JavaScript-Konvention entschieden und die Methode "print" anstelle von "Print" genannt, wie es bei der reinen C#-Programmierung üblich wäre.

Nach meiner Erfahrung müssten Sie jetzt schon mit allen wichtigen Details vertraut sein, um mit ClearScript zu starten. Eine ClearScript-Umgebung lässt sich zum größten Teil innerhalb einer Hostanwendung konfigurieren. In erster Linie werden dabei anwendungsspezifische Objekte verfügbar gemacht. Meistens handelt es sich dabei um spezifisch angepasste Objekte, die als Wrapper für bestehende Geschäftsobjekte eingesetzt werden und innerhalb einer Skriptumgebung angenehmer zu verwenden sind.

ClearScript-Umgebungen werden vorwiegend von Teilzeit-Entwicklern verwendet, die weniger Entwicklungserfahrung haben und es als unnötig kompliziert und lästig ansehen würden, sich mit allen Details der .NET-Klassen vertraut zu machen. Durch ClearScript können Sie weite Bereiche von .NET Framework direkt in JavaScript und VBScript verfügbar machen. Ich habe mich für angepasste Objekte entschieden, die ausgesprochen einfach zu verwenden sind. Das folgende Bespiel zeigt, wie Sie in ClearScript einen Typ anstelle eines Objekts veröffentlichen:

ScriptEngine.AddHostType("dt", typeof(DateTime));

Durch einen Typverweis geben Sie Benutzern die Möglichkeit, programmgesteuert Instanzen dieses Typs zu erstellen. Die vorangehende Codezeile bindet beispielsweise das leistungsfähige .NET DateTime-Objekt in die Skriptumgebung ein. Dadurch kann der folgende JavaScript-Code geschrieben werden:

var date = new dt(1998, 5, 20);
date = date.AddDays(1000);
out.print(date)

Mit JavaScript-Code können Sie das Potenzial von Methoden wie "AddDays" und "AddHours" in vollem Umfang nutzen. Wenn Sie die Differenz zwischen zwei Datumsangaben ermitteln möchten, verwenden Sie z. B. den folgenden Programmcode:

var date1 = new dt(1998, 5, 20);
var date2 = date1.AddDays(1000);
var span = date2.Subtract(date1);
out.print(span.Days)

Das TimeSpan-Objekt wird ordnungsgemäß verarbeitet, und der span.Days-Ausdruck gibt den Wert 1.000 zurück. Dies ist auf die Dynamik der JavaScript-Sprache zurückzuführen, die bestimmt, dass ein Member mit dem Namen "Days" von einem Objekt mit dem Namen "span" verfügbar gemacht wird. Wenn Sie stattdessen eine TimeSpan-Instanz erstellen möchten, müssen Sie dem Modul zunächst mitteilen, dass die Instanz vorhanden ist.

Damit die Anzahl der verschiedenen Typen nicht ins Unendliche ansteigt, ermöglicht Ihnen ClearScript das Hosten ganzer Assemblys. Ein möglicher Ansatz sieht so aus:

ScriptEngine.AddHostObject("dotnet",
  new HostTypeCollection("mscorlib", "System.Core"));

Das Schlüsselwort "dotnet" wird jetzt zum Schlüssel für den Zugriff auf beliebige Typen und statische Member in "mscorlib" und "System.Core". Die Erstellung eines neuen Datumsobjekts dauert zwar etwas länger, dagegen können Sie TimeSpan-Objekte aber explizit einsetzen:

var date1 = new dotnet.System.DateTime(1998, 5, 20);
var ts1 = new dotnet.System.TimeSpan(24, 0, 0);
var ts2 = ts1.Add(new dotnet.System.TimeSpan(24, 0, 0));
out.print(ts2.Days);

Jeder JavaScript-Codeausschnitt gibt die Zahl 2 zurück. Dieser Wert ergibt sich aus der Summe zweier eindeutiger TimeSpan-Objekte von jeweils 24 Stunden. In einer Sache lässt ClearScript noch zu wünschen übrig, die Überladung von Operatoren. Diese Funktion ist schlicht nicht vorhanden. Zum Summieren von Datumsangaben oder Zeitspannen benötigen Sie also Methoden wie "Add" oder "Subtract". Die Reflexion wird aber unterstützt.

Generieren der Ausgabe

Das Tool in Abbildung 1 muss in der Lage sein, dem Benutzer Ergebnisdaten anzuzeigen. Das SonoraOutput-Objekt, das Sie dem ClearScript-Modul hinzufügen, verfügt standardmäßig über ein internes StringWriter-Objekt. Der gesamte, von der Print-Methode verarbeitete Text wird tatsächlich in den zugrunde liegenden Writer geschrieben. Der Inhalt des Writers wird dem Benutzer dann über die SonoraConsole-Klasse verfügbar gemacht. Diese Klasse ist der einzige Kontaktpunkt zwischen dem ClearScript-Modul und der Hostanwendung. Die Präsentationsklasse der Hostanwendung gibt den Inhalt des StringWriters einfach über eine Eigenschaft zurück. Diese Eigenschaft wird dann an einen TextBlock in der WPF-Benutzeroberfläche gebunden. Die print-Methode schreibt über den StringWriter in die Benutzeroberfläche. Die clr-Methode löscht die Eingaben im Puffer und auf der Benutzeroberfläche.

Speichern in eine Textdatei

Meinem Kunden kam es nur auf Textdateien, insbesondere CSV-Dateien, an, was relativ einfach umzusetzen war. Ich musste lediglich eine file-Methode erstellen, an die Text direkt übergeben wird. Alternativ kann die Methode auch Bildschirmdaten oder im internen Puffer gespeicherte Daten sammeln. Der kritischste Aspekt besteht darin, wie Dateien benannt und wo sie gespeichert werden. Damit Skripts ihre volle Leistung entfalten, müssen Dateien so einfach wie möglich erstellt und abgerufen werden. Ich habe deshalb genau zwei Standardordner eingerichtet – einen Eingabe- und einen Ausgabeordner. Außerdem bin ich davon ausgegangen, dass alle Dateien das TXT-Format haben und ein Standardname verwendet wird, wenn kein Dateiname angegeben ist.

In einem anderen Szenario mögen diese Annahmen zu restriktiv sein, für den einfachen Machbarkeitsnachweis eines Tools, das Dateien erstellt, aber nicht speichert, erfüllen sie jedoch ihren Zweck. Wie Abbildung 4 zeigt, kann ich das XmlWriter-Objekt einfach in einen zweckmäßigen Wrapper einwickeln und per Skript eine XML-Datei erstellen.

Create an XML File via Script
Abbildung 4 – Erstellen einer XML-Datei per Skript

Zusammenfassung

Was spricht für die Erstellung einer XML-Datei per Skript? Genauso viel wie für Skriptfunktionen in manchen Unternehmensanwendungen. Sie benötigen Skripts, um Aufgaben zu automatisieren. Manchmal geht es nur um die schnelle Erstellung von Text- oder XML-Dateien. Natürlich können Sie auch SQL Server-Abfragen ausführen und in eine CSV-Datei importieren. Dazu sind jedoch Administratorzugriff auf die produktive Datenbank – und noch wichtiger – spezielle Kenntnisse erforderlich. Auch ich würde an meine Grenzen stoßen, wenn ich mit "xp_cmdshell" Textdateien aus SQL Server-Abfragen schreiben müsste. Einfach zu handhabende und sofort einsetzbare Skriptobjekte sind von einem Entwickler dagegen im Handumdrehen erstellt.

Mein Kunde war restlos überzeugt – genauso wie ich von ClearScript. Er beauftragte mich damit, der dynamischen Umgebung noch viele weitere Objekte hinzuzufügen. Schließlich habe ich noch eine IoC (Inversion of Control)-Ebene hinzugefügt, damit Objekte beim Start geladen werden. Weiter in Planung ist eine unternehmensweite ClickOnce-Bereitstellung für die Freigabe neuer Tools.


Dino Esposito ist Mitautor von "Microsoft .NET: Architecting Mobile Applications Solutions for the Enterprise" (Microsoft Press, 2014) und dem demnächst erscheinenden Artikel "Programming ASP.NET MVC 5" (Microsoft Press, 2014). Esposito ist Technical Evangelist für die .NET Framework- und Android-Plattformen bei JetBrains und spricht häufig auf Branchenveranstaltungen weltweit. Auf software2cents.wordpress.com und auf Twitter unter twitter.com/despos lässt er uns wissen, welche Softwarevision er verfolgt.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Microsoft ClearScript-Team