[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ]  

 

Area_ii  Area_ii  Area_ii  Area_ii  Area_ii  Area_ii  Area_ii  Area_ii  Area_ii  AREA  Hect 
Seed_ii  Seed_ii  Seed_ii  Seed_ii  Seed_ii  Seed_ii  Seed_ii  Seed_ii  Seed_ii  SEEDRATE  UOM

Genau 2 Records werden bezogen und beinhalten die gewünschten Daten. Die bezogenen Daten werden im lokalen Array "laData" aufgebaut. Dieses Array wird im (auf Form Ebene privaten) Array "aAllData" kopiert. Auf diese Weise ist sichergestellt, dass bei mehreren Tabs alle bezogenen Daten aufbewahrt werden können. Die Daten, welche im Array aAllData abgespeichert sind, werden dazu verwendet, festzustellen, ob sich Daten verändert haben oder nicht. Siehe auch weiter unten.

Die Methode presentCropArea() ist für das Präsentieren der Daten zuständig.

    Private Sub presentCropArea()

      txtAreaUnit.Text = aAllData(ssTab.Tab)(0, 11) ' areaunit
      txtSeedUnit.Text = aAllData(ssTab.Tab)(1, 11) ' seedunit

      ' area_ii, seed_ii
      Dim lnField As Integer
      For lnField = 0 To 9

        pvArea(lnField).ValueReal = aAllData(ssTab.Tab)(0, lnField)
        pvSeed(lnField).ValueReal = aAllData(ssTab.Tab)(1, lnField)

      Next

    End Sub

Hier werden die Daten aus dem aAllData Array in die dazu vorgesehenen Controls geschrieben. Hier besteht die Möglichkeit Daten für die Präsentation noch zu verändern.

Anwendungsbeispiel 4

Vor dem Wechsel zu einem anderen Node muss festgestellt werden, ob es allfällige Änderungen, welche zu speichern sind, gegeben hat. Dies erfolgt über die Methode pvExplorer_BeforeNodeSelectionChange(), welche analog zur oben beschriebenen Methode diesmal die SaveData() Methode des entsprechenden Forms aufruft.

Die SaveData() Methode präsentiert sich wie folgt:

    Public Sub saveData(ByVal toNode As pvxNode)

      If hasChanged(toNode) Then If MsgBox("Do you want to save your changes ?", vbYesNo +
      vbQuestion, "Data has been changed!") = vbYes Then

        ' Save Changes Call saveCrop

      Else ' Requery Data

        Call RefreshData(toNode)

      End If

    End If End Sub

Die Prüfung, ob Daten verändert wurden, erfolgt letztendlich durch einen Vergleich der ursprünglich bezogenen aAllData Werte mit den Werten aus den Controls, welche der Benutzer evtl. verändert hat. Die hasChanged() Methode präsentiert sich wie folgt:

    Private Function hasChanged(ByVal toNode As pvxNode) As Boolean
      Dim llRetVal As Boolean 
      Dim laData() As Variant 
      Call prepareSaveData(laData, 0) 

      Dim lnRow As Integer 
      ' Nächste veränderte Row ermitteln (liefert -1 falls keine 
      Änderungen gefunden wurden) 
      llRetVal = modGeneral.getNextChange(laData) > -1 hasChanged = llRetVal

    End Function

