Exportieren (0) Drucken
Alle erweitern

Erstellen von anpassbaren Webdiensten

Veröffentlicht: 01. Nov 2001 | Aktualisiert: 18. Jun 2004
Von Andrew Clinick

Vor wenigen Monaten habe ich Microsoft® Visual Studio® for Applications (VSA) und Skript für .NET Framework vorgestellt und beschrieben, wie Sie Ihre .NET-Anwendung mit Hilfe dieser Technologien anpassbar machen können. Es kann eine undankbare Aufgabe sein, eine .NET-Anwendung zu definieren, da scheinbar jeder eine andere Vorstellung davon hat, was eine Anwendung ist und ob es sich um eine .NET-Anwendung handelt oder nicht. Es gibt jedoch eine Technologie, die in allen Definitionen einer .NET-Anwendung enthalten ist - Webdienste. In diesem Monat werde ich erläutern, wie VSA und Skript für .NET Framework verwendet werden, um einen Webdienst zu erstellen, der anpassbar ist. Insbesondere werde ich beschreiben, wie Sie die Webdienste nutzen können, um eine Infrastruktur zu erstellen, die es Ihren Benutzern ermöglicht, den Skriptcode zur Anpassung des Webdienstes zu schreiben und bereitzustellen

* * *

Links zu verwandten Themen
Auf dieser Seite

Welche Gründe sprechen für das Erstellen von anpassbaren Anwendungen?  Welche Gründe sprechen für das Erstellen von anpassbaren Anwendungen?
Entwickeln eines anpassbaren Webdienstes  Entwickeln eines anpassbaren Webdienstes 
Implementieren eines Webdienstes  Implementieren eines Webdienstes 
Bereitstellen einer Entwicklungsumgebung für das Skript  Bereitstellen einer Entwicklungsumgebung für das Skript 
Einrichten des Entwurfszeitmoduls  Einrichten des Entwurfszeitmoduls 
Hinzufügen von Verweisen und des Objektmodells zum Modul  Hinzufügen von Verweisen und des Objektmodells zum Modul 
Objektmodellimplementierung und Leistung auf dem Server  Objektmodellimplementierung und Leistung auf dem Server 
Anzeigen der IDE  Anzeigen der IDE 
Schreiben von Skriptcode  Schreiben von Skriptcode 
Speichern des Codes  Speichern des Codes 
Ausführen des Codes  Ausführen des Codes 
Zusammenfassung  Zusammenfassung 

Welche Gründe sprechen für das Erstellen von anpassbaren Anwendungen?

Bevor ich näher auf die technischen Details der Verwendung von VSA zum Erstellen von anpassbaren Anwendungen eingehe, möchte ich einen Schritt zurückgehen und erläutern, warum anpassbare Anwendungen beim Erstellen einer .NET-Anwendung so wichtig und geeignet dafür sind. Wenn Sie von dieser Idee bereits überzeugt sind, können Sie diesen Abschnitt überspringen. Beim Entwerfen oder Schreiben einer Anwendung haben Sie stets das Ziel, die Anforderungen der Benutzer zu erfüllen und ihnen ein Programm bereitzustellen, das ihre Probleme löst und ihnen das Leben erleichtert. Trotz all der Anstrengungen, die Sie erbringen, um die Benutzeranforderungen zu ermitteln und die Benutzer in den Prozess der Anwendungsentwicklung einzubeziehen, steht oft von vornherein fest, dass Ihr fertig gestelltes Programm nicht alle Benutzeranforderungen erfüllt. Anforderungen neigen (leider) dazu, sich im Laufe der Zeit zu ändern - und sobald die Benutzer das Programm in der Praxis einsetzen, fallen ihnen sicher noch weitere Anforderungen ein, die Ihre Anwendung erfüllen sollte, um ihnen den Arbeitsalltag zu erleichtern.

Die Entscheidung darüber, welche Benutzeranforderungen erfüllt und welche Probleme gelöst werden sollten, fällt doppelt so schwer, wenn Sie versuchen, Software zu schreiben, die in mehr als einem Unternehmen verwendet wird. Wenn Sie z.B. ein Auftragsabwicklungssystem erstellen, stellt sich die Frage, wie hoch die Wahrscheinlichkeit ist, für 100% Ihrer Kunden die korrekten Geschäftsregeln bereitzustellen. Es ist nicht möglich, eine generische Software zu erstellen, die alle Anforderungen erfüllt, ohne dass eine Produktanpassung erforderlich wäre. Die Art und Weise, wie Sie Ihre Anwendung anpassbar machen, kann erhebliche Auswirkungen auf die Benutzer, die Aktualisierbarkeit der Anwendung sowie auf die Skalierbarkeit des Produkts haben. Zur Anpassung stehen Ihnen mehrere Möglichkeiten zur Verfügung (die Reihenfolge ist ohne Bedeutung):

  • Keine Anpassungsfunktion
    Ich nehme an, dass diese Möglichkeit aus den oben genannten Gründen vermutlich nicht in Frage kommt.

  • Bereitstellen des Anwendungsquellcodes, damit Ihre Kunden den Quellcode ändern und gemäß ihren Anforderungen anpassen können

    Diese Lösung hat einige Vorteile, da im Wesentlichen die Kunden die vollständige Kontrolle darüber erhalten, wie das Produkt implementiert ist, wodurch ein Höchstmaß an Flexibilität erzielt wird. Dies kann eine geeignete und attraktive Möglichkeit sein. Bei diesem Ansatz ergeben sich jedoch zahlreiche Probleme, einschließlich Probleme bei der Aktualisierung auf neuere Versionen und beim Schutz ihres geistigen Eigentums. Ich möchte jetzt nicht in die Debatte zum Thema geistiges Eigentum einsteigen (dieses Thema wird bereits von genug Leuten im Internet diskutiert, ohne dass ich mich daran beteilige), aber die Aktualisierungsprobleme betreffen uns alle. Nehmen wir z.B. an, dass Sie den Quellcode für Version 1.0 Ihres Produkts bereitstellen und einige Ihrer Benutzer die Implementierung einer bestimmten Komponente ändern. (Als Beispiel verwende ich die Steuerberechnungskomponente eines Auftragsabwicklungssystems.) Gehen wir davon aus, dass die interne Implementierung sich unterscheidet und dass die externe Schnittstelle der Komponente geändert wurde, um sie in das unternehmensspezifische Fakturierungssystem des Kunden zu integrieren. Die gute Nachricht ist, dass der Kunde Ihr System erfolgreich in das unternehmensspezifische System integriert hat und dass er zufrieden ist. In der Zwischenzeit haben Sie mit der Arbeit an Version 2.0 Ihres Produkts begonnen, und der Kunde ist daran interessiert, diese Version zu erwerben, da sie neue Funktionen bereitstellt. Sie installieren die Version 2.0 des Produkts vor Ort und stellen dabei fest, dass die Aktualisierung nicht auf einfache Weise durchzuführen ist, da die Steuerberechnungskomponente geändert wurde - die einzige Möglichkeit besteht darin, die von Ihnen an der Komponente vorgenommenen Änderungen mit den Änderungen seitens des Kunden zusammenzuführen.Oberflächlich betrachtet scheint dies kein großes Problem zu sein, aber es wird sehr schnell zu einem wirklichen Problem, wenn Sie die Änderungen im gesamten System multiplizieren. Stellen Sie sich vor, dass im gesamten System kleine Änderungen vorgenommen wurden, um die Anforderungen des Kunden zu erfüllen. Das Bereitstellen einer Aktualisierung von Version 1.0 auf 2.0 ist unglaublich schwierig, wenn alle Änderungen, die der Kunde am System vorgenommen hat, beibehalten werden sollen. Dies kann tatsächlich zu einem Aktualisierungsstillstand führen, d.h. der Kunde kann ausschließlich die alte Version der Software verwenden, auch wenn eine Aktualisierung auf die aktuellste Version gewünscht ist. Für Sie bedeutet dies, dass Ihnen nichts anderes übrig bleibt, als zu versuchen, viele verschiedene Versionen Ihrer Software zu unterstützen.

  • Bereitstellen einer Skriptlösung für Ihre AnwendungWenn Sie Skript in Ihre Anwendung integrieren, stellen

    Sie den Kunden dadurch einen Mechanismus zur Anpassung Ihrer Anwendung bereit, so dass Sie Ihren Quellcode nicht unbedingt für die Kunden offen legen müssen. Zudem können Sie in diesem Fall spezielle Anpassungspunkte in Ihrer Anwendung entwerfen. Das Entwerfen von Anpassungspunkten für Ihre Anwendung ist vermutlich der wichtigste (und manchmal zeitaufwendigste) Teil des Anwendungsentwurfs, wenn Sie Ihre Investition in die Anpassung maximieren möchten. Ein gut durchdachter Anpassungsentwurf spart nicht nur Zeit bei der Systemanpassung, sondern gibt Ihnen auch eine gewisse Kontrolle darüber, welche Anpassungen vorgenommen werden können und zu welchem Zeitpunkt. Der Skriptansatz hat gegenüber der Offenlegung von Quellcode den Vorteil, dass die Anpassungen bei Produktaktualisierungen nicht verloren gehen. Der Skriptcode, mit dem die Implementierung der Steuerberechnungskomponente geändert wurde, funktioniert z.B. auch noch, falls Version 2.0 der Komponente installiert ist (natürlich nur, sofern das Objektmodell abwärtskompatibel ist), da die Anpassung Teil des Skripts und nicht der gesamten Implementierung der Komponente ist. VSA stellt einen Mechanismus bereit, mit dem Sie auf den Vorteilen aufbauen können, die die Integration von Skript für .NET Framework in Ihre Anwendung bietet. VSA stellt eine integrierte Entwicklungsumgebung mit umfassenden Features bereit, die Ihre Kunden zum Schreiben und Debuggen von Skripts verwenden können

  • Bereitstellen einer Möglichkeit für Kunden, eigene Komponenten als Plug-Ins in Ihre Anwendung zu integrieren

    Dies ist eine interessante Option, die viele der Vorteile einer Skriptlösung bietet. Sie stellt aber auch eine Reihe von Herausforderungen an Sie, an den Anwendungsentwickler und an die Person, die die Anpassung der Anwendung vornimmt. Diese Möglichkeit ist ein wenig mit dem "Add-In"-Modell vergleichbar, das von Microsoft® Office und Visual Studio bereitgestellt wird. Ihre Anwendung stellt eine Integrationsschnittfläche bereit, an die sich die Komponenten anpassen müssen, sowie einen Mechanismus, mit dem die Add-In-Komponente zum richtigen Zeitpunkt in Ihre Anwendung geladen wird. Die Implementierung dieses Ansatzes kann sehr sinnvoll sein, wenn die Personen, die die Anwendungsanpassung vornehmen, überwiegend erfahrene Entwickler sind, die routinemäßig Komponenten erstellen und mit der Funktionsweise von Schnittstellen vertraut sind. Aus diesem Grund ist dieser Ansatz meiner Ansicht nach ein Ergänzungsfeature zur Skripterstellung, der keineswegs als Alternative zum Hinzufügen einer Skriptlösung zu Ihrer Anwendung angesehen werden sollte. Nachdem dies nun besprochen ist, möchte ich darauf hinweisen, dass bei diesem Ansatz eine Vielzahl an Herausforderungen bei der Anwendungsentwicklung zu bewältigen sind. Insbesondere erfordert die Implementierung der Lösung einen hohen Arbeitsaufwand. Sie müssen eine Integrationsschnittstelle und einen Mechanismus zum Laden der Komponenten in Ihre Anwendung entwickeln sowie eine Dokumentation für den Integrationsmechanismus bereitstellen. Dies muss nicht unbedingt schwierig sein, aber es wäre gar nicht erforderlich, wenn Sie sich für die Skriptinglösung entschieden hätten.

