SharePoint 2013: Liste innerhalb der Websitesammlung anzeigen

Eine Frage, die immer wieder aufkommt, ist die, wie man die Daten einer Liste oder einer Bibliothek in einer anderen Website anzeigen kann. In einem früheren Beitrag habe ich gezeigt, wie man das in SharePoint 2010 machen kann. Leider funktioniert es in SharePoint 2013 mit der Methode nicht mehr. Die Vorgehensweise ist zwar ähnlich, aber auch die Werkzeuge haben sich verändert, so daß es jetzt einen eigenen Beitrag dazu gibt, wie man eine Listenansicht in SharePoint 2013 für die gesamte Websitesammlung zur Verfügung stellt.

Ansicht vorbereiten

Man kann die Ansicht zwar auch später noch problemlos über die Webparteinstellungen bearbeiten, auch dann, wenn sie in einer anderen Website eingefügt wurde, aber es empfiehlt sich trotzdem sie bereits an der Quelle so aufzubereiten, wie sie später an den meisten Stellen verwendet wird (oder verwendet werden soll).

Man kann dazu entweder eine vorhandene Listen- oder Bibliotheksansicht verändern, auf der Liste oder Bibliothek eine neue Ansicht erzeugen (und später wieder löschen) oder auch auf einer beliebigen Seite eine Ansicht einfügen und verändern. Auch in diesem Fall kann man sie später problemlos wieder von der Seite entfernen (oder die ganze Seite löschen).

Ansicht exportieren

Die oben erstellte Ansicht muß jetzt exportiert werden. Warum Microsoft den dafür vorgesehenen Menüpunkt aus dem Webpartmenü entfernt hat, kann ich nicht sagen, aber mit SharePoint Designer geht es zum Glück immer noch. Man öffnet also die Website in SharePoint Designer und navigiert zur Seite mit der gewünschten Ansicht. Wenn sie sich als Ansicht direkt auf der Liste oder Bibliothek befindet, kann man dazu über Listen und Bibliotheken und dann über die Ansichten einer Liste oder Bibliothek gehen. Wenn sie sich auf einer beliebigen Seite befindet, muß man diese öffnen. Je nach Speicherort der Seite kann man z.B. über Site Pages oder auch über Alle Dateien dorthin gelangen.

In SharePoint Designer 2013 gibt es keine Designansicht mehr, so daß man nach dem Öffnen der Seite direkt in die Quellcodeansicht gelangt. Dort sucht man sich die gewünschte Ansicht (im Idealfall befindet sich nichts anderes auf der Seite). Man findet Listenansichten z.B. indem man nach WebPartPages:XsltListViewWebPart sucht. Jetzt setzt man einfach die Einfügemarke irgendwo innerhalb dieses Elements und im Ribbon oben erscheint unter der Überschrift WEBPARTTOOLS der Reiter FORMAT. Darin gibt es rechts zwei Optionen zum Webpart speichern:

Direktes Speichern in den Websitekatalog hat keinen Sinn, weil es damit in anderen Websites nicht funktionieren wird. Man speichert das Webpart also in eine Datei, z.B. als ListeInWebsite1.webpart und merkt sich den Ort, an dem man die Datei abgelegt hat.

Gespeicherte Ansicht editieren

In der Datei muß jetzt eine Änderung gemacht werden. Es handelt sich dabei um eine XML-Datei, die man mit einem geeigneten Editor bearbeiten kann. Zur Not geht es auch mit Windows-Notepad. Man sucht in der Datei nach WebId und stellt fest, daß diese als leere Guid hinterlegt wurde: 00000000-0000-0000-0000-000000000000

Dort muß jetzt die richtige ID der Website eingetragen werden, in der sich die Liste oder Bibliothek tatsächlich befindet. Man findet diese ID z.B. über den REST-Service, indem man im Browser an die Adresse der Website folgendes anhängt: /_api/web/id

Als Adresse steht dann sowas im Browser http://sharepoint/sites/web2/_api/web/id und die Ausgabe sieht so aus:

Der markierte Teil ist die gesuchte Guid der Website. Man überschreibt damit die leere Guid in der Datei und speichert sie. Falls der Browser die Daten nicht wie oben gezeigt darstellt: hier steht, wie man das ändert.

Ansicht importieren

Die in der Datei gespeicherte Ansicht wird jetzt in den Webpartkatalog importiert. Dazu geht man zuerst in die Rootwebsite der Websitesammlung. Dort dann über das Menü hinter dem kleinen Zahnrad rechts oben in die Websiteeinstellungen. Unter Web-Designer-Kataloge findet man den Link Webparts. Dort lädt man die Datei hoch und kann dann noch verschiedene Metadaten angeben wie z.B. eine Beschreibung. Man kann auch die Gruppe angeben, unter das Webpart später beim Einfügen in eine Seite gefunden wird. Wenn man hier nichts angibt, erscheint es unter Verschiedenes.

Das war’s auch schon. Die Ansicht ist jetzt als wiederverwendbares Webpart im Webpartkatalog gespeichert und kann innerhalb der gesamten Websitesammlung verwendet werden.

SharePoint 2013 Apps: Schnellstartleiste im App Web

Bei der Suche im Web nach einer Möglichkeit die Schnellstartleiste (Quicklaunch) auch im App Web verwenden zu können, bin ich auf viele nicht funktionierende "Lösungen" gestossen. Zumindest bei mir hatte keines der meist identischen Beispiele die erwünschte Wirkung erzielt. Offenbar haben hier viele Blogger ohne zu testen einfach nur voneinander abgeschrieben. Nach ein wenig Probieren habe ich dann aber selbst eine funktionierende Lösung gefunden.

Für das App Web wird standardmäßig die Gestaltungsvorlage (Master Page) app.master verwendet und bei der ist die Schnellstartleiste ausgeblendet. Wenn man diese verwenden möchte, muß man also eine eigene Gestaltungsvorlage verwenden. Wie das geht, habe ich hier beschrieben.

Man sucht jetzt einfach in der eigenen Gestaltungsvorlage nach PlaceHolderLeftNavBar. Standardmäßig ist dieser Platzhalter leer. Den leeren Platzhalter überschreibt man mit folgendem:

<asp:ContentPlaceHolder id="PlaceHolderLeftNavBar" runat="server">
 
<div>
    <asp:SiteMapDataSource SiteMapProvider="SPNavigationProvider"
                           ShowStartingNode="False"
                           id="QuickLaunchSiteMap"
                           StartingNodeUrl="sid:1025"
                           runat="server"/>
    <SharePoint:AspMenu id="V4QuickLaunchMenu"
                        runat="server"
                        EnableViewState="false"
                        DataSourceId="QuickLaunchSiteMap"
                        UseSimpleRendering="true"
                        Orientation="Vertical"
                        StaticDisplayLevels="3"
                        AdjustForShowStartingNode="true"
                        MaximumDynamicDisplayLevels="2"
                        SkipLinkText=""
                        CssClass="s4-ql"/>
  </div>