Die PrepareSaveData() Methode sieht folgendermassen aus:

    Private Sub prepareSaveData(ByRef taData() As Variant, ByVal tnTab As Integer)

    • Dim lnRow, lnRowCount, lnCol, lnColCount As Integer 
      lnRowCount = UBound(aAllData(0), 1) 
      lnColCount = UBound(aAllData(0), 2) 

      ' Neues 3d-Array mit row * columns * 2 ebenen 
      ' Ebene 0: Daten aus letztem Datenbezug (aAllData) 
      ' Ebene 1: Daten aus den Controls des Forms (pvNumerics) ' Columns 0-9:values_ii, 10:fieldprefix 

      ' aAllData() Struktur 

      ' rows: 0:area: columns: 0-9:values_ii, 10:Label, 11:uom 
      ' 1:seedrate: columns: 0-9:values_ii, 10:Label, 11:uom 

      ReDim taData(1, 10, 1) 
      Dim lnIndex As Integer 

      ' AREA 
      taData(0, 10, 0) = aAllData(tnTab)(0, 10) 
      For lnCol = 0 To 9 

        taData(0, lnCol, 0) = aAllData(tnTab)(0, lnCol) 
        taData(0, lnCol, 1) = pvArea(lnCol).ValueReal 

      Next 

      ' SEEDRATE 
      taData(1, 10, 0) = aAllData(0)(1, 10) 
      For lnCol = 0 To 9

        taData(1, lnCol, 0) = aAllData(0)(1, lnCol) 
        taData(1, lnCol, 1) = pvSeed(lnCol).ValueReal

      Next 

    End Sub

      Hier wird ein neues 3D Array aufgebaut. Dieses Array hat 2 Ebenen. Die Ebene 1 beinhaltet die Daten aus aAllData und die Ebene 2 die Daten aus den Controls.

      Die Funktion getnextchange() ist für das Aufspüren von Veränderungen zuständig. Diese befindet sich im Modul modGeneral.

        Public Function getNextChange(ByRef taData() As Variant, Optional ByVal 
        tnStartAt As Integer = 0) As Integer

          Dim lnRetVal As Integer 
          lnRetVal = -1 

          Dim lnRowCount, lnRow As Integer 
          Dim lnFieldCount, lnField As Integer 
          lnRowCount = UBound(taData, 1) 
          lnFieldCount = 9 
          For lnRow = tnStartAt To lnRowCount

            For lnField = 0 To lnFieldCount

              If Abs(taData(lnRow, lnField, 0) - taData(lnRow, lnField,

           1)) >= 0.01 Then 

              lnRetVal = lnRow Exit For

            End If

          Next 

          If lnRetVal > -1 Then

            ' Änderung gefunden also raus 
            Exit For

          End If

        Next 

        getNextChange = lnRetVal ' Row in welcher eine Änderung passierte sonst -1

      End Function

Der eigentliche Speichervorgang wird mit der Methode SaveCrop() angestossen:

    Private Sub saveCrop()

      Dim laData() As Variant 
      Call prepareSaveData(laData, 0) 

      Dim loMktPlan As ImpactData2b.clsMktPlan 
      Set loMktPlan = New ImpactData2b.clsMktPlan 

      ' Key Array 
      Dim laKeys(9, 1) As Variant 
      laKeys(0, 0) = "plu" 
      laKeys(0, 1) = cPLU 
      laKeys(1, 0) = "pluext" 
      laKeys(1, 1) = cPluExt 
      laKeys(2, 0) = "year" 
      laKeys(2, 1) = nYear 
      laKeys(3, 0) = "crop" 
      laKeys(3, 1) = cCrop 
      laKeys(4, 0) = "croptech" 
      laKeys(4, 1) = "" 
      laKeys(5, 0) = "prodtype" 
      laKeys(5, 1) = "" 
      laKeys(6, 0) = "submkt" 
      laKeys(6, 1) = "" 
      laKeys(7, 0) = "targ" 
      laKeys(7, 1) = "" 
      laKeys(8, 0) = "company" 
      laKeys(8, 1) = "" 
      laKeys(9, 0) = "Lprodid" 
      laKeys(9, 1) = 0 

      Dim lnRetVal As Integer 
      lnRetVal = loMktPlan.saveSingle(laKeys, laData, 

    modConstants.const_LEVEL_CROP) 

      Set loMktPlan = Nothing

    End Sub

Die Applikations Logik Schicht

Die Anwendungsbeispiele entsprechen den oben aufgeführten.

Anwendungsbeispiel 1