Entwickeln eines anpassbaren Webdienstes 

Zur Veranschaulichung der Verwendung von Skript für .NET Framework und VSA zum Erstellen eines anpassbaren Webdienstes habe ich einen einfachen Kalkulationswebdienst erstellt, der unter scripthappens.com bereitgestellt wird. Ich habe bewusst eine einfache Webdienstimplementierung gewählt, damit ich mich darauf konzentrieren kann, Ihnen die Skriptintegration zu veranschaulichen, anstatt Ihnen den Code zu erläutern, der für die Steuerberechnung erforderlich ist. Der Webdienst verfügt über die beiden Methoden CalculateTax und CalculateDiscount, die vom Auftragsabwicklungssystem in der E-Commerce-Website scripthappens.com aufgerufen werden. Der Dienst selbst ist vollständig in Microsoft® Visual Basic® .NET integriert, könnte aber ebenso gut in einer beliebigen .NET-Sprache entwickelt werden. Der Webdienst hostet ein Skript für das .NET Framework-Modul, um Skripts zum Anpassen der Implementierung auszuführen und um den Skriptautor zu unterstützen, und stellt ein Objektmodell bereit, das den Status der Anforderung darstellt, die an den Webdienst gerichtet wird. Der Webdienst wurde dahingehend entwickelt, dass er von anderen Webanwendungen aufgerufen werden kann, die sich sowohl auf dem Server als auch auf dem Client befinden (unter Verwendung von Internet Explorer Web Services Behavior). Da der Entwurf eine ausschließlich webbasierte Verwendung vorsieht, hielt ich es für wichtig, dass die Integration der integrierten Entwicklungsumgebung (IDE; Integrated Development Environment) von VSA auch von einer Webschnittstelle aus aufgerufen werden kann. Zu diesem Zweck habe ich ein einfaches System erstellt, dass XML-Code und MIME-Dateizuordnungen verwendet, damit die VSA-IDE von einer Webseite aus gestartet werden kann, die in einem Webbrowser gehostet wird. Weiter unten in diesem Artikel werde ich die Funktionsweise näher erläutern.

Im Beispiel wird das VSA Software Development Kit (VSA-SDK) verwendet, um das Skript für das .NET Framework-Modul und die VSA-IDE zu hosten. Das VSA-SDK ermöglicht es der Anwendung, die VSA hostet, das Speicherformat und den Speicherort des Skriptcodes zu bestimmen, den der Benutzer schreibt. Dies ist eines der wichtigsten Entwurfsmerkmale des VSA-SDKs. Damit dies funktioniert, stellen die Hostingklassen, die mit dem SDK geliefert werden, einen Mechanismus für Anwendungsentwickler bereit, mit dem diese einen eigenen Persistenzmechanismus integrieren können. Dies wird erreicht, indem eine Komponente geschrieben wird, die als Codeprovider bezeichnet wird. Dieser Codeprovider implementiert eine Persistenzschnittstelle, ICodeProvider, die im VSA-SDK definiert ist. Die Hostingklassen im VSA-SDK verwenden den Codeprovider für alle Persistenzvorgänge und das Abrufen von Skriptcode in der Anwendung, und zwar sowohl zur Entwurfszeit (von innerhalb der VSA-IDE) als auch zur Laufzeit, wenn das Skript geladen wird.Da die Person, die das Skript in der VSA-IDE schreibt, vermutlich nicht denselben Computer verwendet, auf dem der Skriptcode später gespeichert und ausgeführt wird, ist es wichtig, dass die Codeprovider remote aufgerufen werden. Das VSA-SDK ist also so ausgelegt, dass die Codeprovider remote verwendet werden, so wie Webdienste SOAP über HTTP verwenden.

In dieser Beispielanwendung wird ein Codeprovider als Webdienst für die VSA-IDE und lokal als .NET-Komponente verwendet, wenn der Code zur Laufzeit geladen wird, um den Webdienst anzupassen.

scripting09102001_fig1

Abbildung 1. Architektur des Kalkulationswebdienstes unter "Scripthappens.com"

Da Sie jetzt hoffentlich allgemeine Kenntnisse darüber haben, wie das System als Ganzes zusammenhängt, werden wir uns nun mit den Implementierungsdetails beschäftigen.

Implementieren eines Webdienstes 

Der Kalkulationswebdienst ist als standardmäßiger .NET-Webdienst calculate.asmx implementiert, wobei die beiden Methoden als Funktionen in einer Klasse definiert sind, für die das WebMethod-Attribut gesetzt ist. Das WebMethod-Attribut fordert die ASP.NET-Laufzeit auf, die Methode offen zu legen, damit sie von einem Webdienst aufgerufen wird - soweit noch nichts Besonderes.Damit der Webdienst angepasst werden kann, muss ein Skriptmodul geladen werden, das den vom Benutzer geschriebenen Skriptcode ausführt. In meiner letzten Kolumne habe ich das Skript für die .NET Framework-Schnittstellen direkt verwendet und die Skriptcodequelle jedes Mal geladen. Dies war in diesem Fall gut, da es sich um eine Clientanwendung handelte, bei der die Leistung des Skriptcodes nicht primär im Vordergrund stand. Da dies jedoch ein Webdienst ist, der auf einem Server verwendet wird, ist die Laufzeitleistung ein wichtiger Faktor. Aus diesem Grund nutze ich die Fähigkeit des Skripts für die .NET Framework-Module, vorkompilierten Code zu laden - insbesondere das simple Skriptmodul, das nur vorkompilierten Code laden kann (letzten Monat habe ich es als Ladeprogramm bezeichnet). Glücklicherweise stellt das VSA-SDK eine Laufzeitintegrationsklasse bereit, die das Hosten des Moduls zu einem Kinderspiel macht. Ich habe diese Klasse verwendet, um den Skriptcode in den Webdienst zu integrieren.

