Dokument in enaio per API erzeugen

Grüß’ Euch alle,

dieser Post lässt mich hoffen, dass mir hier vielleicht jemand weiterhelfen kann.

In einem Event-Skript habe ich testweise eine Funktion angelegt um per API ein neues Dokument in enaio abzulegen. Ich habe die „alte“ AppConnector-API verwendet, weil ich beim neuen DMS Service beim Aufruf von POST /api/dms/objects immer ein „method not implemented“ erhalte.

Die AppConnector-Api läuft bei mir aber auch immer auf einen 500er hinaus.
Vielleicht mache ich was ganz falsch.

Hier meine Funktion:

sub writeDataToNewFile(input_location, input_filename, input_content)

  msgBox "Create File: " & input_filename & " in FolderID " & input_location 

  Set object_XMLHTTP = CreateObject("MSXML2.ServerXMLHTTP.6.0")

  enaio_endpoint = "http://er-anw-enaiotest.kbit.intern/osrest/api/documents/insert/" &  input_location	

  dim request_body, request_boundary, request_metadata, const_ObjectTypeID, const_Cabinet, currentDate

  ' assemble the metadata
  const_ObjectTypeID = 262145 'Object Type ID of "Akte:Dokument"
  const_Cabinet = "Akte"
  currentDate = now()
  request_metadata = "{""data"": {"
  request_metadata = request_metadata & """cabinet"": """ & const_Cabinet & ""","
  request_metadata = request_metadata & """objectTypeId"": """ & const_ObjectTypeID & ""","
  request_metadata = request_metadata & """fields"": {"
  request_metadata = request_metadata & """MAIL_SUBJECT"": {""value"": """ & input_filename & """},"
  request_metadata = request_metadata & """MAIL_SUBMIT_TIME"": {""value"": """ & dateString(currentDate) & """}"
  request_metadata = request_metadata & "}"
  request_metadata = request_metadata & "}}"


  'assemble the request_body 
  request_boundary = "----webKitFormBoundary8IMRZEG5U0UOXO3W"
  request_body = "--" & request_boundary & vbCrLf
  request_body = request_body & "Content-Disposition: form-data; name=""metadata""" & vbCrLf
  request_body = request_body & "Content-Type: application/json" & vbCrLf & vbCrLf
  request_body = request_body & request_metadata & vbCrLf
  request_body = request_body & "--" & request_boundary & vbCrLf
  request_body = request_body & "Content-Disposition: form-data; name=""file""" & vbCrLf
  request_body = request_body & "Content-Type: application/octet-stream" & vbCrLf
  request_body = request_body & "Content-Transfer-Encoding: base64" & vbCrLf & vbCrLf
  request_body = request_body & input_content & vbCrLf
  request_body = request_body & "--" & request_boundary & "--" & vbCrLf

  writeToDebugFile(request_body)

  ' Open the connection
  object_XMLHTTP.open "POST", enaio_endpoint, true

  ' Set the headers
  object_XMLHTTP.setRequestHeader "Content-Type", "multipart/form-data"
  object_XMLHTTP.setRequestHeader "Authorization", "Basic ...="

  ' Send the request with the JSON payload
  object_XMLHTTP.send request_body

  ' Wait until the response is fully received
  while object_XMLHTTP.readyState <> 4
    object_XMLHTTP.waitForResponse(1)  'Pause for 1 second before checking the readyState again
  wend

  httpStatus = object_XMLHTTP.Status
  if httpStatus = 200 then	
    msgBox object_XMLHTTP.responseText
  else  
    msgBox "Es ist ein Fehler bei der Kommunikation mit der enaio-API aufgetreten. HTTP Status: " & httpStatus & " " & object_XMLHTTP.StatusText
	writeToDebugFile object_XMLHTTP.responseText
  end if

end sub

Zum Hintergrund: Ich erhalte den input_content als base64-Blob aus einem anderen Api-Aufruf - und fände es am besten, diesen Blob direkt als neues Dokument in enaio abzulegen.