</asp:ContentPlaceHolder>

Das reicht bereits, um die Schnellstartleiste anzuzeigen. Allerdings gibt es jetzt noch ein Problem: der Inhaltsbereich rutscht jetzt auf manchen Seiten unter die Schnellstartleiste, anstatt rechts daneben angezeigt zu werden. Um das zu beheben, muß man dem <div>, das in der Gestaltungsvorlage direkt darunter notiert ist, einen Abstand per Inline-Style zuweisen:

<div id="contentBox" aria-live="polite" aria-relevant="all" style="margin-left: 250px;">

SharePoint 2013 Apps: Eigene Gestaltungsvorlage verwenden

Wenn eine App für SharePoint ein in SharePoint gehostetes App Web benutzt, kann man für dieses eine eigene Gestaltungsvorlage (Master Page) benutzen. Ohne weiteres Zutun verwendet SharePoint für App Webs die Standard-Gestaltungsvorlage app.master.

Dieser Beitrag zeigt die Vorgehensweise anhand einer SharePoint-hosted App, aber es funktioniert genauso bei cloud-basierten Apps, die ein App Web benutzen, z.B. um dort eigene Listen für die Datenhaltung anzulegen.

Gestaltungsvorlage hinzufügen

Wir müssen zunächst die zu verwendende Gestaltungsvorlage in das Visual Studio Projekt aufnehmen. Dazu legen wir im Projekt, das die App repräsentiert, ein neues Element vom Typ Module an (z.B. durch Rechtsklick auf das Projekt – Add New Item Module). Wichtig ist, daß das wirklich im App-Projekt geschieht (das ist das, welches die AppManifest.xml enthält) und nicht in dem Projekt, das die Remote-Komponenten von cloud-basierten Apps enthält. Bei einer SharePoint-hosted App gibt es in Visual Studio ohnehin nur dieses eine Projekt.

Beim Hinzufügen eines Modules legt Visual Studio automatisch eine Sample.txt Datei an. Sie kann einfach gelöscht werden.

Als Ausgangsbasis für die eigene Gestaltungsvorlage empfiehlt es sich die standardmäßig verwendete app.master zu benutzen. Man findet sie auf einem SharePoint 2013 Server unter 15\TEMPLATE\GLOBAL. Zum Hinzufügen der Datei in Visual Studio gibt es jetzt zwei Möglichkeiten. Man fügt entweder eine neue Textdatei ein, nennt sie z.B. myapp.master und fügt dann per Kopieren und Einfügen den Inhalt hinzu. Oder man kopiert gleich die ganze Datei in den Ordner des Modules, benennt sie um und nimmt sie über Add Existing Item ins Projekt auf. Visual Studio nimmt die Datei jetzt automatisch in die Elements.xml des Modules auf:

<Module Name="CustomMaster">
  <File Path="CustomMaster\myapp.master" Url="CustomMaster/myapp.master" ReplaceContent="TRUE" />
</Module>

Hier müssen jetzt diverse Änderungen gemacht werden, damit die Datei in den Katalog für Gestaltungsvorlagen (Master Page Catalog) aufgenommen wird. Dazu fügt man beim Module-Element ein Attribut List="116" und ein Attribut Url="_catalogs/masterpage" hinzu. Beim File-Element wird der Ordnername aus dem Url-Attribut entfernt, das ReplaceContent-Attribut wird komplett entfernt und ein Attribut Type="GhostableInLibrary" wird hinzugefügt. Das Module sieht dann so aus:

<Module Name="CustomMaster" List="116" Url="_catalogs/masterpage">
 
<File Path="CustomMaster\myapp.master" Url="myapp.master" Type="GhostableInLibrary" />
</Module>

Damit haben wir jetzt schonmal erreicht, daß die Datei im Gestaltungsvorlagenkatalog bereitgestellt wird.

Gestaltungsvorlage verwenden

In einzelnen Seiten

Wenn es hier nur um eigene Seiten, wie z.B. die von Visual Studio automatisch angelegte Default.aspx geht, kann man die zu verwendende Gestaltungsvorlage einfach in der jeweiligen Datei angeben. Standardmäßig steht dort (in der @Page-Direktive)

MasterPageFile="~masterurl/default.master"

Das ersetzt man durch

MasterPageFile="~site/_catalogs/masterpage/myapp.master"

Und schon verwendet die Seite unsere eigene Gestaltungsvorlage.

In allen Seiten

Schwieriger wird das Ganze wenn alle Seiten automatisch die neue Gestaltungsvorlage übernehmen sollen. Wenn man z.B. im App Web eigene Listen hat, sollen die zur Liste gehörenden Ansichts- und Formularseiten ebenfalls die neue Gestaltungsvorlage verwenden. Um das zu erreichen, erstellt man auch noch eine eigene Websitevorlage (Site Template) für das App Web.

Ähnlich wie bei der Gestaltungsvorlage selbst, erstellt man im Projekt ein neues Module, fügt die standardmäßig verwendete ONET.XML ein und modifiziert sie. Die Standardvorlage findet man ebenfalls auf jedem SharePoint 2013 Server im Ordner 15\TEMPLATE\SiteTemplates\App\Xml. Die Datei ONET.XML fügt man im Module ein. Wichtig: sie darf nicht umbenannt werden!

In der Datei müssen jetzt noch Änderungen gemacht werden. Beim Element Project/Configurations/Configuration muß das Name-Attribut geändert und der Pfad im MasterUrl-Attribut angepaßt werden:

MasterUrl="_catalogs/masterpage/myapp.master"

Hier noch ein Screenshot zur Verdeutlichung der Anpassungen:

Die neue Vorlage muß jetzt noch also solche bekannt gemacht werden. Dies geschieht durch ein WebTemplate-Element, das in die Elements-xml des Modules eingefügt wird, das die ONET.XML enthält. Dazu fügt man innerhalb von Elements, aber außerhalb von Module folgendes ein:

<WebTemplate BaseTemplateID="1" BaseConfigurationID="0" BaseTemplateName="APP" Name="CustomTemplate" ProductVersion="15" DisplayCategory="Custom" Description="Web template for Custom Apps."></WebTemplate>

Es sollte also so aussehen:

Wichtig ist dabei, daß das Name-Attribut beim WebTemplate-Element exakt dem Name-Attribut des Module-Elements entspricht!

Als letzten Schritt müssen wir jetzt noch der App mitteilen, daß wir das App Web aus unserer Vorlage erstellen wollen. Das geht über die AppManifest.xml, die durch einen Rechtsklick in der Codeansicht geöffnet wird (nicht mit dem Manifest-Designer). In die Datei fügt man jetzt innerhalb von Properties folgendes ein:

<WebTemplate Id="{6393fe60-d014-4ed1-9153-518538c562a3}#CustomTemplate"></WebTemplate>

Es sollte so aussehen:

Der Inhalt des Id-Attributs setzt sich so zusammen:

  • Öffnende geschweifte Klammer

  • Feature-ID (s.Erklärung unten)

  • Schließende geschweifte Klammer

  • Raute/Hash-Zeichen #

  • Der Name des WebTemplates bzw. des Modules (s.o.)

Als Feature-ID muß die Id des Features angegeben werden, das die Vorlage enthält. Man bekommt sie, indem man das Feature in Visual Studio per Doppelklick öffnet und dann oberhalb des Designers auf Manifest umschaltet:

Test

Um das Ganze zu testen, machen wir jetzt eine Änderung in unserer Gestaltungsvorlage und schauen dann, ob diese Änderung in der bereitgestellten App auch sichtbar ist. Man kann dazu eine beliebige Änderung vornehmen. Ich habe einfach oberhalb des Inhaltsbereichs einen festen Text eingefügt:

In Visual Studio F5 gedrückt und siehe da, es funktioniert:

Vorsicht mit KB2817630

Und schon wieder die Warnung vor einem aktuellen Microsoft-Update. Nachdem erst vor Kurzem ein Update veröffentlich wurde, das Datenansichten in SharePoint unbenutzbar machen konnte, gibt es jetzt schon wieder ein Problem mit einem aktuellen Update vom 10.09.2013. KB2817630 sorgt dafür, daß die Ordneransicht in Outlook 2013 leer bleibt.

Einzige Abhilfe schafft nach derzeitigem Kenntnisstand einzig die Deinstallation dieses Updates. Achtung: bei mir erschien dieses Update zweimal unter den installierten Updates und ich mußte auch beide einzeln deinstallieren bis das Problem behoben war. Selbstverständlich mit einem Neustart zwischendurch…

SharePoint 2013 Apps: Lokalisierung im Host Web

In diesem Beitrag geht es jetzt um die Lokalisierung von App-Komponenten, die im Hostweb angezeigt werden. Das ist zum einen der Name der App selbst, aber es sind z.B. auch die Texte, die für eigene Ribbon-Elemente benötigt werden. Oder Name und Beschreibung von Client Webparts (App Parts) und deren benutzerdefinierte Eigenschaften.

Im Beitrag Lokalisierung bei Remote Apps habe ich bereits gezeigt, wie man die Elemente cloud-basierter Apps lokalisieren kann.

Wie immer gehe ich auch hier wieder davon aus, daß die App mit Visual Studio 2012 erstellt wird. Das Hosting-Modell spielt in diesem Fall keine Rolle, weil es die genannten Elemente bei jeder Art App geben kann.

Ressourcendateien hinzufügen

Die zur Lokalisierung der Hostweb-Komponenten notwendigen Ressourcendateien werden über das App-Manifest zum Projekt hinzugefügt. Man öffnet dazu den Manifest-Designer durch einen Doppelklick auf die Datei AppManifest.xml. Im Reiter Supported Locales gibt man jetzt die zu unterstützenden Kulturen an und Visual Studio fügt dann automatisch eine passende resx-Datei ins Projekt ein. Bei der ersten Angabe, z.B. German (Germany), wird nicht nur die Resources.de-DE.resx, sondern auch die neutrale Resources.resx ins Projekt aufgenommen:

Die Dateien werden in einem Ordner Resources abgelegt. Im Solution Explorer sieht das dann so aus:

Achtung: wenn man bei einer App unterstützte Kulturen angibt, kann diese App nicht mehr auf einer Website verwendet werden, die für eine nicht unterstützte Kultur angelegt wurde! Insofern ist die Verwendung der neutralen Ressourcendatei hier ziemlich sinnlos.

Ressourcen verwenden

Die Texte aus den Ressourcendateien können jetzt in der von Farmlösungen gewohnten Weise verwendet werden. Um z.B. den Titel eines App Parts zu lokalisieren, gibt man dort die zu verwendete Ressource an:

Title="$Resources:AppPartTitle;"

Das funktioniert auch beim Titel der App selbst, bei Texten für eigene Ribbon-Elemente und bei eigenen Eigenschaften, die man einem App Part geben möchte.

OPC Beziehungen

Wer jetzt meint, das wäre schon alles gewesen, muß leider enttäuscht werden. Visual Studio fügt leider nicht alle notwendigen Beziehungen in das fertige App-Paket ein. Warum das nicht gemacht wird und ob diese Unterstützung irgendwann nachgeliefert wird, steht derzeit in den Sternen.

Man kann diese Beziehungen jetzt durch manuelles Gefrickel im App-Paket selbst einfügen, aber dann muß die App auch manuell bereitgestellt werden. D.h. man kann nicht mehr einfach in Visual Studio F5 drücken und es wird sich um alles andere gekümmert. Sehr unschön.

Es geht aber sehr viel einfacher mit einem Tool, das Ricardo Loo bereitgestellt hat und das er hier beschreibt: AddRelsToAppPackage.exe

Man kopiert dieses kleine Programm einfach in das Projektverzeichnis, also in das Verzeichnis, in dem Visual Studio die Dateien und Ordner des App-Projekts ablegt. In Visual Studio macht man dann einen Rechtsklick auf das App-Projekt und klickt dann auf Properties. Im Reiter SharePoint fügt man folgendes bei Pre-deployment Command Line ein:

"$(ProjectDir)\AddRelsToAppPackage.exe" "$(TargetDir) "

Achtung: man verwendet wirklich genau diesen Text inklusive der Anführungszeichen und dem Leerzeichen nach $(TargetDir)!

Das gesamte Projekt AddRelsToAppPackage kann hier heruntergeladen werden. Für alle, die sich nur für die fertige exe-Datei interessieren, habe ich hier eine Kopie hinterlegt.

SharePoint 2013 Apps: Lokalisierung bei Remote Apps

In diesem Beitrag soll gezeigt werden, wie cloud-basierte Apps (auto-hosted oder provider-hosted) lokalisiert werden, d.h. wie man die App für verschiedene Sprachen fit machen kann. Bei SharePoint-hosted Apps kann man dazu auf die von Farmlösungen bekannten Möglichkeiten zurückgreifen. Bei cloud-basierten Apps liegt aber die Verantwortung über das AppWeb beim Entwickler und man muß sich deshalb auch um die Lokalisierung selbst kümmern. Achtung: es geht hier wirklich nur um die Lokalisierung der Remote-Komponenten und nicht darum, wie man z.B. per App bereitgestellte Listen lokalisiert. Das werde ich eventuell in einem späteren Beitrag beschreiben.

