Office Graph API: Neues Dokument auf Basis einer Vorlage
Letztens wollte ich über die Office Graph API ein Word Dokument auf Basis einer Vorlage erstellen und ein Paar Metadaten von dem zugehörigen SharePoint ListItem setzen. Es stellte sich heraus, dass es kein "straight-forward" Job ist, was letztendlich zu diesem Artikel führte.
Problem in a nutshell
Ich habe eine Bibliothek, in dieser Bibliothek sollen Dokumente basierend auf einen ContentType AkDocumentDoc angelegt werden können. Der Besagte ContentType ist vom Typ Document und hat ein Paar Spalten zusätzlich.
Der AkDocumentDoc ContentType hat noch ein Word Template hinterlegt, wodurch beim Erstellen eines Elements auf Basis von AkDocumentDoc zugleich ein Word Dokument mit dieser Vorlage angelegt wird. Die Vorlage nennt sich Prüf.dotx - mir ist nichts besseres eingefallen in der Regel verwende ich meistens bla oder blabla, es muss ja immer schnell gehen :)
Nun soll eine Logik - egal wie getriggert und egal wo - basierend auf diesem ContentType neue Elemente in dieser Bibliothek anlegen und die Metadaten von dem zugehörigen SharePoint ListItem setzen.
Alle Request in diesem Artikel können entweder über den Graph Explorer oder z.B. Postman abgesetzt werden. Für letzteres muss der OAuth2 Token zuerst beschafft werden und bei jeder Query hinzugefügt werden.
Die Umgebung in a nutshell
Für dieses Beispiel verwende ich eine fiktive O365 Subscription, für die jeweiligen Graph API Zugriffe benötigen wir verschiedene Informationen.
Die Umgebung(svariablen)
Es geht hier um die kirmizi Enterprises, die neuerdings in der M365 Welt zu Hause ist. Die besagte Dokumentenbibliothek befindet sich auf einer Sitecollection für die HR Abteilung, also /sites/halklailiskiler ... ich weiß ein Zungenbrecher. Die Bibliothek - in der die Dokumente angelegt werden sollen - beinhaltet alle Profildokumente von allen Mitarbeitern und hat den Titel Profillerimiz.
Das wären mal die Informationen, und die sollten uns zum sammeln der weiteren Informationen auch reichen, denn folgendes brauchen wir:
Name | Wert | Beschreibung |
hostname | kirmizient.sharepoint.com | SharePoint Tenant |
siteUrl | https://kirmizient.sharepoint.com/sites/halkailiskiler | Besagte SiteCollection |
siteCollectionId | abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a | die ID der SiteCollection |
siteId | 953edbdd-3289-4efd-bfdd-18cb1e63e0fd | die ID der Root Site |
listId | ab026011-fa6a-411a-ba00-1ff9d6185849 | die ID der Bibliothek |
Der hostname und siteUrl Parameter sollte uns vorliegen, genauso wie der Titel der Liste.
Die Site Id
Nun können wir über den Graph API Explorer unsere Site Selektieren, am einfachsten geht das mit der Search API.
Als Ergebnis sollte sowas wie hier rauskommen.
Wichtig ist hier der "id" key - kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd - für unsere weiteren Operationen über die Graph API werden wir diese benötigen. Ich nehme diesen Wert als "graph siteId" in die Tabelle mit auf.
Name | Wert | Beschreibung |
hostname | kirmizient.sharepoint.com | SharePoint Tenant |
siteUrl | https://kirmizient.sharepoint.com/sites/halklailiskiler | Besagte SiteCollection |
siteCollectionId | abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a | die ID der SiteCollection |
siteId | 953edbdd-3289-4efd-bfdd-18cb1e63e0fd | die ID der Root Site |
listId | ab026011-fa6a-411a-ba00-1ff9d6185849 | die ID der Bibliothek |
graph siteId | kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd | = <hostname>,<siteCollectionId>,<siteId> |
Wie in der Tabelle ersichtlich besteht die graph siteId aus dem hostname, der siteCollectionId und der siteId.
Drive Id
Jetzt können wir die drive id von unserer Bibliothek herausfinden, die unterscheidet sich von der ListId aus SharePoint. An diese Information kommen wir wieder mit der Graph API ran.
Mit den Variablen aus der Tabelle würde der Request wie folgt aussehen.
https://graph.microsoft.com/v1.0/sites/kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd/drives
Von dem Ergebnis stellt der id Parameter die benötigte Information bereit.
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#drives",
"value": [
{
"createdDateTime": "2020-05-12T09:35:06Z",
"description": "",
"id": "b!EWACgmr6GkF7BC_51hhYSXyTTYegpN1Gp-JagqufRnrd2z6VKzL9Tr_dGMseY-D9",
"lastModifiedDateTime": "2020-05-15T09:58:43Z",
"name": "Profillerimiz",
"webUrl": "kirmizient.sharepoint.com/sites/halklailiskiler",
"driveType": "documentLibrary",
"createdBy": {
"user": {
"email": "sevgi.böcügü@kirmizient.onmicrosoft.com",
"id": "cb7ab853-3ce0-985a-8499-bba138343cca",
"displayName": "Sevgi Böcügü"
}
},
....
Die drive id ist für unsere Library wäre b!EWACgmr6GkF7BC_51hhYSXyTTYegpN1Gp-JagqufRnrd2z6VKzL9Tr_dGMseY-D9. Nachdem diese Information auch in die Tabelle aufgenommen wurde, sind wir komplett.
Name | Wert | Beschreibung |
hostname | kirmizient.sharepoint.com | SharePoint Tenant |
siteUrl | https://kirmizient.sharepoint.com/sites/halklailiskiler | Besagte SiteCollection |
siteCollectionId | abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a | die ID der SiteCollection |
siteId | 953edbdd-3289-4efd-bfdd-18cb1e63e0fd | die ID der Root Site |
listId | ab026011-fa6a-411a-ba00-1ff9d6185849 | die ID der Bibliothek |
graph siteId | kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd | = <hostname>,<siteCollectionId>,<siteId> |
graph drive id | b!EWACgmr6GkF7BC_51hhYSXyTTYegpN1Gp-JagqufRnrd2z6VKzL9Tr_dGMseY-D9 | Die drive id |
Das war es auch schon, an die ListId kommt man entweder in dem man die Einstellungen der Liste/Bibliothek aufruft oder per Graph Explorer und folgender Query.
Hier wenden wir den filter Operator (OData) auf den List-Titel an. Mit den Informationen aus der Liste, würde die Query folgendermaßen aussehen.
https://graph.microsoft.com/v1.0/sites/kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd/lists?$filter=displayname eq 'Profillerimiz'
Nun weiter mit der Thematik.
Solution in a nutshell
Theoretisch ist dies mit CSOM, SharePoint REST oder per Hilfe von PnP (Core) ein sehr einfacher Task. Bei der Graph API ist der Weg nicht so trivial. Hier wird das Dokument über die OneDrive API angelegt, das befüllen der Metadaten erfolgt über die SharePoint API.
Sofern alles notwendige - Word Vorlage, ContentType, Bibliothek, die Tabelle mit den Variablen, etc. - vorhanden ist, kann über die Graph API das Vorhaben realisiert werden.
Für unser Vorhaben werden Graph API Berechtigungen für SharePoint - ich habe hier Sites.ReadWrite.All verwendet - und OneDirve - hier habe ich auch eine hohe Berechtigung File.ReadWrite.All verwendet - benötigt. Diese können im Graph Explorer gesetzt werden, sofern eine Anwendung im Hintergrund agieren soll, dann sollten diese in der App Registrierung im Azure AD gesetzt werden.
Die Beispiel Variablen werden aus dieser Tabelle hergenommen.
Name | Wert | Beschreibung |
hostname | kirmizient.sharepoint.com | SharePoint Tenant |
siteUrl | https://kirmizient.sharepoint.com/sites/halklailiskiler | Besagte SiteCollection |
siteCollectionId | abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a | die ID der SiteCollection |
siteId | 953edbdd-3289-4efd-bfdd-18cb1e63e0fd | die ID der Root Site |
listId | ab026011-fa6a-411a-ba00-1ff9d6185849 | die ID der Bibliothek |
graph siteId | kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd | = <hostname>,<siteCollectionId>,<siteId> |
graph drive id | b!EWACgmr6GkF7BC_51hhYSXyTTYegpN1Gp-JagqufRnrd2z6VKzL9Tr_dGMseY-D9 | Die drive id |
Dokument erstellen (OneDrive API)
Dokumente werden über einen PUT Request gegenüber die OneDrive API realisiert.
Da meine Dokumente direkt auf root Ebene innerhalb der Bibliothek abgelegt werden, muss ich beim <folder> Platzhalter root angeben. Sofern eine Hierarchie vorhanden sein sollte, muss der Pfad an dieser Stelle angegeben werden.
Mein Dokument soll akblogtest123 heißen und vom typ docx sein. Mit den Beispielen aus der Tabelle und den beiden zusätzlichen Informationen, würde die Query wie folgt aussehen.
https://graph.microsoft.com/v1.0/sites/kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd/drives/b!EWACgmr6GkF7BC_51hhYSXyTTYegpN1Gp-JagqufRnrd2z6VKzL9Tr_dGMseY-D9/root:/akblogtest123.docx:/content
Nun können wir um Graph Explorer die Query reinkopieren und als Protokoll PUT auswählen. Als Request Header sollte noch Content-Type: text/plain gesetzt werden.
Bei Erfolg wird dann ein langes JSON inkl. Download Link zurückgeliefert. Ausserdem sollte das Dokument in der Dokumentenbibliothek aufgezeigt werden.
Dokument herausfinden (SharePoint API)
Um die Metadaten von unserem Dokument erfolgreich setzen zu können, müssen wir an unser Dokument über die SharePoint API kommen. Leider bietet die Anwort von der OneDrive API bei/nach der Erstellung keinen Wert, den wir mit der SharePoint API weiter verwenden können.
An diese Information kommen wir mit folgendem GET Request. Hier muss darauf geachtet werden im Header Prefer: HonorNonIndexedQueriesWarningMayFailRandomly mitzugeben. Das FileLeafRef Feld ist nicht indiziert => Ressourcenhungrigere Abfrage, diese muss mit Prefer bestätigt werden.
Mit den Vorgaben und Parametern die wir definiert/gefunden haben, würde die Query wei folgt aussehen.
https://graph.microsoft.com/v1.0/sites/kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd/lists/ab026011-fa6a-411a-ba00-1ff9d6185849/items?select=id&filter=fields/FileLeafRef eq 'akblogtest123.docx'
Die Antwort auf diese Abfrage ist relativ klein, diese besteht aus der SharePoint ListItem ID.
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites('kirmizient.sharepoint.com%2C8abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a%2C8953edbdd-3289-4efd-bfdd-18cb1e63e0fd')/lists('ab026011-fa6a-411a-ba00-1ff9d6185849')/items",
"value": [
{
"@odata.etag": "\"75c3eb58-4ccc-4abe-8ef2-573eb5053cce,1\"",
"id": "11"
}
]
}
Unser neues Dokument hat die ID 11, diese ID brauchen wir bei unserem nächsten Task.
Dokument aktualisieren ( SharePoint API)
Nun können wir die Metadaten von unserem Dokument aktualisieren, hierfür werden wir das PATCH Verfahren verwenden. Zusätzlich müssen wir Änderungen im Request Header vorgeben und ein JSON als Body mitgeben. Der Request an sich würde wie folgt aussehen.
Content-Type: application/json
So wir setzen den Titel auf "Test-Blog-AK", ändern den Dateinamen zu "akblogtest123-update.docx", geben den ContentType mit "AkDocumentDoc" und setzen zusätzlich die boolischen Spalten Test1 und Test2.
Der finale Query würde wie folgt aussehen.
https://graph.microsoft.com/v1.0/sites/kirmizi.sharepoint.com,82026011-fa6a-411a-ba00-1ff9d6185849,874d937c-a4a0-46dd-a7e2-5a82ab9f467a/lists/ab026011-fa6a-411a-ba00-1ff9d6185849/items/items/11
Das Ergebnis
Wir bekommen eine Antwort - JSON - in der auch unsere aktuellen Änderungen enthalten sind. In der Bibliothek sieht das Bild folgendermaßen aus.
Fertig in a nutshell (nicht)!
Das war es dann auch. Was man mit dieser Information hier genau macht ist jedem selbst überlassen. Das Schreiben dauert wieder einmal länger als das Herausfinden/Umsetzen. In Zukunft werde ich das tatsächlich in a nutshell halten... oder auch nicht ;-)