Die von mir in writeToDebugFile generierte Datei sieht etwa so aus:

------webKitFormBoundary8IMRZEG5U0UOXO3W
Content-Disposition: form-data; name="metadata"
Content-Type: application/json

{"data": {"cabinet": "Akte","objectTypeId": "262145","fields": {"MAIL_SUBJECT": {"value": "Kopfbogen_ER.docx"},"MAIL_SUBMIT_TIME": {"value": "27.06.2024"}}}}
------webKitFormBoundary8IMRZEG5U0UOXO3W
Content-Disposition: form-data; name="file"
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64

UEsDBBQACAgIA ... base64-encoded blob ... AAAA=
------webKitFormBoundary8IMRZEG5U0UOXO3W--
2 „Gefällt mir“

Hallo @hajoh,

in welchem Kontext befinden Sie sich hier?
Prinzipiell kann man auch direkt die enaio API verwenden, sofern auf der ausführenden Maschine ein laufender enaio Client bzw. die enaio Server API zur Verfügung steht.
Je nach Anwendungsfall könnte das auch relevant sein, da man so die Session und Rechte des Benutzers verwenden kann. Falls ja, dann kann ich Ihnen hier ein Beispiel liefern.

Der DMS-Service hat ein etwas anderes Format, was vermutlich etwas aufwändig ist mit reinem VBS.
https://help.optimal-systems.com/enaio_develop/pages/viewpage.action?pageId=14418003

Es baut auf eine mutlipart/formdata auf und hat mindestens den Part mit dem Namen Data (hier kommen die Metadaten hin). In den Metadaten werden dann die einzelnen Dateien im Bereich ContentStreams mit CIDs definiert (meist nur eine, ausser es handelt sich um Bild Formate wie singlepage Tifs etc, Typ 1,2,3).

Diese CIDs beschreiben, welche weiteren Parts die angehängten Dokumente sind. Schlüssel ist hierbei der Name der weiteren Parts welche der CID entsprechen müssen.

Grüsse
Uli

2 „Gefällt mir“

Hallo @hajoh, wie @uw würde mich brennen interessieren, ob es sich um ein VBScript handelt, welches als Client- oder Server-Event im enaio „lebt“ oder ob es sich um etwas handelt, was unabhängig vom enaio ausgeführt wird.

Leider ist VBScript etwas umständlich, was REST-Requests, JSON etc. angeht. Ist VBScript eine harte Anforderung?

1 „Gefällt mir“

ich befinde mich innerhalb eines OnClickItem-Events.
Um Rechte/Session möchte ich mich kümmern, sobald der Test einmal erfolgreich war.

Der DMS-Service wäre mir tatsächlich lieber - aber wie oben erwähnt, erhalte ich immer eine „Service method not implemented“-Antwort vom Server, sobald ich einen POST-Request an den /api/dms/objects Endpunkt sende.

1 „Gefällt mir“

Letztendlich muss ich einen Prozess aus dem Userkontext des RichClients starten - und dabei Daten (zumindest Indextdaten) aus dem Kontext übernehmen … also ist VBScript „quasi“ Pflicht

Hallo @hajoh,

wenn ich das letzte Beispiel richtig lese, dann müsste der erste Part „data“ heissen und nicht „metadata“.

Dann müsstest du noch die ContentStreams definieren und die CID als Name des zweiten Parts, aktuell „file“, entsprechend benennen.

Im Client Event wäre sonst die DRTSession verfügbar, welche alle Funktionen der enaio Server API bietet.

Gruss
Uli

Was meinst Du mit „ContentStreams definieren“?
Ich war bisher der Meinung, der untere Part ist der „Stream“ :thinking:

------webKitFormBoundary8IMRZEG5U0UOXO3W
Content-Disposition: form-data; name="data"
Content-Type: application/json

