Worum es geht
Eine Datei kommt auf zwei Wegen zu CDMS:
- Multipart: Die Anfrage besteht aus mehreren Teilen. Ein Teil ist das JSON, die anderen sind die Dateien. Das ist der normale Weg, besonders für größere Dateien.
- Base64: Die Datei steht als Text im JSON selbst. Das ist bequem für kleine Dateien, etwa aus einem Skript, ist aber rund ein Drittel größer als die Datei.
Bei beiden Wegen gilt dieselbe Regel: Der Dateiteil und das Datei-Objekt in data finden über den Namen zusammen.
Multipart
Die /upload-Endpunkte erwarten multipart/form-data mit zwei Arten von Teilen:
| Teil | Inhalt | Anzahl |
|---|---|---|
data | das JSON mit data und response, wie bei einem normalen Create oder Update, mit Content-Type application/json | genau einer |
files | eine Datei; ihr Dateiname muss zu einem name in data passen | beliebig viele, jeder mit einem anderen Namen |
POST /api/rest/fileasset/create/upload
Content-Type: multipart/form-data; boundary=XYZ
--XYZ
Content-Disposition: form-data; name="data"
Content-Type: application/json
{ "data": { "name": "Vertrag.pdf", "title": "Rahmenvertrag" },
"response": ["id", "name", "mimeType", "fileSize"] }
--XYZ
Content-Disposition: form-data; name="files"; filename="Vertrag.pdf"
Content-Type: application/pdf
…Bytes der Datei…
--XYZ--{ "data": { "id": "f1…", "name": "Vertrag.pdf",
"mimeType": "application/pdf", "fileSize": 48213 },
"meta": { "error": false } }mimeType und fileSize hat der Server gesetzt. Den Content-Type des Dateiteils wertet er nicht aus, der Dateityp kommt aus der Endung des Namens.
Im Browser baust du so eine Anfrage mit FormData:
const form = new FormData();
form.append("data", new Blob([JSON.stringify({
data: { name: file.name, title: "Rahmenvertrag" },
response: ["id", "name"]
})], { type: "application/json" }));
form.append("files", file, file.name);
fetch("/api/rest/fileasset/create/upload",
{ method: "POST", body: form, headers: { Authorization: "Bearer " + token } });data als Blob mit type application/json anhängen,
die Datei unter dem Teilnamen "files",
und den Content-Type der Anfrage NICHT selbst setzen:
der Browser ergänzt die boundary.Base64 im JSON
Statt eines Dateiteils schreibst du den Inhalt in das Feld content des Datei-Objekts. Das geht an den normalen JSON-Endpunkten POST /create, PUT und PATCH /update/{id}:
POST /api/rest/fileasset/create
{ "data": { "name": "notiz.txt", "title": "Notiz",
"content": "SGFsbG8gV2VsdA==" },
"response": ["id", "name", "fileSize"] }{ "data": { "id": "f2…", "name": "notiz.txt", "fileSize": 10 },
"meta": { "error": false } }content darf auch als Data-URL kommen, also mit Präfix wie data:text/plain;base64,. CDMS schneidet das Präfix ab. content wird nur zum Hochladen gelesen und nie gespeichert oder zurückgegeben.
/upload-Endpunkte- Bytes unverändert, keine Umrechnung
- gut für große Dateien und Formulare mit Dateiauswahl
- 25 MB je Datei, 500 MB je Request, siehe Größengrenzen
- alles in einem JSON, einfach aus Skripten
- rund ein Drittel größer
- ungültiges Base64 wird übergangen, dann fehlt die Datei
- dieselben Größengrenzen wie Multipart
Mehrere Dateien und verschachtelte Dateien
Ein Request kann mehrere Dateien tragen, auch für Datei-Modelle, die als Kinder an einem anderen Objekt hängen. Jede Datei findet ihr Objekt über den Namen, egal wie tief es in data steht:
POST /api/rest/dossier/create/upload
data: { "data": { "title": "Akte 1",
"attachments": [ { "name": "first.txt" },
{ "name": "second.txt" } ] },
"response": ["id", { "field": "attachments", "response": ["+"] }] }
files: first.txt (Dateiteil)
files: second.txt (Dateiteil)Akte und beide Anhänge werden angelegt,
jeder Anhang mit seinem Inhalt.Dafür bekommen auch Modelle ohne eigene Datei, wie hier die Akte, die /upload-Varianten. Das Kind braucht wie immer das CREATE-Flag an der Beziehung. Siehe Die vier Fälle beim verschachtelten Schreiben.
Der Ablauf
-
1Client→CDMSschickt
dataund die Dateiteile -
2CDMSprüft die Dateiteile: jeder hat einen Namen, kein Name kommt doppelt vor
-
3CDMSlegt jeden Teil als Zwischendatei ab, ebenso jedes
contentaus dem JSON -
4CDMSgeht
datadurch; für jedes Datei-Objekt: Rolle prüfen, Dateiteil über dennamesuchen -
5CDMS→File storagebereitet den Inhalt vor und setzt
fileId,fileSize,mimeType,fileVersion -
6CDMS→DatabaseHooks, Validierung, Datensatz speichern, zurücklesen, Commit
-
7CDMS→File storagemacht den Inhalt wirksam
Aktuell wird der Inhalt erst, wenn die Datenbank festgeschrieben hat. Scheitert die Anfrage vorher, wird er verworfen. Mehr dazu unter Dateien und Transaktion.
Wenn es nicht passt
| Situation | Operation | Ergebnis |
|---|---|---|
Teil passt zum name eines Datei-Objekts | alle | Inhalt wird gespeichert |
| neues Datei-Objekt, kein passender Teil | anlegen | 400 file-part-missing|<name> |
neues Datei-Objekt ohne name | anlegen | 400 file-name-missing |
| bestehendes Datei-Objekt, kein passender Teil | ersetzen, ändern | Inhalt bleibt, wie er ist |
| zwei Teile mit demselben Dateinamen | alle | 400 duplicate-filenames|<name> |
| Teil ohne Dateinamen | alle | 400 missing-filename |
| Teil, zu dem kein Datei-Objekt passt | alle | 400 file-part-unused|<namen> |
Fallen
Wie es weitergeht
- Den Inhalt austauschen: Ersetzen und Umbenennen
- Wie groß eine Datei sein darf: Größengrenzen
- Die Datei wieder holen: Herunterladen