Die VSA-SDK-Laufzeitklasse wurde entworfen, um eine Abstraktion der IVsa-Schnittstellen bereitzustellen und die effiziente Integration der Module in Ihre Anwendung zu ermöglichen. Da Ihre Anwendung die Skriptmodule vermutlich in eine große Anzahl von Komponenten integriert, wollten wir sicherstellen, dass möglichst wenig Code für die Verwendung der Klasse erforderlich ist. Ich war besonders ehrgeizig und wollte erreichen, dass für die Integration nur 3 Codezeilen erforderlich sind. (Drei Codezeilen erschienen ein besonderer Anreiz für die minimalistische Codierung zu sein. Die Antwort darauf, warum gerade die Anzahl von 3 Zeilen so wichtig war, ist allerdings im Nebel des VSA-Entwurfsprozesses verloren gegangen.)

Der Konstruktor des Kalkulationswebdienstes erstellt eine Instanz der Laufzeitklasse und des Codeproviders, der verwendet wird, um das kompilierte Skript für die Komponente zu laden. Weiter unten im Artikel werde ich beschreiben, wie der Codeprovider implementiert ist, aber es handelt sich um eine Klasse, die ICodeProvider implementiert und bereits im Projekt enthalten ist, so dass es einfach nur eine Frage der Erstellung einer neuen Instanz ist. Die Laufzeitklasse verfügt über eine Reihe von überladenden Konstruktoren. In diesem Fall habe ich einen Konstruktor verwendet, der meiner Ansicht nach in der Zukunft am häufigsten verwendet werden wird und der den Namen für die Anpassung und des zu verwendenden Monikers annimmt. Der Moniker zählt vermutlich zu den wichtigsten Konzepten in VSA, da er sowohl für das .NET Framework-Skript als auch die VSA-IDE von entscheidender Bedeutung ist. Der Moniker ist im Grunde ein eindeutiger Bezeichner für den Skriptcode, und daher sollten Sie sorgfältig überlegen, wie Sie ihn konstruieren. Der Moniker spielt auch eine wichtige Rolle beim Speichern und Abrufen von Code durch den Codeprovider. Im Wesentlichen übersetzt der Codeprovider einen Moniker in den Speichermechanismus für Ihre Anwendung. Der Codeprovider in diesem Beispiel übersetzt z.B. com.scripthappens://calculate in die Ordnerhierarchie vsa projects\calculate unter dem aktuellen Arbeitsverzeichnis. Nachdem die Instanz der Laufzeitklasse erstellt wurde, muss lediglich noch die Instanz des Codeproviders bereitgestellt werden, den die Klasse zum Abrufen von Code verwendet. Dann muss die Klasse aufgefordert werden, den Code zu laden, und schließlich muss noch der Code ausgeführt werden - und das mit nur drei Codezeilen! (OK, ich habe also nicht das Erstellen der Instanz des Codeproviders oder der Klasse mitgezählt, aber sie sind für meine Zählung der Codezeilen nicht relevant.)

Try
            ' Neue Instanz des Codeproviders erstellen
            myCodeProvider = New DiskCodeProvider()
            ' Neue Instanz der Laufzeitklasse erstellen
            myRTClass = New Runtime("calculator", "com.scripthappens://calculate")
            ' Codeprovider auf die Instanz des Datenträgercodeproviders setzen
            myRTClass.SetCodeProviderLocal(myCodeProvider)
            ' Kompilierten Code laden
            myRTClass.Load(Nothing)
            ' Code ausführen
            myRTClass.Run()
        Catch e As Exception
            ' Eine besonders nutzlose Ausnahme ausgeben, die den Benutzer informiert,
            ' dass etwas völlig schief gegangen ist
            Throw New Exception("Anpassung kann nicht geladen werden")
        End Try

Der Webdienst ist jetzt bereit, jedes beliebige Skript auszuführen, das der Benutzer an den Webdienst übergibt, aber bisher haben wir noch kein Objektmodell bereitgestellt, auf dem die Skripterstellung basiert. Die Laufzeitklasse bietet zwei Möglichkeiten, um dem Modul Objekte hinzuzufügen: AddObject und AddEventSourceObject. AddObject fügt das Objekt dem globalen Bereich des Moduls hinzu, aber der Skriptcode kann nicht auf Ereignisse reagieren, die von diesen Objekten ausgelöst wurden. AddEventSourceObject fügt ein Objekt entweder einer Klasse oder einem Modul hinzu, und für Ereignisse, die von diesem Objekt ausgelöst werden, kann ein Skript erstellt werden, was nicht überraschend ist.

Der Kalkulationswebdienst verfügt der Einfachheit halber über ein Objekt, das Ereignisse auslöst und das als CalculateObjectModel bezeichnet wird (einleuchtender Name, nicht wahr?). Ich möchte Ihnen nicht verschweigen, dass in der Betaversion 2 ein Bug enthalten ist, der bewirkt, dass die Laufzeitklasse überprüft, ob die von Ihnen bereitgestellte Objektinstanz ungleich Null ist. Es wäre sinnvoll, das eventsourceobject-Objekt dem Modul im Konstruktor hinzuzufügen, aber ich wollte einen Konstruktor für das Objekt verwenden, um einige interne schreibgeschützte Eigenschaften zu setzen, basierend auf den Werten, die an die Webmethode übergeben wurden. Da diese Werte nicht zur Konstruktionszeit verfügbar sind, musste ich die addeventsourceobject-Aufrufe in jede Methode verschieben. In der endgültigen Version von VSA findet keine Prüfung des Instanzenwerts statt, so dass ich die Möglichkeit habe, eine leere Instanz zu übergeben und die Instanz zum Zeitpunkt des Methodenaufrufs zu erstellen.

Bereitstellen eines korrekten Objektmodells

Beim Entwerfen des Webdienstes habe ich versucht, mir vorzustellen, wie der Benutzer den Webdienst an seine Anforderungen anpassen würde. Daher hat der Entwurf des Objektmodells auch einige Zeit in Anspruch genommen. Schließlich habe ich mich für ein Modell entschieden, bei dem ein Ereignis vor und nach der Steuerkalkulation ausgelöst wurde. Ich war der Ansicht, dass dieses Modell mehr Flexibilität bieten würde, da es sowohl den Hauptaspekt - Implementieren der eigenen Steuerberechnungsroutine - berücksichtigt als es dem Skriptautor auch ermöglicht, bei Bedarf nach der Kalkulation weitere Verarbeitungsschritte durchzuführen. Nachdem ich mich für das grundlegende Ereignismodell mit Vor- und Nachbearbeitung entschieden hatte, begann ich, darüber nachzudenken, wie ich das Objektmodell implementieren könnte, um das Leben der Skriptautoren zu vereinfachen, ohne dass sich daraus Auswirkungen auf den externen Entwurf des Webdienstes ergeben. Ein wichtiger Entwurfsaspekt ist sicherzustellen, dass der Webdienst in der tatsächlichen Implementierung im Wesentlichen statusfrei bleibt, während ein Objektmodell bereitgestellt wird, das den Zugriff auf den internen Status jedes Webmethodenaufrufs vom Skript aus vereinfacht. Hierzu habe ich eine neue Klasse erstellt, die das für den Skriptautor sichtbare Objektmodell implementiert hat, und diese Klasse innerhalb jeder Webmethode verwendet.

Zur Veranschaulichung werde ich erläutern, wie ich die CalculateTax-Methode in den Webdienst implementiert habe. Diese Methode ist sehr einfach. Bei dieser Methode werden der Betrag und der Bundesstaat, für den die Steuer berechnet wird, als Argumente verwendet. (Ich weiß, dass dies eine sehr U.S.-spezifische Methode ist, aber letztendlich ist es ja nur ein Beispiel.) Die Informationen zum Betrag und Bundesstaat werden außerhalb der Methode nicht beibehalten, um sicherzustellen, dass der Webdienst in der Implementierung weitestgehend statusfrei bleibt. Die statusfreie Programmierung hat in Bezug auf die Skalierung erhebliche Vorteile, macht jedoch die Programmierung nicht unbedingt einfacher - besonders wenn Sie kein erfahrener Programmierer sind, was sehr häufig auf die Personen zutrifft, die das Skript zum Anpassen Ihrer Anwendung schreiben.

Zur Unterstützung der Skriptautoren habe ich ein einfaches Objektmodell erstellt, das den Status, der in der Webmethode dargestellt wird, als schreibgeschützte Eigenschaften (die Werte werden im Konstruktor der Klasse festgelegt) und als eine Gruppe von Eigenschaften mit Lese-/Schreibrechten offen legt, die verwendet werden können, um das Ergebnis der Skriptanpassung zurückzugeben. Neben diesen Eigenschaften legt das Objekt zudem das Ereignismodell offen, das der Skriptautor als Grundlage für die Codierung verwendet. Für die Ereignisse ihm Rahmen der Vor- und Nachbearbeitung habe ich eine einfache Benennungskonvention verwendet: BeforeEreignisname und AfterEreignisname.

Public Class CalculateObjectModel
    ' Elementvariablen zum Speichern von Eigenschaftswerten
    Private m_amount As Decimal
    Private m_state As String
    Private m_taxAmount As Decimal
    Private m_discount As Decimal
    Private m_custID As Integer
    ' Definieren der Ereignisse
    Event BeforeCalculateTax()
    Event AfterCalculateTax()
    Event BeforeCalculateDiscount()
    Event AfterCalculateDiscount()
    ' Konstruktor, wenn zum Festlegen des Betrags und Bundesstaats verwendet
    Public Sub New(ByVal amount As Decimal, ByVal state As String)
        m_amount = amount
        m_state = state
        m_custID = Nothing
    End Sub
    ' Konstruktor, wenn zum Festlegen des Betrags und der Kunden-ID (custID)
    ' verwendet
    Public Sub New(ByVal amount As Decimal, ByVal custID As Integer)
        m_amount = amount
        m_custID = custID
        m_state = Nothing
    End Sub
    ' Gruppe von Eigenschaften, die das Schreiben von Code für den 
    ' Skriptautor einfacher machen
    Public Property Discount() As Decimal
        Get
            Return m_discount
        End Get
        Set(ByVal Value As Decimal)
            m_discount = Value
        End Set
    End Property
    Public Property Tax() As Decimal
        Get
            Return m_taxAmount
        End Get
        Set(ByVal Value As Decimal)
            m_taxAmount = Value
        End Set
    End Property
    Public ReadOnly Property Amount() As Decimal
        Get
            Return m_amount
        End Get
    End Property
    Public ReadOnly Property state() As String
        Get
            Return m_state
        End Get
    End Property
    Public ReadOnly Property CustID() As Integer
        Get
            Return m_custID
        End Get
    End Property
End Class

Bereitstellen des Objektmodells für das Skript

Ein gut durchdachtes Objektmodell ist zwar eine tolle Sache, erfüllt aber keinen Zweck, wenn es dem Skriptmodell nicht bereitgestellt wird und die Ereignisse im Objekt nicht in der korrekten Reihenfolge aufgerufen werden. Die CalculateTax-Methode im Webdienst hat in meinem Beispiel keine so bedeutende Aufgabe. Sie ruft lediglich eine Instanz des Objektmodells auf, löst das BeforeCalculateTax-Ereignis im Objektmodell aus, gibt den Steuerbetrag vom Objekt zurück und löst dann das AfterCalculateTax-Ereignis aus. Es ist klar, dass ein echter Webdienst zwischen dem Auslösen der Ereignisse ein wenig mehr leisten würde.Bei der Implementierung dieser Methode stellte sich mir eine interessante Herausforderung, die Sie vermutlich auch bewältigen müssen, wenn Sie mit dem Schreiben von Code für Ihre Objektmodelle beginnen. Die Herausforderung war: Wie rufe ich ein Ereignis in einer neuen Instanz einer Klasse auf? Normalerweise würden Sie die RaiseEvent-Methode verwenden, um das Ereignis auszulösen, aber leider wird der Code, der das Ereignis auslöst, nicht in der Instanz des Objektmodells ausgeführt, so dass ein gewisses Maß an Dereferenzierung zwischen dem Objektmodell und dem aufrufenden Code erforderlich ist.

Zunächst dachte ich, dass dieses Problem sehr einfach zu lösen ist. Die Lösung bestand einfach darin, eine FireEvent-Methode im Objektmodell zu implementieren und den Namen des Ereignisses, das ausgelöst werden soll, zu übergeben. Dies funktioniert gut, hat jedoch einen bedauerlichen Nebeneffekt: Die FireEvent-Methode ist für den Skriptautor sichtbar, was zu einigen sehr interessanten festgefahrenen Situationen führen könnte. Stellen Sie sich Folgendes vor: Der Ereignishandler für BeforeCalculateTax hat die FireEvent-Methode mit BeforeCalculateTax als Ereignisnamen aufgerufen - dies hätte zahlreiche Endlosschleifen und sehr viel "Zähneknirschen" zur Folge. Glücklicherweise kann dies einfach umgangen werden, indem die FireEvent-Methode als interne Methode oder als "Freund" verwendet wird, um es in der Visual Basic-Terminologie auszudrücken. Dies bedeutet, dass die Methode nur für den Code sichtbar ist, der in derselben Assembly ausgeführt wird, und daher für den Skriptcode unsichtbar ist.

<WebMethod()> Public Function CalculateTax(ByVal amount As Decimal,_
 ByVal state As String) As Double
        ' Instanz des Objektmodells für das Skript erstellen
        ' und den Betrag und Bundesstaat im Konstruktor festlegen
        CalcOM = New CalculateObjectModel(amount, state)
        Try
            ' Workaround für VSA Betaversion 2, da in dieser Version
            ' keine leere Instanz von EventSourceObject übergeben 
            ' werden kann, bevor das Modul ausgeführt wird
            myRTClass.AddEventSourceObject("CalculateCustomization", "CalcOM", CalcOM)
            ' Modul muss zurückgesetzt werden, um sicherzustellen, dass
            ' das neue eventsourceobject verfügbar ist
            myRTClass.Reset()
            ' Code erneut ausführen
            myRTClass.Run()
        Catch e As Exception
            ' Ausnahme ausgeben
            Throw e
        End Try
        ' FireEvent-Methode im Objektmodell aufrufen, um das
        ' BeforeCalculateTax-Ereignis auszulösen
        CalcOM.FireEvent("BeforeCalculateTax")
        ' In einer echten Anwendung würden Sie an dieser Stelle 
        ' natürlich einige generische Berechnungen durchführen
        ' $1 zur Steuer hinzufügen, um zu zeigen, dass etwas geschehen ist
        CalcOM.Tax += 1
        ' FireEvent-Methode im Objektmodell aufrufen, um das 
        ' AfterCalculateTax-Ereignis auszulösen
        CalcOM.FireEvent("AfterCalculateTax")
        ' Steuerbetrag zurückgeben, der mittels des Objektmodells berechnet wurde
        Return CalcOM.Tax
    End Function

Wenn der Webdienst aufgerufen wird, implementiert der Skriptcode tatsächlich die Steuerberechnung, und der Webdienst gibt das Ergebnis des Skripts über die Tax-Eigenschaft des Objektmodells zurück. Alles, was jetzt noch getan werden muss, ist es dem Skriptautor mit Hilfe einer Implementierung zu ermöglichen, das Skript tatsächlich schreiben und auf dem Server speichern zu können.

Bereitstellen einer Entwicklungsumgebung für das Skript 

Einer der Hauptvorteile, die das Skript für .NET Framework und VSA gegenüber dem Microsoft® Windows®-Skript bietet, ist die mit umfassenden Features ausgestattete VSA-IDE. Die VSA-IDE stellt eine erstklassige Bearbeitungs- und Debuggingumgebung für Skriptautoren bereit, die diese zum Erstellen ihrer Skriptanpassungen verwenden können. Der unter scripthappens.com bereitgestellte Kalkulationswebdienst nutzt die Vorteile der VSA-IDE sowohl zum Bearbeiten als auch zum Debuggen des Skriptcodes. Sie können die VSA-IDE in Ihre Anwendung integrieren, indem Sie das Entwurfszeitmodul für die Skriptsprache einsetzen, die Sie verwenden möchten. (In Version 1.0 von VSA konnten wir aus Zeitgründen nur ein Entwurfszeitmodul für Visual Basic .NET bereitstellen.)

Der wichtigste Entwurfsaspekt für das Entwurfszeitmodul war, dass es über dieselbe Schnittstelle wie das Skriptmodul (IVsa) verfügen sollte, um z.B. Codeelemente und Objekte zu verwalten (damit Sie sich für allgemeine Funktionen nicht mit verschiedenen Hostingschnittstellen auseinander setzen müssen). Dennoch sollten Sie zusätzlich eine Gruppe von Entwurfszeitschnittstellen (IVsaDT) implementieren, um den Zugriff auf die VSA-IDE zu ermöglichen. Das VSA-SDK stellt eine Entwurfszeit-Integrationsklasse bereit, die die Integration der VSA-IDE vereinfacht. Dies geschieht auf ähnliche Weise wie bei der Laufzeitklasse, die das Hosten von Skriptmodulen vereinfacht.

Da der Webdienst nicht über einen dedizierten Windows-Client verfügt, habe ich einen Mechanismus entworfen, mit dem die VSA-IDE von einem Webbrowser aus instanziiert werden kann. Da die VSA-IDE von einer Gruppe von Schnittstellen gesteuert wird, konnte die IDE nicht von einem Skript innerhalb einer Webseite aufgerufen werden. Windows-Skript kann Objekte nur über IDispatch aufrufen, und die VSA-IDE führt ggf. Vorgänge durch, die ein mögliches Sicherheitsrisiko darstellen (z.B. Schreiben und Lesen von einem Datenträger), so dass dies aufgrund der Sicherheitsarchitektur in den meisten Websites nicht funktioniert. Damit der Zugriff auf die VSA-IDE von innerhalb eines Browsers erfolgen kann, habe ich das Internet Explorer-Feature zur MIME-Behandlung genutzt, um eine .NET Windows Forms-Anwendung zu erstellen, eine XML-Datei (generiert von einem Webserver) zu interpretieren und die IVsaDT-Schnittstellen zum Starten der VSA-IDE zu verwenden. Bevor ich genauer erläutere, wie das Programm diese Aufgabe bewältigt, muss an dieser Stelle noch beschrieben werden, wie die MIME-Handler von Microsoft® Internet Explorer in diesem Fall helfen können.