{"data": {"cabinet": "Akte","objectTypeId": "262145","fields": {"MAIL_SUBJECT": {"value": "Kopfbogen_ER.docx"},"MAIL_SUBMIT_TIME": {"value": "27.06.2024"}}}}
------webKitFormBoundary8IMRZEG5U0UOXO3W
Content-Disposition: form-data; name="<ABC123>"
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64

<ABC123>
UEsDBBQACAgIAKuB21gAAAAAAA...

Hallo @hajoh ,

so ist es in der Dokumentation im Beispiel dargestellt. Bin leider schon unterwegs aber ich hoffe der Screenshot von Handy funktioniert :slight_smile:

Gruss Uli

das ist doch die DMS Services API (welche bei mir für den Endpunkt immer nur einen 404er zurückschickt) - mein Beispiel oben ist aus dem AppConnector

Hallo zusammen,

mit OSRest und einem direkten Schreiben habe ich auch keine guten Ergebnisse:

Dim host: host = "http://localhost"
Dim locationId: locationId = 749
Dim boundary: boundary = "----ECMind-1-2-3----"
Dim username: username = "root"
Dim password: password = "optimal"
Dim authHeader: authHeader = "Basic " & Base64Encode(username & ":" & password)

Function Base64Encode(str)
    Dim xml: Set xml = CreateObject("MSXML2.DOMDocument")
    Dim node: Set node = xml.CreateElement("base64")
    node.DataType = "bin.base64"
    node.NodeTypedValue = Stream_StringToBinary(str)
    Base64Encode = node.Text
    Set node = Nothing
    Set xml = Nothing
End Function

Function Stream_StringToBinary(Text)
    Dim objStream: Set objStream = CreateObject("ADODB.Stream")
    objStream.Type = 2 ' adTypeText
    objStream.Charset = "us-ascii"
    objStream.Open
    objStream.WriteText Text
    objStream.Position = 0
    objStream.Type = 1 ' adTypeBinary
    Stream_StringToBinary = objStream.Read
    objStream.Close
    Set objStream = Nothing
End Function

Dim http: Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")
http.Open "POST", host & "/osrest/api/documents/insert/" & locationId, False
http.setRequestHeader "Content-Type", "multipart/form-data; boundary=" & boundary
http.setRequestHeader "Authorization", authHeader

Dim requestBody: requestBody = "--" & boundary & vbCrLf
requestBody = requestBody & "Content-Disposition: form-data; name=""metadata""" & vbCrLf & vbCrLf
requestBody = requestBody & "{ ""cabinet"": ""Test"", ""name"": ""WTest"", ""objectTypeId"": ""262145"", ""fields"": { ""Text0"": { ""internalName"": ""Text0"", ""value"": ""Test-PDF"" } } }" & vbCrLf
requestBody = requestBody & "--" & boundary & vbCrLf
requestBody = requestBody & "Content-Disposition: form-data; name=""file""; filename=""Test.pdf""" & vbCrLf
requestBody = requestBody & "Content-Type: application/pdf" & vbCrLf 
requestBody = requestBody & "Content-Transfer-Encoding: base64" & vbCrLf & vbCrLf
requestBody = requestBody & "JVBERi0xLjIgCjkgMCBvYmoKPDwKPj4Kc3RyZWFtCkJULyA5IFRmKEVDTWluZCBtaW5pIHRlc3Qg" & vbCrLf
requestBody = requestBody & "UERGKScgRVQKZW5kc3RyZWFtCmVuZG9iago0IDAgb2JqCjw8Ci9UeXBlIC9QYWdlCi9QYXJlbnQg" & vbCrLf
requestBody = requestBody & "NSAwIFIKL0NvbnRlbnRzIDkgMCBSCj4+CmVuZG9iago1IDAgb2JqCjw8Ci9LaWRzIFs0IDAgUiBd" & vbCrLf
requestBody = requestBody & "Ci9Db3VudCAxCi9UeXBlIC9QYWdlcwovTWVkaWFCb3ggWyAwIDAgOTkgOSBdCj4+CmVuZG9iagoz" & vbCrLf
requestBody = requestBody & "IDAgb2JqCjw8Ci9QYWdlcyA1IDAgUgovVHlwZSAvQ2F0YWxvZwo+PgplbmRvYmoKdHJhaWxlcgo8" & vbCrLf
requestBody = requestBody & "PAovUm9vdCAzIDAgUgo+PgolJUVPRgo=" & vbCrLf
requestBody = requestBody & "--" & boundary & "--"