Update 27.08.2013: hier habe ich in einem Beitrag die Lokalisierung von Hostweb-Komponenten beschrieben.

Ich gehe hier wieder davon aus, daß die App mit Visual Studio 2012 erstellt wird. Damit ist das AppWeb im Grunde eine ganz normale ASP.NET Anwendung und man kann zur Lokalisierung die dafür vorgesehenen Möglichkeiten verwenden. Die grundsätzliche Vorgehensweise ist hier beschrieben: ASP.NET Globalization and Localization. Ich werde hier nur das Wichtigste kurz zusammenfassen.

Ressourcendateien hinzufügen

Ressourcendateien *.resx werden zur Bereitstellung sprachabhängiger Elemente verwendet. Man legt dabei für jede Kultur, die man unterstützen möchte, eine Datei an. Es kann also eine app.de-DE.resx für Deutschland und eine app.de-CH.resx für die Schweiz geben. Man sollte außerdem immer eine neutrale app.resx bereitstellen, die immer dann verwendet wird, wenn es keine spezifische Datei für eine Kultur gibt.

In ASP.NET kann zwischen lokalen und globalen Ressourcen unterschieden werden. Lokale Ressourcen gelten immer nur für eine Seite *.aspx oder ein benutzerdefiniertes Control *.ascx. Diesen weg möchte ich hier nicht weiter verfolgen und empfehle stattdessen die Benutzung von globalen Ressourcen. Damit hat man alle sprachabhängigen Komponenten in einer Datei und man kann gleiche Elemente an unterschiedlichen Stellen wiederverwenden.

Globale Ressourcen werden in einem besonderen Ordner App_GlobalResources abgelegt. In Visual Studio erzeugt man ihn z.B. durch einen Rechtsklick auf das Projekt im Solution Explorerund dann Add -> Add ASP.NET Folder -> App_GloablResources. Dem Ordner kann man dann ebenfalls z.B. durch Rechtsklick und Add -> Resources File Ressourcendateien hinzufügen. Im Solution Explorer kann das dann so aussehen:

Kultur auswählen

Man muß sich entscheiden, wie die App auf verschiedene Kulturen reagieren soll, d.h. in welcher Sprache die App einem bestimmten Benutzer präsentiert wird.

Es gibt die Möglichkeit, daß die App einfach auf die im Browser eingestellte bevorzugte Sprache reagiert. Beim Internet Explorer ist diese Einstellung z.B. über die Internetoptionen und dann den Button Sprachen im Reiter Allgemein erreichbar. Damit diese Einstellung von der ASP.NET Webanwendung verwendet wird, genügt ein einfacher Eintrag in der web.config der Anwendung:

<configuration>
 
<system.web>
    <globalization culture="auto:de-DE" uiCulture="auto:de-DE" />

Die dort angegebene Kultur (hier de-DE) wird als Standard verwendet, falls ein Browser keine bevorzugte Kultur liefert.

Es gibt aber auch die Möglichkeit, sich nach der in SharePoint verwendeten Kultur zu richten. Diese wird beim ersten Aufruf der App aus SharePoint über die {StandardTokens} als QueryString-Parameter SPLanguage übermittelt. Wenn sie verwendet werden soll, kann man den Parameter auslesen und damit die zu verwendende Kultur selbst setzen. Das sollte dann in einer Überschreibung der InitializeCulture-Methode der Startseite geschehen. Der Code dazu kann z.B. so aussehen:

protected override void InitializeCulture() {
 
string selectedLanguage = Request.QueryString["SPLanguage"];
  if (selectedLanguage != null) {
    Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(selectedLanguage);
    Thread.CurrentThread.CurrentUICulture = new CultureInfo(selectedLanguage);
  }
  base.InitializeCulture();
}

Ressourcen verwenden

Ressourcen können auf zweierlei Art verwendet werden: deklarativ oder per Code. Beim deklarativen Ansatz wird z.B. einem Control direkt beim Erstellen in der aspx ein Wert aus den Ressourcen zugewiesen. Für den Text eines Buttons kann das z.B. so aussehen:

<asp:Button ID="Button1" runat="server" Text="<%$ Resources:AppRes, Button1_Text %>" OnClick="Button1_Click" />

Die Referenz auf ein Ressourcenelement beginnt dabei immer mit dem Schlüsselwort Resources gefolgt von einem Doppelpunkt. Danach kommt der Name der Ressourcendatei ohne Kultur und ohne die resx-Erweiterung. Durch ein Komma getrennt folgt dann der Schlüssel, unter dem das gewünschte Element in den Ressourcen abgelegt ist.

Wenn Ressourcen im Code verwendet werden sollen, z.B. um dynamische Meldungstexte auszugeben, verwendet man dazu die GetGlobalResourceObject-Methode. Zum Festlegen des Seitentitels kann das z.B. so aussehen:

this.Title = GetGlobalResourceObject("AppRes", "PageTitle").ToString();

Ähnlich wie oben steht hier der erste Parameter für den Namen der Ressourcendatei ohne Kultur und ohne die resx-Erweiterung. Der zweite Parameter bezeichnet den Schlüssel, unter dem das gewünschte Element in den Ressourcen abgelegt ist.

SharePoint 2013 Apps: SecurityToken bei Remote Apps

Wenn man in Visual Studio 2012 eine neue cloud-basierte App (auto-hosted oder provider-hosted) anlegt und einfach nur bereitstellt, funktioniert beim ersten Aufruf der App im Browser alles tadellos. Jeder weitere Aufruf z.B. durch eigene Links innerhalb der App oder auch durch einen simplen Button-Klick verursachen jedoch einen Fehler: The parameter ‚token‘ cannot be a null or empty string. Im Browser sieht man die berüchtigte ASP.NET Fehlerseite:

Wie man sieht, entsteht der Fehler beim Versuch ein SecurityToken zu finden, das für den Zugriff von der App aus auf Daten in SharePoint benötigt wird. Visual Studio erzeugt beim Anlegen eines neuen App-Projekts automatisch eine Datei TokenHelper.cs, die eigentlich alles notwendige beinhaltet. Beim Analysieren dieser Datei stellt man dann aber fest, daß dort versucht wird auf Request-Parameter zuzugreifen, die zwar beim ersten Aufruf aus SharePoint gesetzt werden, aber bei jedem weiteren Request dann fehlen.

Man muß sich also selbst darum kümmern diese Werte zwischenzuspeichern. Das kann man mit den bewährten Mitteln von ASP.NET an verschiedenen Stellen machen. Innerhalb einer Seite z.B. im ViewState oder in serverseitigen hidden-Controls. Aber auch seitenübergreifend in der Session. Natürlich sind auch ganz andere Orte denkbar, wie z.B. das Speichern in einer Datenbank.