Internet Explorer verwendet die integrierte Windows-Funktion, die das Verknüpfen von MIME-Inhaltstypen mit Anwendungen ermöglicht. Die MIME-Inhaltstypen stellen eine Obermenge der bekannten Dateinamenerweiterungs-Zuordnung in Windows dar, mit der Sie vermutlich vertraut sind. Ich habe einen MIME-Inhaltstyp sowie die VSA-Konfigurationsdatei erstellt und verknüpfte den Inhaltstyp mit der Dateinamenerweiterung .vsa. Mit Hilfe des Dateityp-Editors, der über die Systemsteuerung für Ordneroptionen aufgerufen werden kann, habe ich den MIME-Inhaltstyp mit der .NET-Anwendung verknüpft, die die VSA-IDE hostet. Dies hat zur Folge, dass die VSA-IDE-Hostanwendung jedes Mal gestartet wird, wenn ein .vsa- oder VSA-Konfigurationsdatei-MIME-Inhaltstyp gedownloadet wird. Die VSA-IDE-Hostanwendung interpretiert dann den XML-Code in der Datei und verwendet die in der XML-Datei enthaltenen Informationen, um den Skriptcode zu laden und die VSA-IDE anzuzeigen. Dieser Ansatz stellt wahrlich kein High-Tech-Verfahren dar und ist in vielerlei Hinsicht vom technischen Standpunkt aus sehr einfach.

Nichtsdestotrotz stellt er eine erweiterbare und elegante Lösung dar, die verwendet werden kann, um die VSA-IDE von innerhalb eines Webbrowsers zu hosten. Bei diesem Ansatz gibt es jedoch ein offensichtliches Problem: Wie schaffen Sie es, die MIME-Zuordnung und die Anwendung auf den Computern der Benutzer bereitzustellen, falls diese eine VSA-Datei downloaden, bevor Sie Ihre Anwendung eingerichtet haben? Glücklicherweise hat Windows für dieses Problem eine hervorragende Lösung parat. Diese Lösung funktioniert aber nur, wenn sich die Benutzer in einem Intranet mit einem Domänenserver befinden.Wenn Ihre Computer mit einer Domäne verbunden sind, können Sie die MIME- und Dateinamenerweiterungs-Handler bereitstellen. Wenn Benutzer, die die Software noch nicht installiert haben, eine Datei für einen Dateityp downloaden, für die ein Handler bereitgestellt wurde, installiert Windows diesen Handler automatisch für diese Benutzer. Somit können Sie sicherstellen, dass für Ihre Benutzer immer die VSA-IDE gestartet wird, wenn sie eine VSA-Datei downloaden.

Weitere Informationen zum Einrichten dieser Lösung finden Sie unter "http://technet.microsoft.com/windowsserver/2000/default.aspx">Step-by-Step Guide to Software Installation and Maintenance (in Englisch).

Hosten der VSA-IDE

Da wir jetzt wissen, wie die MIME-Handler einen Mechanismus bereitstellen können, um eine Unterstützung für das Hosten der VSA-IDE von einem Webbrowser aus bereitzustellen, befassen wir uns nun damit, wie das .NET Windows Forms-Programm die vom VSA-SDK bereitgestellte Entwurfszeitklasse zur Kommunikation mit der VSA-IDE und dem Codeprovider tatsächlich verwendet, um den Skriptquellcode und den kompilierten Code abzurufen und zu speichern.Der Schlüssel zur Hostanwendung ist in diesem Beispiel der in der VSA-Datei enthaltene XML-Code. Das XML-Schema, das wir für das Beispiel gewählt haben, stellt alle Informationen bereit, die erforderlich sind, um den Skriptcode und das Objektmodell für das Skript zu laden. Natürlich hätten wird das XML-Schema in der Anwendung hartcodieren können, da das Objektmodell des Webdienstes weitgehend bekannt ist. Wir waren jedoch der Ansicht, dass es wichtiger sei, ein generisches System zu entwickeln, das als Grundlage für zukünftige VSA-Projekte verwendet werden kann. Wenn wir z.B. tatsächlich ein System für scripthappens.com entwickeln würden, wäre es schön, wenn wir die Lösung für zukünftige Projekte wieder verwenden könnten.

Beispiel-XML-Schema

<application 
   name="calculator"
   targeturl="http://localhost/ScriptHappens/default.aspx"
   moniker="com.scripthappens://calculate"           
   language="Microsoft.VisualBasic.Vsa"
   codeProviderURL="http://localhost/scripthappens/codeprovider.asmx" >
   <reference 
   name="ScriptHappens" 
   assembly="C:\\Inetpub\\wwwroot\\scripthappens\\bin\\scripthappens.dll" 
   /> 
   <class name="Calculate" >
      <event name="calculateObjectModel" type = "scripthappens.calculateObjectModel" 
/>
   </class>
</application>

Der Hauptteil dieses Schemas ist das Anwendungselement. Es enthält alle Informationen, die sich auf den Anwendungsbereich beziehen, einschließlich Moniker und Name der Anpassung, was noch aus der Laufzeitintegration bekannt sein sollte. Die Entwurfszeit führt neben der Laufzeit eine Reihe von Konzepten ein - insbesondere das targetURL-Attribut und den URL für den Codeprovider-Webdienst. Das targetURL-Attribut wird verwendet, wenn der Benutzer eigenen Code innerhalb der VSA-IDE aufruft. Die VSA-IDE kann nicht einfach nur den Code ausführen, da der vom Benutzer geschriebene Skriptcode von Ihrer Anwendung und nicht von der IDE gehostet wird.

Daher kommuniziert die IDE mit der Hostinganwendung und fordert sie auf, die Schritte durchzuführen, die zum Laden des Skriptcodes erforderlich sind. Da dieses Beispiel veranschaulicht, wie Skript in einem Webdienst verwendet wird, wird das targetURL-Attribut auf den URL einer Webseite gesetzt, die den Kalkulationswebdienst aufruft. Das bedeutet, dass durch das Ausführen des Codes in der VSA-IDE der Kalkulationswebdienst gestartet wird, und alle vom Benutzer im Skriptcode gesetzten Haltepunkte veranlassen die IDE, den Skriptcode zu debuggen, wenn der Webdienst ausgeführt wird.Die VSA-Entwurfszeitklasse verwendet einen Codeprovider, um die Persistenz des Skriptcodes zu behandeln, aber anstatt eine lokale Instanz zu verwenden, ruft sie den Codeprovider als Webdienst auf. Dies ist ein wichtiger Entwurfsaspekt für das VSA-SDK. Wir wollten sicherstellen, dass die Persistenz des Codes so offen wie möglich ist, und legten besonderen Wert auf die Fähigkeit, mit Remotecodespeichern zu arbeiten, die sich ggf. hinter Firewalls befinden, da dies ein wichtiges Szenario für uns ist.

Wenn Sie einen Codeprovider über einen Webdienst aufrufen möchten, müssen Sie nur die SetCodeProviderRemote-Methode verwenden und den URL für den Webdienst bereitstellen, der ICodeProviderimplementiert. Im Beispiel habe ich den Codeprovider in codeprovider.asmximplementiert. Bei diesem Beispiel ist interessant, dass der Kalkulationswebdienst den Codeprovider lokal verwendet. Das heißt, dass er die Instanz nicht über SOAP erstellt, sondern dieselbe Implementierung wie der Webdienst verwendet.

Einrichten des Entwurfszeitmoduls 

Die Informationen vom Anwendungselement werden beim Erstellen der Entwurfszeitklasse verwendet. Der Entwurf der Entwurfszeitklasse unterscheidet sich ein wenig von dem der Laufzeitklasse. Die Entwurfszeitklasse muss der Hostanwendung die Verwaltung vieler Module ermöglichen, da die VSA-IDE verwendet wird, um den Code im gesamten System zu bearbeiten, und im System ggf. mehrere Module verwendet werden, um die Anpassung bereitzustellen. Wenn diese Lösung z.B. zwei Webdienste umfassen würde, dann enthielte jeder Webdienst ein Skriptmodul. In der VSA-IDE müssten dann beide Skriptprojekte angezeigt werden. Die Entwurfszeitklasse ermöglicht also das einfache Erstellen von mehreren Entwurfszeitmodulen. Anstatt über die Schnittstellen des Moduls eine Abstraktion bereitzustellen, gibt die Entwurfszeitklasse einfach eine Instanz des Moduls zurück, die Sie dann mit Hilfe von IVsa und IVsaDT zum Programmieren verwenden. Zur Veranschaulichung möchte ich Ihnen schrittweise den Code erläutern, der erforderlich ist, um ein Entwurfszeitmodul startklar zu machen.