http.Send requestBody
Dim response: response = http.responseText
WScript.Echo response

Das Dokument wird brav importiert, ist danach aber eine Textdatei, welche meinen Base64-Code des PDFs enthält:

Es ist, als würde das Content-Transfer-Encoding: base64 nicht beachtet.

@uw meinte, dass dieses Beispiel ggf. funktioniert: excel - How to upload a pdf file through a POST request using VBA - Stack Overflow

Allerdings wäre es definitiv stabiler, die normale Server-API oder sogar Client-COM-API zu verwenden, wenngleich dann eine Temp-Datei geschrieben werden muss.

1 „Gefällt mir“

Vielen Dank für’s Testen :sunglasses:
Ich habe mit der ServerApi bisher noch nicht gearbeitet - ich nehme an, dass ich der auch keinen base64-Stream übergeben kann - ich also definitiv eine lokale Temp-Datei brauche.

Hallo @hajoh, nur über grobe Umwege (die für diesen Fall aus meiner Sicht gar nicht infrage kommen) ist ein direkter Stream meines Wissens nach möglich. :frowning:

Da wir hier in einem Client-Event sind, wäre sogar die Frage, ob dann der Weg über die Client-COM nicht etwas netter ist.

Ganz andere Idee (und so, wie ich es machen würde, da ich keine Lust auf VBScript habe :wink: ):

Statt die externe API anzufragen, könnte das Event einen Python-Service informieren. Dieser könnte die externe API bedienen (fast sicher netter als in VBScript) und dann die Antwort dank dieses Updates direkt streamen (auf der Server-seite):

1 „Gefällt mir“

Ich werde das auf jeden Fall mal testen - für einen breiten Rollout ist aber nicht ideal, weil alle Rechner ein Python-Environment bräuchten.

Nicht unbedingt, wenn der Python-Prozess wiederum einen Web-Service öffnet, den man dann leicht vom Event aus aufrufen kann und alles „komplizierte“ in Python löst:

from fastapi import FastAPI
import uvicorn

app = FastAPI()

@app.get("/items/{object_id}")
def do_stuff(object_id: int):
    ... # <-- Hier Funktion ergänzen
    return {"object_id": "Alles super gelaufen."}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
1 „Gefällt mir“

Ich habe es jetzt noch einmal per DMS Service API versucht.

Hie der Code, den ich in per Swagger UI übergeben habe:

curl -X 'POST' \
  'http://er-anw-enaiotest.kbit.intern:7981/objects?minimalResponse=true' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'data=Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="data"

{
    "objects": [
        {
            "properties": {
                "system:objectTypeId": {
                    "value": "262145"
                },
                "system:parentId": {
                    "value": "5217"
                },
                "MAIL_SUBMIT_TIME": {
                    "value": "2001-01-01"
                },
                "MAIL_SUBJECT": {
                    "value": "Testdokument from Postman"
                },
                "format": {
                    "value": "docx"
                },
                "feld56": {
                    "value": "bearbeitbar"
                }
            }
        },
        "contentStreams": [
"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            "fileName": "file.docx",
            "cid": "cid_file1"
        }]
    ]
}
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="cid_file1"

Content-ID: "cid_file1"
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
Content-Transfer-Encoding: base64
UEsDBBQABgAIAAAAIQAykW9XZgEAAKUFAAATAAg...ganz-viel-base64-code
------WebKitFormBoundary7MA4YWxkTrZu0gW--
'

und hier die Antwort (die ich nicht verstehe):

