Office Graph API: Neues Dokument auf Basis einer Vorlage

calendar_month 15. Mai 2020 25 Wörter

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 Pruef.dotx.

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 Requests 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 Umgebungs(variablen)

Es geht hier um die kirmizi Enterprises, die neuerdings in der M365-Welt zu Hause sind. Die besagte Dokumentenbibliothek befindet sich auf einer SiteCollection für die HR-Abteilung – /sites/halklailiskiler. 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:

NameWertBeschreibung
hostnamekirmizient.sharepoint.comSharePoint Tenant
siteUrlhttps://kirmizient.sharepoint.com/sites/halklailiskilerBesagte SiteCollection
siteCollectionIdabcdd937c-a4a0-46dd-a7e2-5a82ab9f467aDie ID der SiteCollection
siteId953edbdd-3289-4efd-bfdd-18cb1e63e0fdDie ID der Root Site
listIdab026011-fa6a-411a-ba00-1ff9d6185849Die 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:

https://graph.microsoft.com/v1.0/sites?search=halklailiskiler

Als Ergebnis sollte sowas wie hier rauskommen:

{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#sites",
  "value": [
    {
      "createdDateTime": "2019-06-13T13:05:16Z",
      "id": "kirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd",
      "lastModifiedDateTime": "2019-06-13T13:06:34Z",
      "name": "halklailiskiler",
      "webUrl": "https://kirmizient.sharepoint.com/sites/halklailiskiler",
      "displayName": "halklailiskiler",
      "root": {},
      "siteCollection": {
        "hostname": "kirmizient.sharepoint.com"
      }
    }
  ]
}

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.

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:

https://graph.microsoft.com/v1.0/sites/<graph siteId>/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",
      "id": "b!EWACgmr6GkF7BC_51hhYSXyTTYegpN1Gp-JagqufRnrd2z6VKzL9Tr_dGMseY-D9",
      "name": "Profillerimiz",
      "driveType": "documentLibrary"
    }
  ]
}

Die Drive Id für unsere Library wäre b!EWACgmr6GkF7BC_51hhYSXyTTYegpN1Gp-JagqufRnrd2z6VKzL9Tr_dGMseY-D9. Nachdem diese Information auch in die Tabelle aufgenommen wurde, sind wir komplett.

NameWertBeschreibung
hostnamekirmizient.sharepoint.comSharePoint Tenant
siteUrlhttps://kirmizient.sharepoint.com/sites/halklailiskilerBesagte SiteCollection
graph siteIdkirmizient.sharepoint.com,abcdd937c-a4a0-46dd-a7e2-5a82ab9f467a,953edbdd-3289-4efd-bfdd-18cb1e63e0fd= hostname,siteCollectionId,siteId
graph drive idb!EWACgmr6GkF7BC_51hhYSXyTTYegpN1Gp-JagqufRnrd2z6VKzL9Tr_dGMseY-D9Die Drive Id

An die listId kommt man entweder durch Aufrufen der Einstellungen der Liste/Bibliothek oder per Graph Explorer mit folgender Query:

https://graph.microsoft.com/v1.0/sites/<graph siteId>/lists?$filter=displayname eq '<List title>'

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 OneDrive – 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.

Dokument erstellen (OneDrive API)

Dokumente werden über einen PUT-Request gegenüber die OneDrive API realisiert:

https://graph.microsoft.com/v1.0/sites/<graph siteId>/drives/<graph drive id>/<folder>:/<filename>.<file extension>:/content

Da meine Dokumente direkt auf Root-Ebene innerhalb der Bibliothek abgelegt werden, muss ich beim <folder>-Platzhalter root angeben.

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 im 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. Außerdem sollte das Dokument in der Dokumentenbibliothek auftauchen.

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 Antwort 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:

https://graph.microsoft.com/v1.0/sites/<graph siteId>/lists/<listId>/items?select=id&filter=fields/FileLeafRef eq '<filename>.<file extension>'

Mit den Vorgaben und Parametern, die wir definiert/gefunden haben, 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/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(...)/lists(...)/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:

{
  "fields": {
    "Title": "Test-Blog-AK",
    "FileLeafRef": "akblogtest123-update.docx",
    "ContentType": "AkDocumentDoc",
    "Test1": true,
    "Test2": false
  }
}

So setzen wir 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.

Das Ergebnis

Wir bekommen eine Antwort (JSON), in der auch unsere aktuellen Änderungen enthalten sind. In der Bibliothek sieht das Bild dann wie folgt 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 ;-)