Von APD über COM nach SOAP Teil 5

Dieses Mal stellen wir die komplette CRUD-Funktionalität für einen Datensatz bereit. CRUD = Create, Read, Update, Delete oder in der Sql-Welt: Insert, Select, Update, Delete. Die ContactSave(..)-Funktion aus dem letzten Teil wird also ergänzt (bzw. ersetzt) durch ContactInsert(..) usw. Da der Programmcode der Funktionen bereits im letzten Teil erläutert wurde und weiter unten als Download zur Verfügung steht, möchte ich hier nur auf die Parameter der neuen Funktionen eingehen.

ContactSelect

Function: ContactSelect
Return:
  Boolean //false, wenn irgendein Fehler auftritt
Parameter:
  Number: nContactId
  Receive String: rsFirstName
  Receive String: rsLastName
  Receive Date/Time: rdtBirthday
  Receive Number: rnLuckyNumber
  Variant: rvImage
  Receive String: rsErrorMsg

Ein Boolean als Returnwert ist recht praktisch, um festzustellen ob die Funktion erfolgreich war. Sie wird in diesem Projekt durchgängig so verwendet. Als einzig „echter“ Parameter wird der Schlüsselwert der Tabelle nContactId übergeben (als VT_I4 also 4-Byte-Integer). Da es meines Erachtens über die Gupta-COM-Schnittstelle nicht möglich ist, komplexe Parameter (also Functional Classes zu übergeben),  werden die angeforderten Werte mittels Receive-Variablen zurückgegeben. Sollte jemand wissen, wie man doch Functional Classes auf vernünftige Weise übergeben kann, bitte Bescheid geben. Ich setze eine Kiste Bier o.ä. dafür aus… Zurück zum Code: Die letzte Receive-Variable gibt im Fehlerfall eine (hoffentlich) vernünftige Fehlermeldung  zurück. Aussagekräftige Fehlermeldungen haben sich speziell in diesem Szenario (Datenbank – Guptacode – COM – SOAP – Webanwendung) bewährt. Es erleichtert das Debugging in diesem komplexen Umfeld ungemein. (Ist eine Erfahrungstatsache.)

In diesem Zusammenhang ist es mehr als nur empfehlenswert, modale Fehlermeldungs-Messageboxen, die Gupta z.B. bei nicht abgefangenen Sql-Fehlern ausgibt, zu unterdrücken. Mit SalUseEventLog(..) werden diese Meldungen in das System-Eventlog von Windows umgeleitet.

On SAM_AppStartup
  ! No Sql-Error-Message-Boxes that stops COM-Object
   Call SalUseEventLog( TRUE, TRUE )

Die weiteren Funktionen:

ContactInsert

Function: ContactInsert
  Return:   Boolean //false, wenn irgendein Fehler auftritt
Parameter:
  Receive Number: rnContactId
  String: sFirstName
  String: sLastName
  Date/Time: dtBirthday
  Number: nLuckyNumber
  Variant: vImage
  Receive String: rsErrorMsg 

Im Unterschied zu ContactSelect ist in diesem Fall der Parameter ContactId als Receive-Parameter ausgelegt, die anderen jedoch als „normale“ Parameter – von der ErrorMsg natürlich abgesehen. ContactId wird während des Insert-Vorgangs ermittelt und es ist sinnvoll, diesen wieder zurückzugeben.

ContactUpdate

Function: ContactUpdate
  Return:   Boolean //false, wenn irgendein Fehler auftritt
Parameter:
  Number: nContactId
  String: sFirstName
  String: sLastName
  Date/Time: dtBirthday
  Number: nLuckyNumber
  Variant: vImage
  Receive String: rsErrorMsg

Diese Funktion hat fast die selben Parameter wie Insert(..) – lediglich die ContactId ist nicht mehr als Receive-Parameter deklariert.

ContactDelete

Function: ContactDelete
  Return:   Boolean //false, wenn irgendein Fehler auftritt
Parameter:
  Number: nContactId
  Receive String: rsErrorMsg 

Delete benötigt außer dem Schlüsselwert keine weiteren Parameter.

Das Projekt kann nun gebaut werden. Registrieren nicht vergessen.

Die SOAP-Webservices

Zu Beginn muss im ggf. bestehenden Projekts wieder der Verweis auf GuptaCom gelöscht und erneuert werden. Es sind ja einige Funktionen dazugekommen.

Die Webservice-Funktionen sind so konzipiert, dass alle Rückgabewerte in einem Struct zusammengefasst und keine Receive-Parameter verwendet werden müssen. Da die Rückgabewerte bei drei der vier Funktionen unterschiedlich sind, definieren wir am besten für jede Funktion einen eigenen Return-Struct.

Select

Der Return-Struct für den Select-Aufruf ist natürlich der umfangreichste. Es werden alle Attribute des Contacts zurückgegeben, sowie bRet und sErrorMsg. Zwar könnte man die nContactId herausstreichen, da sie ja bereits vor dem Funktionsaufruf bekannt ist, aber was soll’s.

public struct ContactSelectResult
{
public Boolean bRet;
public String sErrorMsg;
public Int32 nContactId;
public String sFirstName;
public String sLastName;
public DateTime dtBirthday;
public Int32 nLuckyNumber;
public String sImagePath;
}

Der eigentliche Webservice beginnt so:

[WebMethod]
public ContactSelectResult ContactSelect(Int32 nContactId )
{

es folgen einige Variablendeklarationen

 ContactSelectResult cResult = new ContactSelectResult();
 Object oImage = null;
 Image img = null;
 String sFilename = "";
 String sMyUri = "";

Da der COM-Funktionsaufruf aus diversen Gründen, die wir nicht unmittelbar im Griff haben, mit einer Exception fehlschlagen kann (z.B. COM-Objekt ist nicht registriert),  wird dieser in einen try-Block gesetzt.

try
{
 GuptaCom.MyCom cGc = new GuptaCom.MyCom();
 cResult.bRet = cGc.ContactSelect(nContactId, ref cResult.sFirstName, ref cResult.sLastName, ref cResult.dtBirthday, ref cResult.nLuckyNumber, ref oImage, ref cResult.sErrorMsg);
}
catch (Exception ex)
{
 cResult.bRet = false;
 cResult.sErrorMsg = ex.Message;
}
{

Eigentlich war es das schon. Wenn da nur nicht die Sache mit dem Bild wäre: Zwar könnten wir uns die Sache einfach machen und es als Byte-Array zurückgeben. Das ist aber keine so gute Idee, da in der Regel das Bild in einem Browser ausgegeben wird. Es ist also ungemein praktisch, es in einem öffentlichen Ordner abzulegen und einen Link (eine URI) darauf zurückzugeben, der dann ganz easy in HTML verwendet werden kann. Am Rande erwähnt: Es existiert zwar prinzipiell auch die Möglichkeit, Binärdaten eines Bilds direkt in HTML einzubetten (Base64 codiert) – jedoch ist das sehr unüblich (und somit erklärungsbedürftig) und außerdem gibt es browserseitig Limitationen (z.B. max. 64kByte – bei manchen Browsern auch weniger).

if (cResult.bRet)
{
 cResult.nContactId = nContactId; // ja, ja, das ist redundant. Aber nützlich.
 try // Da wir nicht sicher sein können, dass es sich bei dem Byte-Array wirklich um Bilddaten handelt, könnte im Folgenden eine Exception ausgelöst werden
 {
 // Das allgemein Objekt oImage wird in ein Byte-Array gecastet
 // mit diesem wird ein Memory-Stream initialisiert
 // das wiederum dem Image zugewiesen wird.
 img = Image.FromStream(new MemoryStream((byte[])oImage));
 // Das Image wird in Form eines Jpeg in den Unterordner Images abgelegt
 // Dieser Order muss natürlich vorhanden sein und sollte von außen zugänglich sein (Rechtesteuerung)
 sFilename = this.Server.MapPath("./images/" + nContactId.ToString() + ".jpg");
 img.Save(sFilename, System.Drawing.Imaging.ImageFormat.Jpeg);
 // und über folgende URL ist das Bild abbrufbar
 cResult.sImagePath = "/images/" + nContactId.ToString() + ".jpg";
 //aus der relativen URL eine absolute erstellen
 sMyUri = this.Context.Request.Url.AbsoluteUri;
 sMyUri = sMyUri.Substring(0, sMyUri.LastIndexOf("/"));
 cResult.sImagePath = sMyUri + cResult.sImagePath;
 }
 catch (Exception ex)
 {
 // Bild konnte nicht umgewandelt werden (z.B. weil in der DB leer, oder unverständliches Format
 // interessiert uns nicht weiter. Wir werten den Funktionsaufruf trotzdem als erfolgreich.
 }
}
return cResult;
}

Insert und Update

Der Inhalt der beiden Funktionen wurde im Wesentlichen bereits in der letzten Folge anhand von ContactSave(..) erläutert. Lediglich eine Besonderheit liegt bei der Update-Funktion vor: Da man i.d.R. bei einem Update des Datensatzes nicht unbedingt wieder ein Bild mitsenden möchte, wird ein leerer Parameter sImagePath so interpretiert, dass hier keine Änderung vorliegt. Will man das Bild explizit löschen, wird in sImagePath der Wert „remove“ an COM übergeben.

Generell wird sichergestellt, dass es sich bei der Datei, auf die sImagePath verweist, um eine Bilddatei handelt. Des weiteren wird das Bild auf eine definierte Größe gebracht. Dazu habe ich eine SizeToFit-Funktion implementiert, die dafür sorgt, dass a) das Bild auf 200 x 300 Pixel „gesized“ und b) dabei die Aspect-Ratio nicht verändert wird. Die sich ggf. ergebenden Leerflächen werden mit einer Hintergrundfarbe versehen, das Bild mittig platziert.

Delete

Diese Funktion ist die einfachste und bedarf keiner weiteren Erklärung.

Damit wäre die  CRUD-Funktionalität komplett. In der nächsten Folge stelle ich eine ASPX-Webanwendung auf Basis dieser Webservices vor. Derweil lassen sich die Webservices über die von Visual Studio automatisch generierten Testseiten aufrufen.

Hier die Downloads zur aktuellen Folge:

GuptaCom Teil5.zip

GuptaSoap Teil5.zip

Advertisements

Über thomasuttendorfer
Ich bin Entwicklungsleiter bei der Softwarefirma [ frevel & fey ] in München. Wir entwickeln Business-Software für Verlage und verwenden dafür den Gupta Team-Developer sowie Visual Studio.

One Response to Von APD über COM nach SOAP Teil 5

  1. Pingback: Von APD über COM nach SOAP Teil 6 « Thomas Uttendorfers

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: