Von APD über COM nach SOAP Teil 6

Im letzten Teil haben wir die komplette CRUD-Funktionalität für eine Datenbank-Entität namens Contact in COM erstellt und als SOAP-Webservice der Außenwelt bereitgestellt. In dieser Folge möchte ich darauf aufbauend ein „echtes“ Web-Userinterface vorstellen.

Webservices einbinden

Zunächst wird mit dem Visual Web Developer ein neues Projekt mit der Vorlage „Leere ASP.NET-Webanwendung“ names GsContact erstellt. Um die Webservices nutzen zu können sollten wir das Projekt GuptaSoap aus der letzten Reihe in einer zweiten Web Developer Instanz laden und ausführen – am besten im Debug-Mode. Unsere Webservices sind nun unter http://localhost:4141/GuptaSoap.asmx erreichbar.
Zurück zu unserem aktuellen Projekt. Dort wird zuerst ein Verweis auf die Webservice-Funktionen erstellt. Mittels Rechtsklick auf Verweise im Projektmappenexplorer wird dort der Menüpunkt „Dienstverweis hinzufügen“ gewählt. Komischerweise ist der aufgegangene Dialog noch nicht der, den wir brauchen. Erst ein Klick auf „Erweitert…“ und dann nochmal „Webverweis hinzufügen“ führt uns zu dem Fenster, mit dem wir die SOAP-Webservices einbinden können. In das Feld „URL“ wird die weiter oben beschriebene eingetragen. Nach einer kurzen Denkpause von VS  bekommen wir die angebotenen Funktionen zu sehen. Als Webverweisname sollte ein aussagekräftiger eingetragen werden, in unserem Fall wsGuptaSoap. Drückt man nun „Webverweis hinzufügen“ erstellt Visual Studio anhand der via obigem Link heruntergeladenen WSDL-Datei eine komplette Wrapperklasse namens wsGuptaSoap, die all unsere Webservice-Methoden (und ggf. deren public structs, enumerations etc.) beinhaltet. Ab sofort können wir (fast) vergessen, dass wir mit Webservices arbeiten. Wir verwenden sie nun wie eine ganz normale Klasse.

GUI erstellen

Als nächstes benötigen wir das Userinterface. Ein Rechtsklick auf den Projektmappenexplorer/Hinzufügen/Web Form erzeugt die gewünschten Dateien; noch in GsContact.aspx umbenennen und gut. In VS wurden somit drei Dateien erzeugt: GsContact.aspx, GsContact.aspx.cs und GsContact.aspx.designer.cs. Die ersten beiden dürfen wir editieren, die dritte nicht. Von den beiden ersten Dateien ist die .aspx-Datei für den sichtbaren Teil der Anwendung zuständig, die sog. Code-Behind-Datei steuert den Programmcode bei. In der ersten kann sich der Designer austoben, in der zweiten der Programmierer. (Dass jemand beides gleich gut beherrscht, kommt nach meiner Erfahrung äußerst selten vor.) Im großen und ganzen platzieren wir in der aspx-Datei vier Textfelder, ein Multiline-Labelfeld und ein Image-Control. Damit können alle Felder unserer Contact-Tabelle darstellen, sowie ggf. die Fehlermeldung. Um beim Insert oder Update ein Bild hinzufügen zu können, wird noch ein Fileupload-Control hinzugenommen. Die CRUD-Funktionalität wird durch vier Buttons bereit gestellt. Das ganze sieht dann ungefähr so aus:

GUI unsere Web-Application

Code-Behind

In der Code-Behind-Datei wird auf das Click-Ereignis der Buttons reagiert. Auf die Insert-Funktion möchte ich hier näher eingehen, da in ihr alle wichtigen Aspekte vorkommen. Die anderen drei Funktionen enthalten dann nichts neues mehr.

Create

Die Handlerfunktion  für das Click-Ereignis kann man wie in VS gewohnt z.B. per Doppelklick in der Entwurfsansicht erzeugen. Von dort rufen wir die noch zu erstellende Funktion InsertContact() auf:

protected void btnInsert_Click(object sender, EventArgs e)
 {
 InsertContact();
 }

Die ersten beiden Zeilen instanziieren zum einen unser Soap-Webservice-Objekt, so dass wir dessen Funktionalität nutzen können, zum anderen das Result-Objekt (eigentlich ein Struct), das das Funktionsergebnis aufnimmt.