Eigentlich reicht es das sogenannte Context Token (ein String) zu speichern. Wenn man schon dabei ist, lohnt es sich aber auch gleich die HostUrl mit zu speichern, damit sie in allen weiteren Seitenaufrufen auch wirklich zur Verfügung steht. Dadurch funktioniert auch alles, wenn man vergißt diese Url in einem eigenen Link weiterzugeben.

Ich habe mich dazu entschieden die Werte in der Session abzulegen, weil ich sie dann innerhalb des gesamten AppWebs verwenden kann. Dazu habe ich eine eigene Methode GetContext erstellt, die den passenden ClientContext zurückgibt. Die Methode habe ich als statische Methode einfach zusätzlich in die TokenHelper-Klasse eingebaut. Immer wen man irgendwo einen ClientContext benötigt, kann man dann auf diese Methode zugreifen (anstelle der sonst verwendeten TokenHelper.GetClientContextWithContextToken. Der Methode muß nur der aktuelle HttpContext übergeben werden, der in jeder Seite über HttpContext.Current erreichbar ist. So sieht die Methode aus:

public static ClientContext GetContext(HttpContext httpContext) {
 
string token = httpContext.Session["myToken"] as string;
  string hostUrl = httpContext.Session["myHostUrl"] as string;
  if (token == null || hostUrl == null) {
    token = TokenHelper.GetContextTokenFromRequest(httpContext.Request);
    hostUrl = httpContext.Request["SPHostUrl"];
    httpContext.Session["myToken"] = token;
    httpContext.Session["myHostUrl"] = hostUrl;
  }
  return TokenHelper.GetClientContextWithContextToken(hostUrl, token, httpContext.Request.Url.Authority);
}

SharePoint 2013 Apps: das Chrome Control

In diesem Beitrag soll gezeigt werden, wie man in einer cloud-basierten App (auto-hosted oder provider-hosted) das Chrome Control verwendet. Dieses sorgt dafür, daß die eigene App die von SharePoint 2013 gewohnte Titelleiste verwendet. Außerdem wird dadurch das passende CSS eingebunden, so daß sich die App damit dem Aussehen des Hostwebs anpassen kann. Bei SharePoint-hosted Apps ist das nicht notwendig, weil die Seiten dort als normale Webpartseiten realisiert werden können.

Das wichtigste zu diesem Thema habe ich MSDN entnommen: How to: Use the client chrome control in apps for SharePoint. Ich habe aber das notwendige JavaScript etwas modifiziert und in eine eigene Datei ausgelagert, damit es leichter wiederverwendet werden kann.

Ich gehe hier davon aus, daß die App mit Visual Studio 2012 erstellt wird. Wer die App mit anderen Mitteln baut, kann aber immer noch das notwendige JavaScript gebrauchen.

QueryString erweitern

Wir beginnen zunächst damit die Abfragezeichenfolge (QueryString), die an die App übergeben wird, zu erweitern. Das läßt sich sehr einfach über den AppManifest-Designer erledigen. Visual Studio fügt hier bereits den Platzhalter {StandardTokens} ein, der von SharePoint zur Laufzeit aufgelöst wird und bereits einige wichtige Parameter enthält. Es gibt aber einige mehr und wir wollen hier noch den Titel und das Logo des Hostwebs, damit wir diese ebenfalls im Chrome verwenden können. Das läßt sich erreichen, indem man das hier anhängt: &SPHostTitle={HostTitle}&SPHostLogoUrl={HostLogoUrl}

Weitere Informationen zu den verfügbaren Tokens finden sich hier bei MSDN: URL strings and tokens in apps for SharePoint

Außerdem nutzen wir gleich die Gelegenheit und geben einen weiteren Parameter DisplayType mit, über den wir in der App unterscheiden können, ob eine Seite im FullScreen-Modus oder als AppPart (in einem iframe) aufgerufen wird. Falls eine Seite als AppPart verwendet wird, wollen wir das Chrome Control nicht laden, sondern binden nur die CSS-Datei ein, damit das Styling korrekt funktioniert. Für den DisplayType-Parameter gibt es kein vordefiniertes Token, so daß wir den Wert fest verdrahtet eingeben müssen. Als mögliche Optionen habe ich FullScreen bzw. iframe gewählt.

Insgesamt sieht es dann so aus:

AppWeb erweitern

Unserer App fügen wir jetzt eine zusätzliche JavaScript-Datei hinzu. Ich habe sie appScripts.js genannt und sie kann hier heruntergeladen werden. Wir greifen außerdem auf jQuery zurück, das von Visual Studio aber bereits automatisch zum Projekt hinzugefügt wurde. Der Solution Explorer für das AppWeb sieht dann ungefähr so aus:

JavaScript

Alles was zum Einbinden des Chrome Controls notwendig ist, werden wir hier per JavaScript in der appScripts.js machen. Die fertige Datei kann hier heruntergeladen werden. Ein deklarativer Ansatz ist ebenfalls möglich, aber meiner Meinung nach nicht wirklich wiederverwendbar. Wenn es jemanden interessiert, kann er es im erwähnten Beitrag auf MSDN nachlesen.

Unseren gesamten JavaScript-Code fügen wir in ein eigenes Objekt myApp ein, einfach weil das in JavaScript Best Practices entspricht und den globalen Namensraum nicht "vollmüllt". Wir beginnen also die appScripts.js zu füllen:

"use strict";
var myApp = {
  // code goes here
};

Wir brauchen zum Auslesen der QueryString-Parameter eine möglichst einfache Funktion. Ich habe sie an dieser Stelle schon öfter verwendet und möchte sie deshalb hier nur der Vollständigkeit halber erwähnen. Ich verwende immer das hier:

getQueryStringParameter: function (paramName) {
 
var params = document.URL.split("?")[1].split("&"),
      i,
      singleParam;
  for (i = 0; i < params.length; i++) {
    singleParam = params[i].split("=");
    if (singleParam[0] == paramName) {
      return singleParam[1];
    }
  }
  return "";
}

Wir definieren uns noch eine Variable chromeContainerId, die die ID eines div-Elementes enthält. In dieses div wird dann später das Chrome Control eingesetzt. Weiter unten wird gezeigt, wie man es in die Seite einbaut. Außerdem definieren wir noch ein Array chromeLinks. Diesem können wir einfach weitere Links hinzufügen, die vom Chrome Control gerendert werden. Auch dazu weiter unten mehr. Die Definition als separat zugängliche Variablen habe ich gemacht, damit später immer noch jede Seite die Möglichkeit hat diese Grundeinstellungen zu verändern.

chromeContainerId: "divSPChrome",
chromeLinks: []

Fullscreen oder iframe?

Als nächstes bauen wir eine setupPage-Funktion ein, die von einer Seite aus aufgerufen werden kann und die die ganze Arbeit erledigt. Innerhalb der Funktion holen wir uns zunächst den DisplayType und die SPHostUrl. Außerdem setzen wir uns eine Variable layoutsRoot auf den Layouts-Ordner des Hostwebs, damit wir einfach Elemente von dort referenzieren können:

// Get DisplayType from url parameter
var displayType = decodeURIComponent(myApp.getQueryStringParameter("DisplayType")),
// Get URI decoded SharePoint host site url from the SPHostUrl parameter
spHostUrl = decodeURIComponent(myApp.getQueryStringParameter("SPHostUrl")),
// Build absolute path to the layouts root with the spHostUrl
layoutsRoot = spHostUrl + "/_layouts/15/";

Falls die Seite als AppPart, also in einem iframe, angezeigt werden soll, binden wir kein Chrome Control ein, sondern laden einfach die korrekte CSS-Datei aus dem Hostweb. Das geht über einen dynamisch erzeugten CSS-Link, den wir dem head der Seite hinzufügen:

// Create a Link element for the defaultcss.ashx resource
linkElement = document.createElement("link");
linkElement.setAttribute("rel", "stylesheet");
linkElement.setAttribute("href", layoutsRoot + "defaultcss.ashx");

// Add the linkElement as a child to the head section of the html
headElement = document.getElementsByTagName("head");
headElement[0].appendChild(linkElement);

Wenn die Seite im Fullscreen-Modus angezeigt werden soll, müssen wir zunächst eine SharePoint-Scriptdatei nachladen. Die Datei enthält u.a. das eigentliche Chrome Control. Wir laden die Datei mit der jQuery-Funktion $.getScript und übergeben dabei eine weitere Funktion renderSPChrome. Diese Funktion wird von jQuery aufgerufen, sobald die gewünschte Scriptdatei vollständig geladen ist.

$.getScript(layoutsRoot + "SP.UI.Controls.js", myApp.renderSPChrome);

Jetzt machen wir also mit der renderSPChrome-Funktion weiter. Analog zu oben holen wir uns die SPHostLogoUrl aus dem Querystring:

var hostlogourl = decodeURIComponent(myApp.getQueryStringParameter("SPHostLogoUrl"));

Chrome Control

Als nächstes bauen wir uns ein options-Objekt zusammen. Mit Hilfe dieses Objekts kann man diverse Einstellungen an das Chrome Control übergeben. Die Möglichkeiten sind:

appIconUrl: ein String mit der URL zu einem Bild, das als Anwendungsicon links oben erscheint. Wir verwenden hier einfach das Icon aus dem Hostweb, aber natürlich kann es auch ein eigenes, app-spezifisches Bild sein.

appTitle: ein String mit dem Namen der Anwendung. Er wird rechts neben dem Anwendungsicon und nochmal darüber in der Titelleiste angezeigt. Wir verwenden hier den Titel der Seite, der über document.title erreichbar ist.

appHelpPageUrl: ein String mit der URL zu einer Hilfeseite. Wenn der Parameter angegeben ist, wird ganz rechts oben ein Hilfesymbol mit einem Link zur angegebenen Url eingeblendet. Wie bei allen Urls, die innerhalb der App zur Navigation verwendet werden, sollte man immer die Querystring-Parameter weitergeben, damit sie auf den Folgeseiten ebenfalls zur Verfügung stehen. Das erreicht man mit diesem Code:

"Help.html?" + document.URL.split("?")[1]

onCssLoaded: ein String, der den Namen einer Funktion enthält. Wenn der Parameter angegeben ist, wird diese Funktion aufgerufen, sobald das Chrome Control fertig und alle referenzierten CSS-Dateien geladen sind. Die Funktion kann dann Abschlussarbeiten erledigen, die erst dann ausgeführt werden können oder sollen. Achtung: man muß wirklich einen String angeben. Eine Referenz auf eine Funktion tut es nicht. Der übergebene Parameter wird offenbar per eval() ausgeführt, weshalb man auch die Klammern hinter dem Funktionsnamen () angeben muß. Bei uns sieht das so aus:

"onCssLoaded": "myApp.chromeLoaded()"

settingsLinks: ein Array mit passenden Link-Objekten. Wenn der Parameter angegeben wird, werden diese Links rechts oben als Menü gerendert. Das geschieht an der Stelle, wo sich sonst in SharePoint das Menü befindet, über das z.B. die Websiteeinstellungen zugänglich sind. Die einzelnen Link-Objekte müssen linkUrl und displayName jeweils als Strings enthalten. Die Namen sollten selbsterklärend sein: linkUrl enthält die aufzurufende Url und displayName den angezeigten Text. Das kann z.B. so aussehen:

[
 
{
    "linkUrl": "Account.html?" + document.URL.split("?")[1],
    "displayName": "Account settings"
  },
  {
    "linkUrl": "Contact.html?" + document.URL.split("?")[1],
    "displayName": "Contact us"
  }
]

Über weitere Möglichkeiten der Optionen habe ich noch nichts herausgefunden. Schade, daß Microsoft auch hier wieder auf eine Dokumentation komplett verzichtet 😦

Update 11.09.2013: beim Durchstöbern von SP.UI.Controls.debug.js bin ich auf folgende Möglichkeiten gestossen:

  • siteTitle
  • siteUrl
  • clientTag
  • appWebUrl
  • onCssLoaded
  • assetId
  • appStartPage
  • rightToLeft
  • appTitle
  • appIconUrl
  • appTitleIconUrl
  • appHelpPageUrl
  • appHelpPageOnClick
  • settingsLinks
  • language
  • bottomHeaderVisible
  • topHeaderVisible

Ich habe aber noch nicht alle getestet. Wenn ich mehr über die einzelnen Optionen herausfinde, werde ich obige Aufzählun ergänzen.

Zuletzt müssen wir jetzt noch das eigentliche Chrome Control erzeugen und sichtbar machen:

// Load the Chrome Control in the Chrome Container element of the page
var chromeNavigation = new SP.UI.Controls.Navigation(myApp.chromeContainerId, options);
chromeNavigation.setVisible(true);

Der Vollständigkeit halber hier noch die chromeLoaded-Funktion, die zum Abschluß ausgeführt wird. Wir machen hier nur den body der Seite sichtbar (er ist initial ausgeblendet):

$("body").show();

HTML erweitern

Es folgen noch die Erweiterungen, die im HTML der Seite(n) gemacht werden müssen. Zur Erinnerung: wir verwenden aspx-Seiten, aber grundsätzlich geht es mit jeder anderen Technologie analog. Wir binden zunächst im head jQuery und unsere eigene Scriptdatei ein. Danach rufen wir nur noch eine Funktion auf, sobald die Seite vollständig geladen ist:

<script type="text/javascript" src="../Scripts/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="../Scripts/appScripts.js"></script>
<script type="text/javascript">
  $(document).ready(
    myApp.setupPage
  );
</script>

Außerdem brauchen wir ein leeres div mit der oben angesprochenen ID. In dieses div wird das Chrome Control später gerendert und deshalb sollte es als allererstes ganz oben im body stehen:

<body style="display: none;">
 
<form id="form1" runat="server">
    <div id="divSPChrome"></div>

Hier sieht man auch gleich, daß der gesamte body anfangs unsichtbar gesetzt wurde. Zu Testzwecken wurde noch etwas Inhalt eingefügt, bei dem SharePoint-eigene CSS-Klassen verwendet wurden. Die Referenz zu den verfügbaren Klassen findet sich hier: Verwenden des CSS der Hostwebsite in Apps für SharePoint. Man kann daran testen, ob angewendete Designs aus dem Hostweb korrekt übernommen werden:

<h1 class="ms-accentText">H1 Header</h1>
<h2 class="ms-accentText">H2 Header</h2>
<div id="MainContent">
  This is the page’s main content.<br/>
  You can use the links in the header to go back to the host web,
  to help, account or contact pages.
</div>

Zum Schluß noch ein Bild, wie das Ganze aussehen kann:

Und hier nochmal der Downloadlink für die JavaScript-Datei.

SharePoint 2013 Workflows Teil 2: Listenelemente iterieren

Im ersten Teil habe ich gezeigt, wie man in einem SharePoint 2013 Workflow mit Hilfe der Aktion Call HTTP Web Service auf Listenelemente zugreifen kann. Dort ging es erstmal nur darum, die Anzahl verbundener Elemente zu ermitteln. In diesem Teil soll jetzt gezeigt werden, wie man mit den in 2013 neuen Mitteln eine Schleife zum Durchlaufen der gefundenen Elemente realisieren kann.

Testumgebung

Als Testumgebung werden die beiden Listen aus dem ersten Teil verwendet. Die Child List wurde um ein Zahlenfeld Value erweitert. Die Parent List bekam ein zusätzliches Feld Item Sum. Per Workflow sollen jetzt die Werte aus Value aller verbundenen Child-Elemente aufsummiert und in Item Sum des Parent-Elementes geschrieben werden.

Es geht hier nur um das Prinzip und natürlich sind auch viele andere Aktionen denkbar. Man könnte innerhalb der Schleife z.B. auch alle verbundenen Child-Elemente aktualisieren.

Schleifenaufbau

Wir erweitern also unseren bestehenden Workflow um eine neue Stufe. Damit wir die Schleife realisieren können, benötigen wir eine Schleifenvariable. Wir legen also eine Integer-Variable LoopCounter an und initialisieren sie mit 0 (Null). Außerdem benötigen wir eine Variable, in der wir die Werte der einzelnen Child-Elemente aufsummieren können. Wir legen also noch eine Number-Variable ItemSum an und initialisieren sie ebenfalls mit 0. Jetzt fügen wir eine Schleife vom Typ Schleife n-mal ein und konfigurieren sie so, daß sie so oft ausgeführt wird, wie unsere Variable ItemCount aus dem ersten Teil vorgibt. Die Variable enthält die Anzahl der gefundenen Elemente. Bis jetzt siehst es also so aus:

Zugriff auf ein Element

Als nächstes holen wir uns die ID eines Listenelementes aus den Ergebnissen der REST-Abfrage. Dazu verwenden wir wieder die Aktion Get an Item from a Dictionary. Für den Parameter item by name or path verwenden wir d/results(<Counter>)/ID, wobei der Platzhalter <Counter> für die nullbasierte Laufvariable steht, in unserem Fall also die Variable LoopCounter. Im Ganzen sieht das so aus:

Wie haben jetzt eine Variable ChildItemID, die pro Schleifendurchlauf immer die Element-ID eines Child-Elementes enthält und wir können damit auf das jeweilige Element zugreifen. Wir verwenden im Beispiel die Aktion Do Calculation, um den Wert des Feldes Value zu unserer Variablen ItemSum zu addieren. Der Zugriff auf das das Feld des Child-Elementes erfolgt dabei so:

Schleifenabschluß

Damit die Schleife auch korrekt funktioniert, müssen wir jetzt noch unsere Laufvariable LoopCounter hochzählen. Dazu verwenden wir wieder die Aktion Do Calculation und addieren einfach 1 (eins). Dabei ist zu beachten, daß Do Calculation immer einen Gleitkommawert zurückliefert, den wir nicht wieder direkt unserer Integer-Variablen zuweisen können. Wir verwenden hier also eine Zwischenvariable vom Typ Number und eine weitere Aktion Set Workflow Variable um diese unserer Laufvariablen zuzuweisen. Hier funktioniert die Konvertierung dann.

Ich habe versucht, als Laufvariable selbst eine Number-Variable zu verwenden, aber das hat bei meinen Versuchen nicht zuverlässig funktioniert. Deshalb der kleine Workaround.

Zum Abschluß benutzen wir noch eine Aktion Set Field in Current Item (nach der Schleife!) und schreiben damit die ermittelte Summe in das Feld Item Sum des aktuellen Elements. Zur Erinnerung: der Workflow läuft auf der Parent List und das aktuelle Element im Workflow ist daher ein Element dieser Liste.

Hier nochmal die gesamte Schleife in der Übersicht:

SharePoint 2013 Workflows Teil 1: Call HTTP Web Service

Nachdem ich mich jetzt endlich etwas mit den neuen SharePoint 2013 Workflows beschäftigen konnte, möchte ich hier meine Erfahrungen festhalten. Auch damit ich selbst etwas zum Nachschlagen habe 😉

Im ersten Teil soll es darum gehen, wie man mit der neuen Aktion Call HTTP Web Service Daten aus SharePoint abfragen kann. Selbstverständlich kann man damit jeden beliebigen Webservice abfragen, aber mir ging es ganz speziell um Daten aus SharePoint. Prinzipiell kommt man damit an die Daten aus jeder beliebigen SharePoint-Website, auch wenn sie sich in einer anderen Farm befinden. Voraussetzung dafür ist, daß der Benutzer dazu berechtigt ist (und natürlich daß der Webservice erreichbar ist).

Testumgebung

Für den Test habe ich ein einfaches Szenario aufgebaut: innerhalb einer Website gibt es eine Parent List und eine Child List, die über ein Nachschlagefeld mit der Parent List verbunden ist. Die Parent List hat ein zusätzliches Zahlenfeld Child Count. Ein Workflow auf der Parent List soll jetzt alle zugehörigen Elemente der Child List finden und deren Anzahl beim Parent in Child Count eintragen. Zur Verdeutlichung hier die Parent List:

Und die Child List:

Workflow erstellen

Wir öffnen jetzt die Website in SharePoint Designer 2013 und erstellen einen neuen Listenworkflow für die Parent List. Die Vorgehensweise dazu ist dieselbe wie in 2010, aber beim Anlegen muß darauf geachtet werden, daß SharePoint 2013 Workflow ausgewählt ist. Nur dann stehen die neuen Funktionen auch zur Verfügung:

Service-URL

Im Workflow brauchen wir zunächst die Adresse des Services, den wir für die Abfrage der verknüpften Elemente verwenden wollen. Wir nehmen dafür den in SharePoint 2013 neuen REST-Service, der unter der Adresse <Website>/_api zur Verfügung steht. Der Platzhalter <Website> steht hierbei für die absolute URL der Website, also ungefähr http://sharepoint.firma.tld/Sites/Site1. Wenn es sich wie in unserem Beispiel um die aktuelle Website handelt, kann man dafür die vordefinierte Variable Workflowkontext: Aktuelle Website-URL verwenden.

Da wir hier die Daten einer ganz bestimmten Liste möchten, hängen wir an die URL noch folgendes an: /_api/lists/GetByTitle(‚Child List‘)/Items. Da wir uns im ersten Schritt nur für die ID und den Titel der verknüpften Elemente interessieren, teilen wir der API das mit: ?$select=Id,Title.

Das wichtigste ist aber der Filter, weil wir nur die Elemente haben möchten, die zu unserem aktuellen Element der Parent List gehören. Prinzipiell geht das so: $filter=<LookupField> eq <ID>. <LookupField> ist der interne Name des Nachschlagefelds, <ID> ist die ID des aktuellen Parent-Elements und eq steht für equals also zu deutsch ist gleich. Achtung: damit der Filter funktioniert, muß man das Feld auch beim $select-Parameter angeben und dort mit dem Suffix Id (also ParentId). Zum Ausprobieren wie die Abfrage genau aussehen muß und wie die Felder heißen, empfehle ich einen Test im Browser. Wie das geht, wird weiter unten unter Exkurs erklärt.

Hier nochmal zusammengefaßt die gesamte aufzurufende URL, die wir einer Variablen REST-URL zuweisen. Da man den Text wegen der Nachschlageinformationen ohnehin nicht kopieren kann hier nur als Screenshot:

Request Header

Damit der REST-Service die Daten in einem Format liefert, das vom Workflow verarbeitet werden kann, müssen für die Abfrage sogenannte Request Header definiert werden. Die Header Accept und Content-Type müssen beide auf den Wert application/json;odata=verbose gesetzt werden. Im Workflow geschieht das durch den ebenfalls neuen Variablentyp Dictionary.

Wir legen also eine neue Dictionary-Variable RequestHeaders an und verwenden die Aktion Build Dictionary um die beiden Header einzutragen:

Exkurs: SharePoint 2013 REST API

Man kann den REST-Service zum Ausprobieren auch jederzeit einfach im Browser aufrufen. Der Service liefert XML zurück, das der Internet Explorer als Feed interpretiert. Wie man ihn dazu bringen kann die rohen XML-Daten anzuzeigen, habe ich hier beschrieben.

Wenn man sich die vom Service gelieferten Json-Daten im Browser anschauen möchte, wird es schon schwieriger, weil man wie oben für den Workflow beschrieben, die an den Server gesendeten Header modifizieren muß. Beim Internet Explorer habe ich keinen Weg gefunden das zu erreichen. Ich habe dann einige Plugins für Firefox getestet, aber leider auch ohne Erfolg. Fündig geworden bin ich dann mit Google Chrome und dem Plugin Change HTTP Request Header. Wenn man damit die Header wie oben gezeigt modifiziert, kann man sich das Ergebnis einer REST-Abfrage auch im Json-Format anzeigen lassen. Wer es etwas schöner formatiert haben möchte, kopiert es in den Json Parser Online.

Ein guter Einstiegspunkt in die offizielle Dokumentation von Microsoft findet sich hier: Get startet with the SharePoint 2013 REST service.

Call http Web Service

Jetzt aber weiter im Workflow. Nachdem wir jetzt die URL des Services und die notwendigen Header haben, fügen wir eine Call HTTP Web Service Aktion ein. Für den ersten Parameter verwenden wir einfach unsere vorher festgelegte Variable und belassen die HTTP-Methode bei GET:

Von den restlichen Parametern der Aktion setzen wir nur den ResponseContent (das Ergebnis der Abfrage) auf eine gleichnamige Dictionary-Variable und den ResponseCode auf eine gleichnamige String-Variable. Der Response Code gibt Auskunft über den Erfolg der Abfrage und kann zur weiteren Steuerung des Workflows verwendet werden. Er lautet OK, wenn die Abfrage syntaktisch in Ordnung war. Natürlich kann die Abfrage trotzdem logische Fehler enthalten, so daß z.B. der Filter nicht wie erwartet funktioniert, aber das muß separat festgestellt werden. Alle anderen Parameter interessieren uns hier nicht.

Achtung: der aufmerksame Leser fragt sich jetzt wahrscheinlich was mit unserer Variablen für die Request Header passiert. Diese muß der Workflowaktion auf anderem Wege zugewiesen werden und zwar über das Eigenschaften-Fenster, das man bei jeder Workflowaktion über ihr Menü erreichen kann. Das Menü findet man, wenn man mit der Maus über die Aktion fährt und dann auf den DropDown-Pfeil ganz rechts klickt. Innerhalb des Fensters kann man jetzt auch die Variable für die Request Header setzen (3. Punkt von oben):

Warum das an dieser Stelle so umständlich zu handhaben ist, kann ich nicht sagen, halte es aber für einen Bug. Oder zumindest für ein Versäumnis.

Ergebnisverarbeitung

Das Ergebnis der Abfrage haben wir jetzt in einer Dictionary-Variablen ResponseContent. Hier soll nur gezeigt werden, wie man die Anzahl der gefundenen Elemente ermitteln und in ein Feld des Parentelements schreiben kann. Zunächst holen wir uns ein Subset des Ergebnisses in eine weitere Dictionary-Variable ResultItems. Dazu verwenden wir die Aktion Get an Item from a Dictionary und geben für den Parameter item by name or path den Wert d/results an. Mit der Aktion Count Items in a Dictionary bekommen wir jetzt die Anzahl der gefundenen Elemente in eine Integer-Variable ItemCount. Mit der Aktion Set Field in Current Item lassen wir uns diesen Wert jetzt noch ins aktuelle Parentelement ausgeben.

Hier nochmal der gesamte Workflow zusammengefaßt:

Im zweiten Teil wird gezeigt, was man mit dem Ergebnis der Abfrage noch anstellen kann. Dort geht es dann um eine Schleife, die alle gefundenen Elemente durchläuft.