Das XML-Schema wird im Load-Ereignis des Windows-Formulars für die Hostinganwendung analysiert. Zum Analysieren des XML-Codes habe ich den im Lieferumfang von .NET Framework enthaltenen XML-Parser verwendet und einfach alle Attribute des Anwendungselements durchlaufen und die Werte für eine zukünftige Verwendung mit der Entwurfszeitklasse gespeichert. Das Programm verwendet das Windows Form Progress Bar-Steuerelement und setzt den Statuswert beim Lesen der Attribute, um dem Benutzer ein Feedback darüber zu geben, was geschieht. Dies ist sehr einfach, aber dennoch ziemlich effektiv.

' XML-Datei öffnen, die in der Befehlszeile übergeben wurde
                Dim xml As XmlDocument
                xml = New XmlDocument()
                xml.Load(args(1))
                Dim node As XmlNode
                node = xml.SelectSingleNode("application")
                Me.ProgressBar1.Value = 5
                ' Leerzeichen ignorieren
                '        myXML.WhitespaceHandling = WhitespaceHandling.None
                Me.ProgressBar1.Value = 10
                ' Name abrufen
                custName = node.Attributes.ItemOf("name").Value
                Me.ProgressBar1.Value = 15
                ' targeturl abrufen
                targetUrl = node.Attributes.ItemOf("targeturl").Value
                Me.ProgressBar1.Value = 20
                ' Moniker abrufen
                moniker = node.Attributes.ItemOf("moniker").Value
                Me.ProgressBar1.Value = 25
                ' Modulsprache abrufen
                language = node.Attributes.ItemOf("language").Value
                Me.ProgressBar1.Value = 30
                ' Codeprovider-URL abrufen
                codeProviderURL = node.Attributes.ItemOf("codeProviderURL").Value
                Me.ProgressBar1.Value = 35

Nachdem alle Informationen für das Anwendungselement analysiert wurden, verfügt das Programm über ausreichende Informationen, um die Entwurfszeitklasse zu starten. Im scripthappens.com-Beispiel habe ich eine Klasse zur Erweiterung der VSA-Entwurfszeitklasse erstellt, um die Integration zu vereinfachen. Ich empfehle, beim Verwenden der Entwurfszeitklasse ähnlich vorzugehen. Diese Klasse verfügt über einen Konstruktor, der alle im Anwendungselement enthaltenen Informationen aufnimmt. Es muss also lediglich eine Instanz der Klasse erstellt werden, und alle Informationen müssen an den Konstruktor übergeben werden.

' Instanz der webhost-Klasse erstellen
                myhost = New dthost(Me, custName, moniker, targetUrl, language, 
codeProviderURL)
                Me.ProgressBar1.Value = 60

Der Konstruktor für die dthost-Klasse verwendet den Codeprovider-URL, um den Codeprovider für die Entwurfszeitklasse einzurichten, die den Skriptcode lädt. Als Vorsichtsmaßnahme prüft die Klasse, ob der URL den Wert Null hat. Trifft dies zu, wird der lokale Codeprovider verwendet. Nachdem der Codeprovider eingerichtet wurde, verfügt die Entwurfszeitklasse über alle Informationen, die erforderlich sind, um ein VSA-Projekt zu laden oder zu erstellen, so dass unbesorgt ein neues Entwurfszeitmodul erstellt werden kann. Die Entwurfszeitklasse wurde so entworfen, dass problemlos mit mehreren Modulen gearbeitet werden kann, indem eine Auflistung von Modulen, VsaEngines, bereitgestellt wird. Diese Auflistung verfügt über die create-Methode, die ein neues Modul zurückgibt und es der Auflistung hinzufügt. Zur Vereinfachung wird in diesem Beispiel nur ein Modul verwendet, aber die Auflistung kann für Sie sehr hilfreich sein.

'------------------------------------------------------------------
            ' Codeprovider für dieses Modul festlegen
            '------------------------------------------------------------------
            If "" = strCodeProviderURL Then
                SetCodeProviderLocal(New DiskCodeProvider())
            Else
                SetCodeProviderRemote(strCodeProviderURL)
            End If

Nachdem der Codeprovider festgelegt wurde, prüft die Hostingklasse, ob für den bereitgestellten Moniker bereits ein Projekt vorhanden ist. Dies kann auf sehr einfache Weise erreicht werden, indem die LoadEngineSource-Methode der Basis-Entwurfszeitklasse des VSA-SDKs aufgerufen wird und alle Ausnahmen aufgefangen werden. Wenn das Projekt bereits vorhanden ist, dann sind wir schon fast fertig, da das Projekt alle Informationen enthält, die für das Objektmodell und die Verweise erforderlich sind. Wenn ein Projekt jedoch nicht vorhanden ist, müssen wir das alte Modul aus der Auflistung entfernen und dann ein völlig neues Modul erstellen. Das neue Modul wird von jetzt ab verwendet, um Code, das Objektmodell und auch Verweise hinzuzufügen. Zu diesem Zeitpunkt gibt es nichts, was wir noch machen möchten, um das Modul zu initialisieren. Der Code ruft also die InitNew-Methode im Modul auf. Die Methode informiert das Modul darüber, dass die Initialisierung abgeschlossen ist und dass das Modul die Erstellung des neuen Moduls fortsetzen kann.

Try
                '------------------------------------------------------------------
                ' Versuch, unser Modul zu laden
                '------------------------------------------------------------------
                LoadEngineSource(strMoniker, Nothing)
                '------------------------------------------------------------------
                ' Bei Erfolg newEngine auf False setzen
                '------------------------------------------------------------------
                newEngine = False
            Catch e As Exception
                '------------------------------------------------------------------
                ' Laden fehlgeschlagen, dies ist ein völlig neues Modul
                '------------------------------------------------------------------
                VsaEngines.Remove(strMoniker)
                dtEngine = VsaEngines.Create(strMoniker, strLanguage, Nothing)
                engine = dtEngine
                engine.InitNew()
                '------------------------------------------------------------------    
                ' Modulnamen festlegen
                '------------------------------------------------------------------
                engine.Name = strCustName
            End Try

In der Hostanwendung wird eine einfache Prüfung der NewEngine-Eigenschaft der Instanz der dtClass-Klasse durchgeführt. Wenn es ein neues Modul gibt, dann müssen dem Modul Verweise und das Objektmodell hinzugefügt werden. Ansonsten sind keine weiteren Vorgänge erforderlich, da das Projekt bereits geladen wurde.

Hinzufügen von Verweisen und des Objektmodells zum Modul 

Zu diesem Zeitpunkt verfügen wir über ein Entwurfszeitmodul zum Hinzufügen des Quellcodes, Objektmodells und der Verweise, die für das Projekt erforderlich sind. Das von der Hostanwendung verwendete XML-Schema enthält Informationen über alle Verweise, die dem VSA-Projekt hinzugefügt wurden. Als Erstes muss also das XML-Schema dem Modul hinzugefügt werden. Das Hinzufügen eines Verweises zu einem Entwurfszeitmodul unterscheidet sich nicht vom Hinzufügen von Skript für das .NET Framework-Modul. Beide Module implementieren IVsa. Es muss lediglich die CreateItem-Methode mit einem Element vom Typ VsaItemType.Reference aufgerufen und dann AssemblyName auf das Element gesetzt werden, das als Pfad der Assembly verwendet werden soll. Der XML-Code enthält sowohl den Namen als auch den Pfad des hinzuzufügenden Verweises, so dass dies recht unkompliziert ist. Zur Vereinfachung des Vorgangs legt die dtHost-Klasse diese Funktionen mit Hilfe einer AddReference-Methode offen, die den Namen und den Pfad entgegennimmt.

'Verweise abrufen
                nodeList = node.SelectNodes("reference")
                For Each subNode In nodeList
                    refName = subNode.Attributes.ItemOf("name").Value
                    refAssembly = subNode.Attributes.ItemOf("assembly").Value
                    myhost.AddReference(refName, refAssembly)
                Next

Nachdem dem Modul alle Verweise hinzugefügt wurden, muss nur noch das Objektmodell hinzugefügt werden, das der Skriptautor beim Codieren verwendet. Dem Modul können Objekte hinzugefügt werden, indem die AddEventSource-Methode für ein Codeelement im Modul aufgerufen wird. In dem in diesem Beispiel verwendeten XML-Code sind alle Objekte Ereignisquellobjekte, die den Klassenelementen hinzugefügt werden. Zur Unterstützung der Implementierung stellt die dtHost-Klasse eine Abstraktion der Codeelementerstellung bereit und fügt Ereignisquellobjekte hinzu.

                Dim nodeList As XmlNodeList
                nodeList = node.SelectNodes("class")
                Dim classNode As XmlNode
                Dim stepit As Integer
                Dim eventNodeList As XmlNodeList
                stepit = 20 / nodeList.Count
                For Each classNode In nodeList
                    className = classNode.Attributes.ItemOf("name").Value
                    myhost.AddClass(className)
                    eventNodeList = classNode.SelectNodes("event")
                    Dim eventNode As XmlNode
                    For Each eventNode In eventNodeList
                        eventName = eventNode.Attributes.ItemOf("name").Value
                        eventType = eventNode.Attributes.ItemOf("type").Value
                        myhost.AddEvent(className, eventName, eventType)
                    Next
                    Me.ProgressBar1.Value = Me.ProgressBar1.Value + stepit
                Next