In diesem Beispiel wird aus der Business Komponente die Methode getindex() aufgerufen. Diese Methode ist es, welche die Verbindung zum Data Tier herstellt, einen Datenbezug durchführt und einen disconnected Recordset zurückliefert und die Verbindung wieder schliesst.

    Public Function getIndex(ByVal tcPLU As String, _
          ByVal tcPLUExt As String, 
          _ ByVal tnYear As Integer, 
          _ ByVal tcCrop As String, 
          _ ByVal tcProdType As String) As ADODB.Recordset
      ' Optionale 
      If tcProdType = "" Then 
        tcProdType = "%"
      End If 

      If tcCrop = "" Then

        tcCrop = "%"
      End If 

      Dim oConn As ADODB.Connection
      Set oConn = New ADODB.Connection
      oConn.CursorLocation = adUseClient
      oConn.ConnectionString = modConnData.gs_ConnectionString
      oConn.Open

      Dim oCmd As ADODB.Command
      Dim oParam As ADODB.Parameter

      Set oCmd = New ADODB.Command

      'Command definieren
      Set oCmd.ActiveConnection = oConn
      oCmd.CommandType = adCmdStoredProc
      oCmd.CommandText = "usp_getMktPlanIndex"

      'Parameter erstellen
      Set oParam = oCmd.CreateParameter("tcPLU",

            adVarChar, adParamInput, 10, tcPLU)
      oCmd.Parameters.Append oParam
        Set oParam = oCmd.CreateParameter("tcPluExt",
            adVarChar, adParamInput, 10, tcPLUExt)
      oCmd.Parameters.Append oParam
      Set oParam = oCmd.CreateParameter("tnYear",
            adInteger, adParamInput, 4, tnYear)
      oCmd.Parameters.Append oParam
      Set oParam = oCmd.CreateParameter("tcCrop",
            adVarChar, adParamInput, 6, tcCrop)
      oCmd.Parameters.Append oParam
      Set oParam = oCmd.CreateParameter("tcProdType",
            adVarChar, adParamInput, 10, tcProdType)
      oCmd.Parameters.Append oParam

      Dim lnRecsAffected As Long
      Set getIndex = oCmd.Execute(lnRecsAffected)
      Set oConn = Nothing

    End Function

Anwendungsbeispiel 2

In der Mittelschicht läuft folgender Code ab:

    Public Function queryAll() As ADODB.Recordset

      Dim loConn As ADODB.Connection 
      Set loConn = New ADODB.Connection 
      loConn.ConnectionString = modConnData.gs_ConnectionString 
      loConn.Open 

      Dim lcSQL As String 
      lcSQL = "select * from prodtype order by prodtypeDescr" 

      Dim loRS As ADODB.Recordset 
      Set loRS = New ADODB.Recordset 
      loRS.CursorLocation = adUseClient 
      loRS.Open lcSQL, loConn, adOpenStatic, 

          adLockBatchOptimistic, adCmdText

      Set loConn = Nothing 
      Set queryAll = loRS

    End Function

Anwendungsbeispiel 3

Die Methode getcropArea() ist für den Datenbezug zuständig.

    Public Function getCropArea(tcPLU As String, tcPLUExt As String, tnYear 
    As Integer, tcCrop As String) As ADODB.Recordset

      Dim loConn As ADODB.Connection 
      Set loConn = New ADODB.Connection 
      loConn.ConnectionString = modConnData.gs_ConnectionString 
      loConn.Open 

      Dim lcWhere As String 
      lcWhere = " where plu = " & modFunctions.strsql(tcPLU) & _

        " and pluext = " & modFunctions.strsql(tcPLUExt) & _ 
        " and year = " & modFunctions.numsql(tnYear) & _ 
        " and crop = " & modFunctions.strsql(tcCrop)

      Dim lcSQL As String 
      ' area_ii, 'AREA', areauom 
      ' seedrate_ii, 'SEEDRATE', seedunituom 
      lcSQL = "select " & 
      modFunctions.buildFieldListExpr("isnull(<area>,0)",

            "<area>;area", "val") & _

          ", 'AREA' label" & _

        ", areaunit uom" & _

      " from PLCROP" & _ 
      lcWhere 

    lcSQL = lcSQL & " UNION ALL " & _

      "select " & 

    modFunctions.buildFieldListExpr("isnull(,0)",

        ";seedrate", "val") & _ ", 'SEEDRATE' label" & _ ", seedunit uom" & _

    " from PLCROP" & _ lcWhere

      Dim loRS As ADODB.Recordset
      Set loRS = New ADODB.Recordset
      loRS.CursorLocation = adUseClient
      loRS.Open lcSQL, loConn, adOpenStatic,

            adLockBatchOptimistic, adCmdText

      Set getCropArea = loRS 

    End Function

Hier erkennt man, wie der Recordset mit 2 Records mittels einer UNION ALL Anweisung aufgebaut wird.

Anwendungsbeispiel 4