void InsertContact()
 {
 wsGuptaSoap.GuptaSoap wGs = new wsGuptaSoap.GuptaSoap();
 wGs.Timeout = 10000; // 10 Sec. - länger wollen wir nicht warten
 wsGuptaSoap.ContactInsertResult cResult = new wsGuptaSoap.ContactInsertResult();

Alsdann werden die Werte aus den Eingabefeldern abgeholt. Da die TextBox-Elemente grundsätzlich Text enthält, muss dieser in den Zieldatentyp umgewandelt werden. Das geschieht am besten mit den TryParse()-Methoden der Zielobjekte. Die haben den Vorteil, keine Exceptions auszuwerfen, die wir wieder extra behandeln müssten.

//Int32.TryParse finde ich besser als die Alternative SomeString.ToInt32(), da by Design keine Exception ausgeworfen wird
 Int32.TryParse(dfLuckyNumber.Text, out nLuckyNumber);
 DateTime.TryParse(dfBirthday.Text, out dtBirthday);

Für die Bilddatei, die der User angegeben hat, wurde ein FileUpload-Control verwendet. Das besteht aus einem Edit-Feld und dem typischen „Durchsuchen“-Button. Freundlicherweise kümmert sich das ASP-Framework selbständig um den Upload der Datei. Wir können bequem über das Control sowohl an den Filename als auch die Bilddaten herankommen, ohne uns um irgendwelche Details kümmern zu müssen. Zuerst fragen wir nach, ob überhaupt eine Datei angekommen ist.

if (dfImageFile.HasFile)
 {

Als nächstes müssen wir das Bild in ein Verzeichnis schreiben, von dem aus es der Webservice  abholen und in die Datenbank schreiben kann. (Weil: Die Web-Applikation kann auf einem anderen Server laufen, als die Webservices). Das kostet etwas Mühe, da wir dafür sorgen müssen, dass der Filename unique ist und nicht evt. mit einem anderen kollidiert, das möglicherweise von einem zweiten User gerade eben eingespielt wird.

 //das vom User übergebene Image so umbenennen, dass es nicht mit anderen Files kollidieren kann
sImageFileName = dfImageFile.FileName;
 //File-Extension ermitteln
 sImageFileNameExt = sImageFileName.Substring(sImageFileName.LastIndexOf("."));
 sImageFileName = Guid.NewGuid() + sImageFileNameExt;
 //Das neue Image im Unterverzeichnis /upload ablegen
  //Das Unterverzeichnis muss bereits vorhanden sein und von der Außenwelt per URL zugängig!
 sImageFilePath = "./upload/ContactImage" + sImageFileName;
 //Den relativen Pfad in einen Absoluten wandeln
 sImageFilePath = MapPath(sImageFilePath);
 //und dort abspeichern
 dfImageFile.SaveAs(sImageFilePath);
 }

Jetzt ist alles vorbereitet, wir können die Webservice aufrufen

try
 {
 cResult = wGs.ContactInsert(dfFirstName.Text, dfLastName.Text, dtBirthday, nLuckyNumber, sImageFilePath);

Wenn der Insert fehlerfrei funktioniert rufen wir die Ladefunktion, die den frisch gebackenen Datensatz wieder aus der Datenbank abholt und anzeigt. Zugegeben – das ist eigentlich etwas aufwändig (nochmal ein Webservice-Aufruf, der das COM-Objekt verwendet, Datenbankzugriff…) und könnte auch eleganter erledigt werden – aber man muss ja auch noch Raum für Optimierungen lassen… 😉

if (cResult.bRet == true)
 {
 //ContactId setzen, Load ausführen, um das Bild anzuzeigen
 dfContactId.Text = cResult.nContactId.ToString();
 LoadContact();
 }
}
 catch(Exception ex)
 {
 cResult.sErrorMsg = ex.Message;
}

Im Fehlerfall lassen wir uns die Fehlermeldung anzeigen

if(cResult.bRet == false)
 {
 dfErrorMsg.Text = cResult.sErrorMsg;
 dfErrorMsg.Visible = true;
 }
}

Read

Das Laden des Datensatzes ist wesentlich einfacher.

protected void btnSelect_Click(object sender, EventArgs e)
{
LoadContact();
}

void LoadContact()
{

Die Webservice-Funktion (Klasse) instanziieren. Den Timeout auf 10 Sec. beschränken (dh. die Zeitspanne, in der nach dem Absetzen der SOAP-Message an den Webservice auf eine Antwort gewartet wird. Kommt keine, wird eine Timeout-Exception geworfen.
wsGuptaSoap.GuptaSoap wGs = new wsGuptaSoap.GuptaSoap();
wGs.Timeout = 10000;   // mehr als 10 Sek. wollen wir nicht warten müssen (Default liegt bei 60 Sek. glaube ich)
// hier könnte man auch ein Animated Gif einblenden, um die Wartezeit zu verschönern
wsGuptaSoap.ContactSelectResult cResult = new wsGuptaSoap.ContactSelectResult();

Der einzige Parameter ist die ContactId.

Int32 nContactId = 0;
if (Int32.TryParse(dfContactId.Text, out nContactId))
{
try  //Calls auf externe Funktionen (COM oder SOAP) am besten mit try-catch ausführen. Sonst gibt’s hässliche Fehlermeldungen
{
cResult = wGs.ContactSelect(nContactId);
}
catch (Exception ex)
{
cResult.bRet = false;
cResult.sErrorMsg = ex.Message;
}
}
else
{
cResult.bRet = false;
cResult.sErrorMsg = „Invalid ContactId“;
}
Ergebnis auf dem Formular anzeigen

dfFirstName.Text = cResult.sFirstName;
dfLastName.Text = cResult.sLastName;
dfBirthday.Text = cResult.dtBirthday.ToShortDateString();
dfLuckyNumber.Text = cResult.nLuckyNumber.ToString();

Da das Bild ggf. „nur“ als URL vorliegt, muss diese noch an das Image-Control übergeben werden – ansonsten wird es unsichtbar gemacht.

if ((cResult.sImagePath != null) && (cResult.sImagePath.Length > 0))
{
imgContact.ImageUrl = cResult.sImagePath;
imgContact.Visible = true;
}
else
{
imgContact.Visible = false;
}

if (cResult.bRet)
{
dfErrorMsg.Visible = false;
}
else
{
dfErrorMsg.Text = cResult.sErrorMsg;
dfErrorMsg.Visible = true;
}
}

Die Funktionen Update und Delete funktionieren analog. Die Details können im Quellcode nachgelesen werden. Wir können also prinzipiell unsere in SQLWindows programmierte Business-Logik via Webservice verfügbar machen. Die Datentypen Number, Date und String und auch Binärdaten können sowohl eingelesen als auch ausgegeben werden.

In den nächsten beiden Teilen der Serie werden wir einen Webservice konsumieren, bevor wir uns der Übergaben von komplexen Parametern und Arrays zuwenden.

Sourcefiles zum Download:

Die Web-Applikation: GuptaSoapWebApplication6.zip

Nochmal die Webservices (inverändert zum Teil 5): GuptaSoapWebservices6.zip

Advertisements