Objektmodellimplementierung und Leistung auf dem Server 

Die Entscheidung darüber, wie ein Objektmodell dem Modul hinzugefügt wird, ist sehr wichtig, wenn das Skript auf einem Server ausgeführt wird, bei dem die Geschwindigkeit und Skalierbarkeit des Skriptcodes von großer Bedeutung sind. Ein VSA-Modul kann Objekte auf zwei Arten hinzufügen: als globale Objekte (die keine Ereignisse auslösen können) und als Ereignisquellobjekte. Globale Objekte sind, was nicht überraschend ist, im Skriptcode global zugeordnet und werden daher statisch definiert. Statische Objekte eignen sich für die Ausführung in einer Singlethreadumgebung, aber bei Verwendung in einer Multithreadumgebung müssen wichtige Aspekte berücksichtigt werden.In einer Multithreadumgebung, z.B. wenn der Code auf einem ASP.NET-Server ausgeführt wird, können potenziell sehr viele Instanzen des Skripts gleichzeitig ausgeführt werden, und jede statische Variable, die im Skript deklariert ist, wird gemeinsam von allen Skripts verwendet. Wenn sich also ein globales Objekt in einem Skript befindet, wird dieselbe Instanz des Objekts von allen Instanzen des ausgeführten Skripts gemeinsam genutzt.Die sich daraus ergebenden potenziellen Probleme werden deutlich, wenn Sie sich z.B. vorstellen, dass im Skript ein globales Objekt definiert ist, das über eine einfache Zeichenfolgeneigenschaft namens "Titel" verfügt. Die erste Instanz des Skripts setzt die Titel-Eigenschaft auf "Hallo Welt" und gibt dann den Wert der Eigenschaft zurück. Dies funktioniert problemlos, wenn nur eine Instanz des Skripts ausgeführt wird. Stellen Sie sich nun dasselbe Szenario erneut vor, aber dieses Mal wird zwischen dem Festlegen der Eigenschaft und dem Zurückgeben des Wertes ein zweites Modul mit demselben Objektmodell, jedoch mit einem etwas anderen Skript geladen. Die erste Zeile des zweiten Skripts setzt die Titel-Eigenschaft auf "Gruß von Skript 2". Wenn das erste Skript den Wert der Eigenschaft zurückgibt, dann handelt es sich hierbei um den Wert "Gruß von Skript 2", weil die Instanz des Objektmodells von den Skripts gemeinsam genutzt wird. Die Situation verschlimmert sich sogar noch, wenn beide Skripts versuchen, gleichzeitig auf die Eigenschaft zuzugreifen - keine schöne Vorstellung.

Glücklicherweise gibt es die Möglichkeit der Kontextisolation, um mit statischen Variablen in einer Multithreadumgebung zu arbeiten. Die Kontextisolation stellt sicher, dass die statischen Variablen nicht von allen Instanzen gemeinsam genutzt werden. VSA verwendet diesen Mechanismus um sicherzustellen, dass die oben beschriebenen Konfliktprobleme nicht bei Ihnen auftreten. Die Kontextisolation vermeidet diese Konflikte, indem für jeden Thread eine Kopie der statischen Variablen erstellt wird. Der in den einzelnen Threads ausgeführte Skriptcode bemerkt keinen Unterschied, aber er sieht die Kopie der statischen Variablen, die die Kontextisolation für den Thread bereitgestellt hat.

Leider hat auch die Kontextisolation ihren Preis. Das Erstellen einer Kopie von jeder statischen Variable und das Bereitstellen einer Infrastruktur, die für das Arbeiten mit all den Kopien erforderlich ist, bewirkt einen erheblichen Leistungsabfall. In den von uns durchgeführten Leistungstests zeigte sich eine Leistungsminderung von bis zu 50% im Vergleich zur Verwendung von Instanzvariablen.Auf Servern sollten also möglichst keine statistischen Variablen verwendet werden, um Leistungseinbußen zu vermeiden. Glücklicherweise haben wir uns mit diesem Problem beim Entwurf von VSA beschäftigt, da das Erstellen von Anpassungscode, der effizient auf dem Server ausgeführt wird, für diese Version die höchste Priorität besaß. Es gibt zwei Möglichkeiten, wie Sie VSA ein Objektmodell hinzufügen können, wobei die Variablen statisch deklariert werden: Globale Objekte und Ereignisquellobjekte, die in Modulen enthalten sind. Das Hinzufügen von Ereignisquellobjekten zu Klassenelementen führt jedoch zu Instanzvariablen, bei deren Verwendung keine Leistungsprobleme auftreten. Vielleicht fragen Sie sich, warum die Ereignisquellobjekte in den Modulen statisch sein müssen. Der Grund ist, dass es sich bei allen Variablen, die in Modulen deklariert sind, um statische Variablen handelt. Daher empfehle ich Ihnen dringend - was hoffentlich nicht allzu überraschend für Sie ist - Ereignisquellobjekte in Klassen für das gesamte Objektmodell zu verwenden, das Sie für Skripts bereitstellen, die auf dem Server ausgeführt werden. Wenn Sie keine statischen Objekte verwenden, stellt VSA sicher, dass die Deklaration der Variablen das ContextStatic-Attribut enthält, das .NET auffordert, die Kontextisolation zu erstellen, damit bei keinem Skriptcode Konfliktprobleme auftreten. (Die Geschwindigkeit ist viel geringer als nötig.) Daher werden alle Ereignisquellobjekte im Beispiel den Klassen hinzugefügt, um mit gutem Beispiel voranzugehen.

                Me.ProgressBar1.Value = 90
                myhost.InitCompleted()
                Me.ProgressBar1.Value = 100
                Button1.Visible = True

Bevor das Modul verwendet werden kann, muss dem Modul jetzt nur noch mitgeteilt werden, dass die Initialisierung abgeschlossen ist, indem die InitCompleted-Methode aufgerufen wird.

            '  Das Modul ist bereit zur Verwendung, daher InitCompleted 
aufrufen
            dtEngine.InitCompleted()

Anzeigen der IDE 

Das VSA-Entwurfszeitmodul verfügt nun über alle erforderlichen Informationen, um es dem Benutzer ermöglichen, mit dem Bearbeiten des Skripts zu beginnen. Daher muss die Hostanwendung das Modul auffordern, die IDE sichtbar zu machen. Dies ist kinderleicht, denn hierzu muss lediglich die ShowIDE-Methode im Modul aufgerufen werden. Zur Erhöhung der Stabilität verfügt die dtHost-Klasse über eine Show-Methode, die einfach die ShowIDE-Methode aufruft, allerdings bindet sie die Methode in einen 'Try Catch'-Codeblock ein, um alle Ausnahmen abzufangen und die entsprechende Ausnahmen an die Anwendung unter Verwendung der dtHost-Klasse auszugeben. Nach dem Anzeigen der IDE minimiert die Hostanwendung sich selbst, damit der Benutzer die VSA-IDE sehen kann.

                'VSA-IDE zeigen
                myhost.Show()
                'Anwendung minimieren, damit der Benutzer die VSA-IDE sehen kann
                Me.WindowState = FormWindowState.Minimized

Schreiben von Skriptcode 

Der große Vorteil, den die Verwendung einer Entwicklungsumgebung mit umfassenden Features zum Schreiben von Anwendungsskripts bietet, ist der, dass Sie nicht mehr eine eigene Entwicklungsumgebung erstellen müssen (was meistens doch nur irgendeine Variation eines Textfelds ist). Das Schreiben des Skripts ist sehr viel einfacher, da Sie sich nicht mehr damit beschäftigen müssen, welche Objekte verfügbar sind, da die IDE alle Informationen für Sie bereitstellt.Wenn die VSA-IDE nach dem Aufrufen der Show-Methode zum ersten Mal gestartet wird, werden für den Benutzer alle Projektinformationen angezeigt, die vom XML-Code hinzugefügt wurden, und in einem Codefenster wird die Klasse angezeigt, die dem Projekt hinzugefügt wurde.

scripting09102001_fig2

Abbildung 2. In der VSA-IDE werden das Projekt und die Klasse angezeigt

Wenn der Skriptautor den Skriptcode zum Anpassen des Kalkulationsobjekts (calculateObjectModel) schreiben möchte, muss er hierzu zunächst das Ereignis auswählen, für das der Skriptcode geschrieben werden soll. Dies ist in der VSA-IDE ganz einfach. Wählen Sie hierzu einfach in der Objektliste oben im Code-Editor das calculateObjectModel-Objekt und dann das Ereignis in der Ereignisliste aus.

scripting09102001_fig3

Abbildung 3. Objektlistenfeld

scripting09102001_fig4

Abbildung 4. Ereignisinformationen

Durch das Auswählen des Ereignisses wird der Klasse automatisch ein Ereignishandler hinzugefügt, so dass der Skriptautor mit dem Schreiben des Skripts beginnen kann.

scripting09102001_fig5

Abbildung 5. Hinzugefügter Ereignishandler

Auch das Zugreifen auf das von der Anwendung bereitgestellte Objektmodell ist sehr einfach, da die VSA-IDE eine umfassende Microsoft® IntelliSense®-Unterstützung für Objekte bietet, die für das VSA-Modul bereitgestellt werden. Der Benutzer muss lediglich den Namen des Objekts und einen Punkt eingeben. Daraufhin wird in der VSA-IDE eine Liste aller Elemente des Objekts angezeigt. Wenn Sie bereits mit Visual Basic for Applications (VBA) gearbeitet haben, ist dies für Sie nichts Neues, aber für einen Skriptbenutzer ist das großer Schritt nach vorn.

scripting09102001_fig6

Abbildung 6. Elementinformationen für "calculateObjectModel"

Speichern des Codes 

Wenn Sie mit dem von Ihnen geschriebenen Code zufrieden sind, müssen Sie ihn speichern können. Einer der Hauptvorteile von VSA und von Skript für .NET Framework ist, dass die Anwendung, die das Skript hostet, darüber entscheidet, wo der Code gespeichert wird. Der Entwickler muss sich um nichts kümmern, sondern nur auf die Schaltfläche Speichernklicken. Wenn Sie auf Speichern klicken, stellt die VSA-IDE eine Verbindung zur Hostinganwendung her und übergibt den gesamten Code, den Sie geschrieben haben, damit die Hostinganwendung den Code speichern kann. Sie bestimmen ganz allein, wo die Hostinganwendung den Code in Ihre Anwendung einfügt.Viele Entwickler entscheiden sich dafür, den Code in einer Datenbank zu speichern. Zur Vereinfachung speichert der Kalkulationswebdienst unter scripthappens.com den gesamten Code auf dem Datenträger in einem Ordner im virtuellen Stammverzeichnis scripthappens.Das VSA-SDK verwaltet den Großteil der Infrastruktur, die zum Speichern des Codes erforderlich ist, indem der Codeprovider zum Durchführen aller Persistenzvorgänge verwendet wird. Der Code muss nur noch entscheiden, ob der Quellcode oder der kompilierte Code gespeichert werden soll. Normalerweise ist es sinnvoller, den Quellcode zu speichern, besonders dann, wenn Sie Serveranpassungen schreiben. Im Beispielcode stellt die dtHost-Klasse eine SaveVsaEngine-Methode bereit, die zunächst den Quellcode speichert, dann den Quellcode kompiliert (nur falls dies noch nicht geschehen ist) und schließlich den kompilierten Code speichert.

Sub SaveVsaEngine()
        Try
            '------------------------------------------------------------------
            ' Quellcode speichern
            '------------------------------------------------------------------        
            SaveEngineSource(engine.RootMoniker, Nothing)
            '------------------------------------------------------------------
            ' Anpassungscode kompilieren und speichern
            '------------------------------------------------------------------
            If engine.Compile() Then
                SaveEngineCompiledState(engine.RootMoniker, Nothing)
            End If
        Catch e As Exception
            MsgBox("Fehler beim Speichern des Moduls") ' & e.StackTrace)
        End Try
End Sub

Wenn die Methoden SaveEngineSource und SaveEngineCompiledState in der VSA-SDK-Entwurfszeitklasse aufgerufen werden, ruft die Klasse mit Hilfe von SOAP die Methoden PutSourceCode und PutBinaryCode des Codeproviders auf, um den Code zu speichern.

Ausführen des Codes 

Der Skriptcode wurde mittels des Codeproviders geschrieben und im Codespeicher gespeichert. Jetzt muss nur noch der Code ausgeführt werden, um zu überprüfen, ob er funktioniert.Da der Skriptcode auf dem Server ausgeführt wird, ist es sehr wichtig, die korrekte Ausführung des Codes sicherzustellen. Da Ihre Anwendung das Skript ausführt, verlässt sich die VSA-IDE darauf, dass die Hostanwendung die korrekte Ausführung des Skripts sicherstellt. Durch die XML-Schemadefinition wurde ein targetURL-Attribut in das Anwendungselement eingefügt, um zur Lösung dieses Problems beizutragen.Im Beispiel verwendet die dtHost-Klasse den Wert des targetURL-Attributs, um die VSA-Entwurfszeitklasse darüber zu informieren, welcher URL gestartet werden soll, wenn der Benutzer versucht, das Skript auszuführen oder zu debuggen. Die Entwurfszeitklasse implementiert IVSADTSite, zu der die VSA-IDE eine Verbindung herstellt, wenn der Benutzer den Code ausführt und den URL startet. Wenn der URL gestartet wird, gibt der Benutzer die Informationen ein, die zum Aufrufen des Webdienstes erforderlich sind, und sendet die Informationen ab. Nachdem die Informationen an den Webserver übermittelt wurden, wird der Webdienst instanziiert, wodurch wiederum der kompilierte Skriptcode geladen und ausgeführt wird.

Das Debugging des Skripts wird gewissermaßen 'kostenlos' mitgeliefert, da Sie hierbei genauso wie beim Ausführen des Codes vorgehen müssen. Der einzige Unterschied gegenüber dem reinen Ausführen des Skripts besteht darin, dass der Skriptautor die Codezeile wählt, an der der Debugprozess starten soll, und dann den Code ausführt. Durch das Ausführen des Codes wird der URL wieder geladen, und das Skript wird beim Aufrufen des Webdienstes gestartet. Die VSA-IDE ist bereit, den Debugprozess an den Haltepunkten des Codes zu starten, wenn der Skriptcode ausgeführt wird.

scripting09102001_fig7

Abbildung 7. Debuggen des Skriptcodes

Speichern und Abrufen des Skriptcodes In diesem Artikel habe ich erläutert, wie die VSA-Laufzeitklasse und die VSA-Entwurfszeitklasse den Skriptcode mit Hilfe von Codeprovidern speichern und abrufen, aber ich habe bisher noch nicht detailliert beschrieben, wie ein Codeprovider implementiert wird. Eigentlich wäre eine ganze "Erste Hilfe für Skript"-Kolumne erforderlich, um dem Codeprovidermechanismus gerecht zu werden. Ich werde hier jedoch die Grundlagen behandeln, damit Sie sehen können, wie der Codeprovider im Beispiel verwendet wurde.Ein Codeprovider ist eine .NET-Komponente, die die ICodeProvider-Schnittstelle implementiert und die Moniker in das von der Anwendung verwendete Speicherformat übersetzt. Ein Codeprovider kann z.B. einen Moniker in eine Gruppe von Abfragen in einem SQL Server- oder XML-Dokument übersetzen. Die Schnittstelle wurde entworfen, damit sowohl der Quell- als auch der Binärcode geladen, gespeichert und gelöscht werden kann. Der Entwurf ist einfach und statusfrei, damit die Komponenten lokal oder als Webdienst aufgerufen werden können.

Der Codeprovider, der im Beispiel für diesen Artikel verwendet wird, speichert den gesamten Skriptcode auf einem Datenträger, um den Setup-Prozess zu vereinfachen. Der gesamte Quell- und Binärcode des Skripts wird im Ordner vsa projects im virtuellen Stammverzeichnis scripthappens auf dem Webserver gespeichert. Der Codeprovider verwendet die Anwendungsinformationen im Moniker und erstellt einen Ordner im Ordner vsa projects, so dass aus com.scripthappens://calculate der Ordner vsa projects\calculate wird. Wenn der Codeprovider von der Entwurfszeit-Hostingklasse aufgerufen wird, werden das VSA-Projekt, die Codelemente, die Debuginformationen und das kompilierte Formular im Ordner gespeichert.

scripting09102001_fig8

Abbildung 8. Codespeicher für "scripthappens.com"

Wenn die Anwendung ausgeführt wird, verwendet die Laufzeit-Hostingklasse den Codeprovider, um das binär kompilierte Skript aus dem Ordner calculate aufzurufen.

Zusammenfassung 

Visual Studio for Applications (VSA) und das Skript für .NET Framework stellen Ihnen ein leistungsstarkes Verfahren zum Erstellen von Anwendungen bereit, die Sie oder Ihre Kunden an die sich ändernden Anforderungen anpassen können. Ich hoffe, dieser Artikel ermöglicht Ihnen einen guten Einstieg in den Prozess der Erstellung einer anpassbaren .NET-Anwendung. Insbesondere hoffe ich, dass Sie jetzt wissen, wie Sie VSA anwenden können, um anpassbare Webdienste zu erstellen und den Zugriff auf die VSA-IDE von einem Webbrowser aus zu ermöglichen. Ich möchte diese Gelegenheit nutzen, um Wayne King zu danken. Er ist Tester im VSA-SDK-Team und hat einige meiner Brainstorm-Entwurfskonzepte hervorragend für das Beispiel in diesem Artikel implementiert. Wie immer würden wir uns über Ihr Feedback sehr freuen. Sie können sich über die VSA-Newsgroups oder unter der E-Mail-Adresse msscript@microsoft.com an uns wenden.


Anzeigen:
© 2015 Microsoft