Die Methode SaveSingle() ist für das Abspeichern der Daten zuständig. Damit der Client nichts mit der Speicherlogik zu tun hat und auch um Form seitenweise jeweils in einem Prozess als Ganzes abspeichern zu können, wird der Methode auch der 3 D Array mit den Ursprungs- und den aktuellen Werten übergeben.

    Public Function saveSingle(ByRef taKeys(), taData() As Variant, tnLevel 
    As Integer) As Integer

      Dim lnRetVal, lnResult As Integer 
      lnRetVal = 0 

      Dim loConn As ADODB.Connection 
      Set loConn = New ADODB.Connection 
      loConn.ConnectionString = modConnData.gs_ConnectionString 
      loConn.Open



Wird in der Speicherlogik auch noch der aktuelle Wert in der DB benötigt, so wird dieser explizit von der Mittelschicht bezogen.

Die Daten Schicht

Die Anwendungsbeispiele entsprechen den oben aufgeführten.

Anwendungsbeispiel 1

In diesem Beispiel ruft die Business Komponente die Stored Procedure "usp_getMktplanIndex" auf.

    create procedure usp_getMktPlanIndex 
    @tcPLU as varchar(10), 
    @tcPLUExt as varchar(10), 
    @tnYear as int, 
    @tcCrop as varchar(6), 
    @tcProdType as varchar(2) 
    as 
    begin

      /* Code… */ select "felder"

        from "tabelle" 
        where plu = @tcPLU and PLUExt = @tcPLUExt and Year = @tnYear and

          crop like @tcCrop and prodtype like @tcProdType

        order by "sortierung"

    end

Anwendungsbeispiel 2

Der Data Tier erhält über die Business Komponente eine SQL Anfrage und retourniert die angeforderten Daten.

Anwendungsbeispiel 3

Der Data Tier erhält über die Business Komponente eine SQL Anfrage und retourniert die angeforderten Daten.

Anwendungsbeispiel 4

Der Data Tier erhält über die Business Komponente eine SQL Anfrage und retourniert die angeforderten Daten.

SOAP/XML vs. DCOM

Grenzen von COM/DCOM

COM resp. DCOM ist ein sog. "multi port protocol". Es arbeitet hoch effizient in geschützten LAN Bereichen. Wird hingegen das Internet als globales IP Netz verwendet, so machen die dort vorhandenen "Network Address Translators" sowie die "Firewalls" DCOM das Leben schwer, da nicht alle Ports frei sind und auch schwer vorauszusagen ist, welche freigehalten werden können. Obwohl Microsoft vor einiger Zeit noch von "DCOM over HTTP" gesprochen hat, zeigt sich heute klar, dass diese Art der Kommunikation zwischen User Tier und Business Tier stattdessen via XML implementiert wird.

Vision für n-Tier Anwendungen

Es zeichnet sich ab, dass in Zukunft vermehrt XML als das Kommunikationsprotokoll zwischen User Tier und Business Tier verwendet wird. Die Vision, wie heutige n-Tier Entwicklungen auf die neue .net Platform gebracht werden können zeigt der im Folgenden beschriebene SOAP Toolkit Beispielanwendung auf. Von einem Anwendungs Entwicklungs Standpunkt aus müsste man sagen: "Mir ist es egal, ob DCOM oder XML, ich will einfach, dass es läuft". Um das zu erzielen, ist es sinnvoll, sich von den Spazifika DCOM vs. SOAP/XML nach Möglichkeit abzuschotten, ja sogar vor den Änderungen, die noch kommen werden. Microsoft ist ja dafür bekannt, bis kurz vor eine definitive Version noch einiges zu verbessern.

Das Prinzip der Remote Message Invocation

Bei einem DCOM Aufruf wird ein Remote Procedure Call abgesetzt. Für den Entwickler geschieht das ganze dank DCOM völlig transparent. Der Code präsentiert sich typischerweise folgendermassen:

    Dim oImpData As ImpactData2b.clsMktPlan 
    Set oImpData = New ImpactData2b.clsMktPlan 
    Dim oRS As Adodb.Recordset 
    Set oRS = oImpData.getIndex(tcPlu, tcPluExt, tnYear, tcCrop, tcProdType) 
    Set oImpData = Nothing
     … 
    …(Verarbeitungslogik) 
    … 
    oRS.Close 
    Set oRS = Nothing

Egal, ob die aufgerufene Methode in einer dll auf Maschine x oder y, oder sogar auf der eigenen läuft, der Aufruf bleibt immer derselbe. Hierzu wird in der Registry nachgeschaut, wo diese dll registriert ist. Handelt es sich um eine Remote Registrierung, wird vollautomatisch ein RPC via DCOM abgesetzt.

Bei der Kommunikation via XML wird mittels dem SOAP Toolkit versucht, dieser Lösung möglichst nahe zu kommen. Hierzu wird mit einer sog. ROPE.DLL eine Brücke zwischen dem Client und dem Server geschlagen. Die ROPE.DLL ist es, die den Hauptteil der Remote Message Invocation abnimmt und hierzu das XML als Kommunikationsmedium über das standard HTTP Protokoll verwendet.

Der SOAP Toolkit

Microsoft hat mit der gesamten .net Strategie vor, die Entwicklung und das via XML zur Verfügung stellen von sog. Webservices, zu vereinfachen. Dank dem SOAP Toolkit ist es heute schon auf relativ einfache Art und Weise möglich, Business Funktionalität über das Internet den Windows Clients zur Verfügung zu stellen. Der Soap Toolkit kann von der Microsoft homepage runtergeladen werden. Ich verzichte hier darauf einen Link anzugeben, da sich die Lokationen so rasch ändern, dass Sie sowieso mit der Suche nach "SOAP Toolkit" besser bedient sind.

Ich will an dieser Stelle nicht das wiederholen, was in der SOAP Toolkit Dokumentation steht. Stattdessen will ich eine verständliche Übersicht geben und das mit einer Live Demonstration einer selber entwickelten Beispielanwendung illustrieren.

XML

XML ist das Format, welches für den Datenaustausch verwendet wird. Wenn man mit dem SOAP Toolkit arbeitet, muss man keine XML Dateien von Hand erzeugen!

HTTP

SOAP läuft über das HTTP Protokoll auf dem Port 80 und kann somit auch in Umgebungen mit Network Address Translation und Firewalls eingesetzt werden. Konkret: Überall, wo eine HTML Webseite durchkommt, kann auch ein XML File transportiert werden.

SOAP

SOAP steht für Simple Object Access Protocol. Die SOAP Spezifikationen sind beim W3C Konsortium hinterlegt.

SDL

SDL steht für Service Description Language. Diesen Begriff werden Sie noch oft unter Visual Studio.net zu hören bekommen. Es handelt sich hier um ein XML File, welches die zur Verfügung stehenden Services auflistet. Man kann sich darunter auch eine Type Lib im XML Format vorstellen.

COM

Der SOAP Toolkit verwendet auf der Serverseite COM um die Serverkomponenten aus dem Transaction Server aufzurufen. Generell zeigt sich in diesem Szenario, dass COM lediglich auf der Serverseite zum Einsatz gelangt.

IIS

Der Ganze Ablauf benötigt einen Webserver. Der HTTP Post gelangt auf den Webserver und dort werden die Serverkomponenten instanziiert. Auf dem Web muss eine listener.asp vorhanden sein plus eine weitere asp Datei und ein XML File.

Übersicht

In der SOAP Dokumentation ist eine Übersicht über den grundsätzlichen Ablauf unter Verwendung der ROPE.DLL. Im Folgenden wird anhand unserer Beispielanwendung gezeigt, wie sich das Ganze aus Entwicklersicht präsentiert:

SOAP Beispielanwendung

Bei unserer Beispielanwendung handelt es sich um eine VB Client Applikation, welche via SOAP Daten in Form eines Recordsets bezieht.

      Der Client

Der Datenbezug präsentiert sich wie folgt:

        Dim sXML As String 
        Dim oXML As MSXML.DOMDocument 
        Dim oRope As ROPE.Proxy 

        Set oXML = New MSXML.DOMDocument 
        Set oRope = New ROPE.Proxy 

        Set oRs = New ADODB.Recordset 
        oRope.LoadServicesDescription icURI,

    "http://212.59.144.70/soaptest/address.xml"

        sXML = oRope.GetXMLStream(vintsearchmode:=2, _

            vstrfirstname:="", _ 
            vstrlastname:="")

        oXML.loadXML sXML 

        oRs.Open oXML 

        Set oXML = Nothing 

        Set oRope = Nothing

