Es sollen Web-Services implementiert werden um bestimmte Funktionen einer Anwendung für externe Verwendung zur Verfügung zu stellen. Dieses Dokument untersucht die Funktionsweise eines Web-Service, und wie bestehende SEU/TAA Funktionalitäten hierfür verwendet werden können.
Grundvoraussetzung für eine reibungslose Kommunikation zwischen der Implementierung des Web-Service (Server) und einer externen Komponente (Client) ist die sogenannte WSDL. Diese beschreibt mit XML-Schemas die einzelnen Nachrichten, die an einen Web-Service gesendet (Request), oder von dem Web-Service zurück geliefert (Response) werden können. Jeweils eine Request- und eine Responsenachricht werden zusammen als eine Operation des Web-Service veröffentlicht. Diese Operation wird mit einer URL verknüpft. ASP.NET bildet jeweils eine Operation als Methode an einer Klasse ab. Diese Methode bekommt die Requestnachricht als Eingabeparameter, und die Reponsenachricht als Ausgabeparameter übergeben. Bei ASP.NET ist allerdings nicht die WSDL die Ausgangslage, sondern diese wird aus der Definition der Klasse abgeleitet. Aus der Signatur der Methode leitet ASP.NET die Struktur der Request- und Responsenachrichten ab. Für Fehlersituationen gibt es sog. SOAP Faults. Die Struktur dieser SOAP Faults ist vordefiniert, und ein Client muss darauf vorbereitet sein, dass diese von einem Web Service zurückgeliefert werden. ASP.NET übersetzt aufgeworfene und nicht behandelte .NET-Exceptions als SOAP Faults für den Client.
Die Struktur eines Web Service lässt sich auf ein TAA-Modul abbilden. Ein auslösbares Ereignis eines TAA Moduls kann auf eine Operation eines Web-Services abgebildet werden. Die Parameterobjekte können an Hand deren Rolle als Bestandteil der Request- und/oder Responsenachricht aufgenommen werden. Wenn ein Web Service mit ASP.NET implementiert wird, muss in der Methode ein TAA Modul mit dem entsprechenden Ereignis ausgerufen werden, wobei die Eingangsparameter auf die TAA Parameterobjekt übertragen werden müssen. Nach dem erfolgreichen Aufruf müssen dann die Ausgangsparameter aus dem TAA Parameterobjekte bestückt werden. Obwohl sich ADO.NET-Datatables und Datasets für die Abbildung der Datenstrukturen anbieten würden, könnte dies für Clients, die kein ADO.NET verwenden, zu Problemen führen. In Anhang B sind in dem MSDN Artikel Web Services and DataSets einige der Probleme und Tücken in diesem Zusammenhang beschrieben. Laut einem Artikel in MSDN (Siehe Anhang B) werden die übergebenen Daten nicht nach dem XML Schema geprüft. In dem Artikel wird auch eine Lösung vorgestellt, die es allerdings selbst zu implementieren gilt. Hierbei ist zu berücksichtigen, dass nur die Struktur und Format der übergebenen Daten überprüft werden kann. Der Inhalt der Daten muss von der Anwendung geprüft werden.
TAA Module sollen als Web Service veröffentlicht werden können. Hierzu soll ein Werkzeug geschaffen werden, welches die Schnittstelle des Web Services für ein TAA Moduls bearbeiten und generieren lässt. Auslösbare Ereignisse sollen als Methoden an diesem Webservice veröffentlicht werden. Die für das jeweilige Ereignis definierten Parameterobjekte sollen als Argumente an diese Methoden übergeben werden können. Die Zustände sollen als Rückkehrwert der Methoden festgelegt werden können. Der Anwendungsentwickler soll die Möglichkeit bekommen, selber die Schnittstelle der Webservice anzupassen, in z.B. die einzelnen Ereignisse nicht oder unter einem anderen Namen veröffentlicht werden, oder die zu übergebenen und zurück gelieferten Daten angepasst und/oder eingeschränkt werden. Diese Anpassungen sollen mit der Definition der Web Service in der EDB gespeichert, und für eine Generierung heran gezogen werden können.
Da die WSDL die Voraussetzung ist, mit dem der Client mit dem Server kommuniziert, soll die WSDL die Ausgangslage für denWeb Service sein. Es ist zwar auch möglich, die WSDL von ASP.NET automatisch generieren zu lassen; die Einflussmöglichkeiten auf der .asmx Seite auf dem automatisch generierten WSDL sind aber sehr begrenzt. Einige MSDN Artikel, die dieses Thema vertiefen, sind in Anhang B mit aufgenommen. Neben dem WSDL soll eine .NET Klasse für die .asmx Seite auf dem Server generiert werden. Dieser generierten Klasse soll die Implementierung des Web Service dienen. Diese abgeleitete Klasse soll nicht die Implementierung des TAA Moduls ersetzen, denn das Modul soll mit den bestehenden Möglichkeiten implementiert werden. In diese Klasse sollen die einzelne Methoden und zu übergebenden Daten des Web Service auf die entsprechende Ereignisse und Parameterobjekte für das TAA Modul übertragen werden, und anschließend soll das TAA Modul aufgerufen werden. Daten, die zurück gereicht werden müssen, sollen nach dem Aufruf des TAA Moduls aus den Parameterobjekten extrahiert werden. Außerdem soll hier der von dem TAA Modul gesetzte Zustand auf einen Rückkehrwert für die Web Methode übertragen werden. Die zu generierende Klasse soll auf einer neu zu implementierende Klasse TeamWiSE.TAA.Web.WebService basieren. Diese Basisklasse stellt die zu bestückenden Parameterobjekte zu Verfügung, und stellt sicher, dass der Aufruf in der richtigen Anwendungsversion ausgeführt wird. In Anhang C ist ein Beispiel aufgenommen, wie die Struktur der generierten WSDL und Klasse aussehen würden.
Für die Datenübergabe muss aus der Datenstruktur der Parameterobjekte ein entsprechendes XML Schema generiert werden, das in der WSDL veröffentlicht wird. Für jedes Parameterobjekt soll ein XML Schema erzeugt werden mit den zu transportierenden Feldern. In Anhang A ist eine Tabelle aufgeführt, wie welche Datentypen übertragen werden sollen. Hier sind neben den XML Schema Datentypen auch die entsprechenden .NET Datentypen enthalten. Der Anwendungsentwickler soll in die Lage versetzt werden können, dieses XML Schema anzupassen, indem einzelne Felder ausgeblendet werden, oder, wenn Parameterobjekte in einer Beziehung stehen, diese Beziehung in der Klassenstruktur zu definieren. Bei der Verknüpfung von Parameterobjekten ist zu berücksichtigen, dass keine Zyklen entstehen, da diese nicht in einem XML Schema abgebildet werden können. In Anhang C ist ein Beispiel aufgenommen, wie 2 Parameterobjekte, die mit einander verknüpft sind, generiert werden.
Das Fehlerhandling soll mit TAA Conditions implementiert werden, indem aufgeworfene TAA Conditions in SOAP Faults übertragen werden. Hierbei soll unterschieden werden können zwischen 2 Arten von SOAP Faults, nämlich Clientseitige und Serverseitige SOAP Faults. Clientseitige SOAP Faults beziehen sich auf den Dateninhalt oder die Art und Weise, wie der Request formuliert wurde. Serverseitige SOAP Faults beziehen sich auf Fehler, die auf dem Server passiert sind. Dies können z.B. technische Fehler sein, wie eine Datenbanksperre. Es soll festgelegt werden können, welche TAA Conditions vom dem TAA Modul geraised werden können, und ob diese auf einen Client oder Server SOAP Fault übertragen werden soll. Eventuell soll ein Default festgelegt werden können, wie TAA Conditions übertragen werden.
Enthält allgemeine Eigenschaften des Web-Service wie Name und Beschreibung. Für jedes in der Schnittstelle verwendete Ereignis gibt es eine Instanz der Klasse WebMethod.
Enthält Eigenschaften zu einer Web-Methode wie Name der Methode, auszulösendes Ereignis. Für jeden definierten Parameter gibt es eine Instanz der Klasse WebParameter.
Einhält die Eigenschaften eines Parameters, wie ein Kenzeichen ob In, Out oder In/Out. Diese Klasse basiert auf der Klasse WebElement. Diese enthält Eigenschaften für ein einzelnes Feld aus der XML Datenstruktur wie Name, und mit welchem Feld in einem TAA Parameterobjekt es verknüpft ist. Es enthält ebenfalls einen Verweis auf den WebDatatype
Beschreibt die Struktur eines WebElements, wobei es 2 Varianten gibt: einen SimpleType wie String, Integer, Datum, etc., oder ein aus mehreren Elementen zusammengestellter ComplexType. Bei letzterem wird unterschieden zwischen GroupBased für redefines und occurs, und ParmBased für verknüpfte Parameterobjekte. ParmBased verweist auf das WebElement, das für die Verknüpfung benötigt wird.
Rochade | XML Schema | .NET(C#) |
---|---|---|
character n [variable] alphanumerisch n [variabel] alphabetisch n [variable] | <xsd:simpleType> <xsd:restriction base=“xsd:string“> <xsd:maxLength value=“n“ /> </xsd:restriction> </xsd:simpleType> | string |
numerisch [+]n.m [gepackt] | <xsd:simpleType> <xsd:restriction base=“xsd:decimal“> <xsd:totalDigits value=“n“ /> <xsd:fractionDigits value=“m“ /> </xsd:restriction> </xsd:simpleType> | Decimal |
numerisch +n (n ⇐ 4) | <xsd:simpleType> <xsd:restriction base=“xsd:short“> <xsd:totalDigits value=“n“ /> </xsd:restriction> </xsd:simpleType> | short |
numerisch n (n ⇐ 4) | <xsd:simpleType> <xsd:restriction base=“xsd:unsignedShort“> <xsd:totalDigits value=“n“ /> </xsd:restriction> </xsd:simpleType> | ushort |
numerisch +n (n ⇐ 9) | <xsd:simpleType> <xsd:restriction base=“xsd:int“> <xsd:totalDigits value=“n“ /> </xsd:restriction> </xsd:simpleType> | int |
numerisch n (n ⇐ 9) | <xsd:simpleType> <xsd:restriction base=“xsd:unsignedInt“> <xsd:totalDigits value=“n“ /> </xsd:restriction> </xsd:simpleType> | uint |
numerisch +n (n ⇐ 18) | <xsd:simpleType> <xsd:restriction base=“xsd:long“> <xsd:totalDigits value=“n“ /> </xsd:restriction> </xsd:simpleType> | long |
numerisch n (n ⇐ 18) | <xsd:simpleType> <xsd:restriction base=“xsd:unsignedLong“> <xsd:totalDigits value=“n“ /> </xsd:restriction> </xsd:simpleType> | ulong |
Timestamp | <xsd:simpleType> <xsd:restriction base=“xsd:string“> <xsd:pattern value=“\d{4}\-\d{2}\-\d{2}\-\d{2}\.\d{2}\.\d{2}\.\d{6}“/> </xsd:restriction> </xsd:simpleType> | string |
DateTime | <xsd:dateTime />
| DateTime |
Datum | <xsd:date />
| DateTime |
Uhrzeit | <xsd:time />
| DateTime |
Binär n BLOB n | <xsd:simpleType> <xsd:restriction base=“xsd:base64Binary“> <xsd:maxLength value=“n“ /> </xsd:restriction> </xsd:simpleType> | byte[] |
Gruppenstufen mit occurs | <xsd:complexType> <xsd:sequence> <xsd:element name=“name“ maxOccurs=“n“> … </xsd:element> </xsd:sequence> </xsd:complexType> | Array |
Gruppenstufen mit redefines | <xsd:complexType> <xsd:choice> … </xsd:choice> </xsd:complexType> | object + enum für Type |
Die MSDN Artikel sind als PDF Datei in diesem Dokument enthalten. Wenn der Acrobat Reader auf Ihrem Rechner installiert ist, können sie durch eine Doppelklick auf das jeweilige PDF-Symbol die entsprechende Datei öffnen.
In diesen beiden Artikeln werden die Vor- und Nachteile des .asmx Programmiermodell für Web Services beschrieben, und wieso zuerst die WSDL erfasst werden soll.
contract-first_service_development.pdf
techniques_for_contract-first_development.pdf
In diesem Artikel wird (ab Seite 4) beschrieben wie die WSDL von ASP.NET generiert wird, und wie ein selbst erfasstes WSDL verwendet werden kann.
generating_asp.net_web_service_classes.pdf
In diesem Artikel sind die Nachteile für die Verwendung von ADO.NET DataSets in einem Web Service beschrieben:
web_services_and_datasets.pdf
In diesem Artikel wird eine Lösung beschrieben wie die Struktur der Daten die einem Web Service übergeben werden, geprüft werden können.
xml_schema_validation.pdf
Alle Beispiele basieren auf die Steuerung TS-TARIFRECHNER-ANTRAG. Dieser kennt ein Ereignis BERECHNEN, welches als Web Methode Berechnen für einen Web Service mit den Namen TarifRechner veröffentlicht wird.
In diesem Beispiel wird die generelle Struktur der Web Service mit Zustandsmapping gezeigt, ohne Datenübergabe und Conditionhandling.
Die WSDL für DENWeb Service würde folgendermaßen aussehen:
<?xml version="1.0" encoding="utf-8"?> <wsdl:definitions targetNamespace="http://www.al.de/Levert/TarifRechner" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.al.de/Levert/TarifRechner"> <wsdl:types> <xsd:schema elementFormDefault="qualified" targetNamespace="http://www.al.de/Levert/TarifRechner"> <xsd:element name="BerechnenRequest"> <xsd:complexType ... /> </xsd:element> <xsd:element name="BerechnenResponse"> <xsd:complexType> <xsd:sequence> <xsd:element name="return" type="tns:TarifRechnerState"> ... </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:simpleType name="TarifRechnerState"> <xsd:restriction base="xsd:NMTOKEN"> <xsd:enumeration value="OK" /> <xsd:enumeration value="ABBRUCH" /> </xsd:restriction> </xsd:element> </xsd:schema> </wsdl:types> <wsdl:message name="BerechnenRequestMsg"> <wsdl:part name="parameters" element="tns:BerechnenRequest" /> </wsdl:message> <wsdl:message name="BerechnenResponseMsg"> <wsdl:part name="parameters" element="tns:BerechnenResponse" /> </wsdl:message> <wsdl:portType name="ITarifRechner"> <wsdl:operation name="Berechnen"> <wsdl:input message="tns:BerechnenRequestMsg" /> <wsdl:output message="tns:BerechnenResponseMsg" /> </wsdl:operation> </wsdl:portType> <wsdl:binding name="TarifRechnerSoapBinding" type="tns:ITarifRechner"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="Berechnen"> <soap:operation soapAction="http://www.al.de/Levert/TarifRechner/Berechnen" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="TarifRechner"> <wsdl:port name="TarifRechnerSoapPort" binding="tns:TarifRechnerSoapBinding"> <soap:address location="http://localhost/TarifRechner/TarifRechner.asmx" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
namespace AL.Levert.TarifRechner { using System; using System.Web.Services; using System.Web.Services.Description; using System.Web.Services.Protocols; [WebService(Name="TarifRechner")] [WebServiceBinding( Name="TarifRechnerSoapBinding", Namespace="http://www.al.de/Levert/TarifRechner", Location="TarifRechner.wsdl")] public class TarifRechner : TeamWiSE.TAA.Web.WebService { public TarifRechner() { base("TS-TARIFRECHNER-ANTRAG", "SSTR", "LEVERT", "BL_029"); } [WebMethod()] [SoapDocumentMethod( "http://www.al.de/Levert/TarifRechner/Berechnen", RequestElementName="BerechnenRequest", ResponseElementName="BerechnenResponse", Use=SoapBindingUse.Literal, ParameterStyle=SoapParameterStyle.Wrapped)] public TarifRechnerState Berechnen(...) { // Daten übertragen auf TAA Parameterobjekte base.Invoke("BERECHNEN"); // Daten zurück übertragen switch (base.State) { case "OK": return TarifRechnerState.OK; case "ABBRUCH": return TarifRechnerState.ABBRUCH; } } } [XmlType(Namespace="http://www.al.de/Levert/TarifRechner")] public enum TarifRechnerState { OK, ABBRUCH, } }
Für dieses Beispiel wird ein Ausschnitt aus 2 der Parameterobjekte ANTVTG (Antrag) und ANVTGT (Vertragsteil) die für das Ereignis BERECHNEN der Steuerung TS-TARIFRECHNER-ANTRAG definiert sind verwendet.
<xsd:complexType name="Antrag"> <xsd:sequence> <xsd:element name="Vertragsnummer"> <xsd:simpleType> <xsd:restriction base="xsd:unsignedLong"> <xsd:totalDigits value="11" /> </xsd:restriction> </xsd:simpleType> </xsd:element> <xsd:element name="Vetragsteile"> <xsd:complexType> <xsd:sequence> <xsd:element name="Vertragsteil" type="tns:Vertragsteil" maxOccurs="unbounded" /> </xsd:sequence> </xsd:complexType> </xsd:element> ... </xsd:sequence> </xsd:complexType> <xsd:complexType name="Vertragsteil"> <xsd:sequence> <xsd:element name="Vertragsteilnummer"> <xsd:simpleType> <xsd:restriction base="xsd:unsignedShort"> <xsd:totalDigits value="4" /> </xsd:restriction> </xsd:simpleType> </xsd:element> ... </xsd:sequence> </xsd:complexType> <xsd:element name="BerechnenRequest"> <xsd:complexType> <xsd:sequence> <xsd:element name="Antrag" type="tns:Antrag" /> ... </xsd:sequence> </xsd:complexType> </xsd:element>
[XmlType(Namespace="http://www.al.de/Levert/TarifRechner")] public class Antrag { public System.UInt64 Vertragsnummer; [XmlArrayItem(IsNullable=false)] public Vertragsteil[] Vetragsteile; ... } [XmlType(Namespace="http://www.al.de/Levert/TarifRechner")] public class Vertragsteil { public System.UInt16 Vertragsteilnummer; ... } ... public TarifRechnerState Berechnen(Antrag Antrag, ...) { // Daten übertragen auf TAA Parameterobjekte base.Parameter["AntVtg"].objField("VSG-VSG-LNR") = Antrag.VertragsNummer; foreach (Vertragsteil o in Antrag.Vertragsteile) { base.Parameter["AnVtgT"].objAdd(); base.Parameter["AnVtgT"].objField("VSG-VSG-LNR") = Antrag.Vertragsnummer; base.Parameter["AnVtgT"].objField("LVT-LVT-LNR") = o.Vertragsteilnummer; ... } ... base.Invoke("BERECHNEN"); ... }