{
  "httpStatusCode": 500,
  "serviceErrorCode": 1000,
  "time": "2024-07-03T15:52:54.0235121",
  "message": "Content-Type 'application/octet-stream' is not supported",
  "service": "dms"
}

Hallo @hajoh, ich habe in VSCode mit dem Extension [humao.rest-client](VS Marketplace Link: REST Client - Visual Studio Marketplace) Folgendes gemacht:

Klick auf (1) Send Request, das Ergebnis (2) ist für mich gut.

Meine POST-Befehl in VSCode in voller Länger:

POST /api/dms/objects HTTP/1.1
Host: enaio-rk-2021
Accept-Encoding: gzip, deflate, br
Authorization: Basic cm9vdDpvcHRpbWFs
Content-Type: multipart/form-data; boundary="ECMindBoundary"
Content-Length: 26455


--ECMindBoundary
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name=data

{
    "objects": [
        {
            "properties": {
                "system:objectTypeId": {
                    "value": "262145"
                },
                "system:parentId": {
                    "value": "749"
                },
                "Text0": {
                    "value": "TestTestTest"
                }
            },
            "contentStreams": [
                {
                    "mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                    "fileName": "file.docx",
                    "cid": "cid_file1"
                }
            ]
        }
    ]
}
--ECMindBoundary
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
Content-Disposition: form-data; name=cid_file1; filename=Dokument1.docx; filename*=utf-8''Dokument1.docx

@/home/rk/Desktop/Document1.docx
--ECMindBoundary

Unter dem Pfad /home/rk/Desktop/Document1.docx habe ich mir soeben eine Test-DOCX-Datei gelegt. Ich habe es aber auch (erfolgreich) mit Inline-Base64 probiert (zu lange für diesen Post).

Hier die gesamte Antwort:

HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
X-Application-Context: os-gateway:prod:80
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: SAMEORIGIN
Date: Wed, 03 Jul 2024 16:33:52 GMT
Keep-Alive: timeout=60
Connection: keep-alive, close
Content-Type: application/json
Transfer-Encoding: chunked

{
  "objects": [
    {
      "properties": {
        "system:objectId": {
          "value": "2475"
        },
        "system:OBJECT_AVDATE": {
          "value": "2024-07-03"
        },
        "system:OBJECT_CO": {
          "value": "1"
        },
        "system:deleted": {
          "value": "0"
        },
        "system:OBJECT_DOCPAGECOUNT": {
          "value": 0
        },
        "Text2": {
          "value": ""
        },
        "system:REG_ID": {
          "value": "2475"
        },
        "Text1": {
          "value": ""
        },
        "Text0": {
          "value": "TestTestTest"
        },
        "system:creationDate": {
          "value": "2024-07-03T18:33:52+02:00"
        },
        "system:OBJECT_TXTNOTICECOUNT": {
          "value": "0"
        },
        "system:OBJECT_FLAGS": {
          "value": "2"
        },
        "system:OBJECT_MIMETYPEID": {
          "value": "117"
        },
        "system:objectTypeId": {
          "value": "262145"
        },
        "system:createdBy": {
          "value": "ROOT"
        },
        "system:STAMM_TIME": {
          "value": "2024-07-03T18:33:52+02:00"
        },
        "system:OBJECT_MEDDOCID": {
          "value": "4"
        },
        "system:STAMM_ID": {
          "value": "2475"
        },
        "system:lastModifiedBy": {
          "value": "ROOT"
        },
        "system:baseTypeId": {
          "value": "document"
        },
        "system:OBJECT_INDEXHISTFLAGS": {
          "value": "0"
        },
        "system:OBJECT_MEDDIAID": {
          "value": "4"
        },
        "system:OBJECT_DOCHISTFLAGS": {
          "value": "0"
        },
        "system:versionSeriesLockedBy": {
          "value": "0"
        },
        "system:lastModificationDate": {
          "value": "2024-07-03T18:33:52+02:00"
        },
        "system:OBJECT_COUNT": {
          "value": 1
        },
        "system:OBJECT_MAIN": {
          "value": "4"
        },
        "system:OBJECT_PDFANNOTATIONCOUNT": {
          "value": "0"
        },
        "system:OBJECT_CHECKOUTTIME": {
          "value": "0"
        },
        "system:OBJECT_MEDDOCNA": {
          "value": "04\\01\\AB\\000009AB.docx"
        },
        "system:OBJECT_LINKS": {
          "value": "0"
        },
        "system:OBJECT_USERGUID": {
          "value": "177655BE7E554372877680136001B875"
        },
        "system:OBJECT_AVID": {
          "value": "ROOT"
        },
        "system:STAMM_LINKS": {
          "value": "0"
        },
        "system:OBJECT_FILESIZE": {
          "value": 33848
        }
      },
      "contentStreams": [
        {
          "cid": "1",
          "length": 33848,
          "mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
          "fileName": "1.docx"
        }
      ]
    }
  ],
  "failed": []
}
1 „Gefällt mir“

Es wird für mich immer weniger verständlich:

Mein Skript baut diesen Call:

--boundary8IMRZEG5U0UOXO3W
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name=data
{"objects": [{"properties": {"system:objectTypeId": {"value": "262145"},"system:parentId": {"value": "5216"},"MAIL_SUBMIT_TIME": {"value": "2001-01-01"},"MAIL_SUBJECT": {"value": "Testdokument from enaio"},"format": {"value": "docx"},"feld56": {"value": "bearbeitbar"}},"contentStreams": [{"mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document","fileName": "file.docx","cid": "cid_file1"}]}]}
--boundary8IMRZEG5U0UOXO3W
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document
Content-Disposition: form-data; name=cid_file1; filename=Dokument1.docx; filename*=utf-8''Dokument1.docx
UEsDBBQACAgIAMiN6lgAAAAAAAAAAAAA...FBLBQYAAAAAGQAZAFgGAACxVwAAAAA=
--boundary8IMRZEG5U0UOXO3W--

Und erhalte dann diese Fehlermeldung:

{"httpStatusCode":500,"serviceErrorCode":1000,"time":"2024-07-10T17:46:19.3663912","message":"Required part 'data' is not present.","service":"dms"}```

Dabei sieht mein 'data'-part doch genauso aus wie Deiner - die fehlenden NL sollten doch egal :-/

Hoi @hajoh, wirklich verrückt. Vielleicht macht VBScript hier etwas anders/falsch. Was ich mir vorstellen könnte, um der Sache auf die Spur zu kommen (und ohnehin eines der besten Tools in diesem Bereich):

https://mitmproxy.org/

Mit diesem Tool kann man z. B. folgendes tun:

  • Start auf der Kommandozeile: mitmweb -p 8080 --mode reverse:http://localhost:80/ auf dem enaio-Server bzw. mit anderen Hostname auf dem eigenen Rechner
  • Umbiegen der Anfrage aus dem Event auf Port 8080
  • Mitlesen im Browser unter dem Default-Port http://localhost:8081

Im Browser kann man jetzt genau sehen, welche Header, Inhalte etc. geschickt wurden. Ggf. gibt es einen Unterschied zu dem, was „mein“ http-Call oben beinhaltet. So sieht das z. B. bei mir aus:

  1. MITM-Proxy im Web-Modus starten und mit meinem Testsystem verbinden.
  2. Meine Anfrage umbauen auf die Adresse des MITM-Proxys.
  3. Anfrage ist ausgeführt.
  4. Das hier sehe ich im Browser-Fenster, dass sich nach dem Bestätigen mit Enter der Zeile aus 1. geöffnet hat:

Mit einem Klick auf einen Eintrag öffnet sich der komplette Vorgang zur Betrachtung:

Mit der Toolbar kann jeder Eintrag dupliziert, gespeichert, wiederholt und vorher in der Seitenleiste beliebig verbessert/verändert werden:

1 „Gefällt mir“