Die Rope.dll übernimmt quasi die Arbeit, die eigene dll, welche im MTS läuft und aus der ASP Seite aufgerufen wird, zur Verfügung zu stellen. Rope.dll ist auf dem Client und dem Server installiert und stellt folgenden Ablauf sicher:

  1. Client erstellt eine Objektreferenz zur "rope.dll"
  2. Mit dem Aufruf "oRope.LoadServicesDescription" wird das SDL File (address.xml) gelesen um festzustellen, welche Methoden zur Verfüguing stehen
  3. Der Client ruft die gewünschte Methode auf. Der rope Proxy formt diese Anfrage in eine SOAP Anfrage um, wartet auf die Antwort, formt das Resultat wieder um und stellt es dem Client zur Verfügung

Der Server

Der Server erhält einen Web Request via HTTP. Das Web beinhaltet die Dateien address.asp, address.xml sowie listener.asp. Der Ablauf ist der Folgende:

  1. Der Webserver erhält einen HTTP Request. Dieser erhält über den Web Request das vom Client erstellte XML File, welches dem listener.asp weitergeleitet wird
  2. listener.asp liest die XML Message und routet den Aufruf an die selbst geschriebene dll weiter. Aus der address.asp wird die eigene dll letztendlich instanziiert.
  3. Das Resultat wird dem listener übergeben
  4. Der Listener packt das Resultat in ein XML File, welches als SOAP Message wieder an den Client geschickt wird (auf dem Client wird dieses Package wieder vom Rope Proxy in Empfang genommen, wie oben beschrieben)

Das address.asp File präsentiert sich wie folgt:

    <%@ Language=VBScript %>

    <% Option Explicit

    Response.Expires = 0

    '--------------------------------------------

    ' SOAP ASP Interface file Address.asp

    ' Generated 9/25/2000 4:23:23 PM

    ' By Microsoft SOAP Toolkit Wizard, Version 205.0.3

    '--------------------------------------------

    Const SOAP_SDLURI = "http://inspiron3800/soaptest/Address.xml" 'URI of service description file %>

    <!--#include file="listener.asp"-->

     

    <%

    '_____________________________________________________________________

    Public Function GetXMLStream (ByVal vintsearchmode, ByVal vstrFirstname, ByVal vstrLastname)

       Dim objGetXMLStream

       Set objGetXMLStream = Server.CreateObject("SOAPTestData.Address")

       GetXMLStream = objGetXMLStream.GetXMLStream(vintsearchmode, vstrFirstname, vstrLastname)

       'Insert additional code here

       Set objGetXMLStream = NOTHING

    End Function

    '_____________________________________________________________________

     

    Public Function Update (ByVal tnAddressid, ByVal tcFirstname, ByVal tcLastname, ByVal tcStreet, ByVal tcZip, ByVal tcCity)

       Dim objUpdate

       Set objUpdate = Server.CreateObject("SOAPTestData.Address")

       Update = objUpdate.Update(tnAddressid, tcFirstname, tcLastname, tcStreet, tcZip, tcCity)

       'Insert additional code here

       Set objUpdate = NOTHING

    End Function

    '_____________________________________________________________________

    Public Function getVersion ()

       Dim objgetVersion

       Set objgetVersion = Server.CreateObject("SOAPTestData.Address")

       getVersion = objgetVersion.getVersion()

       'Insert additional code here

       Set objgetVersion = NOTHING

    End Function

    '_____________________________________________________________________

    Public Function getNumber ()

       Dim objgetNumber

       Set objgetNumber = Server.CreateObject("SOAPTestData.Address")

       getNumber = objgetNumber.getNumber()

       'Insert additional code here

       Set objgetNumber = NOTHING

    End Function

    '_____________________________________________________________________

    %>

      Man erkennt, dass der eigene Code über die server.createobject Anweisung instanziiert wird. Hier erfolgt letztendlich der Aufruf des eigenen Codes.

Man erkennt, dass der eigene Code über die server.createobject Anweisung instanziiert wird. Hier erfolgt letztendlich der Aufruf des eigenen Codes.

[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ]