Nahtlose Provider-Hosted-App mit SharePoint 2013

Die Verbindung von Collaboration mit Application war beim traditionellen Lösungsdesign stets damit verbunden, Software in der Farm des Kunden zu installieren.

Mit SharePoint 2013 stellt Microsoft über das App-Model ein Konzept bereit, diese Notwendigkeit zu durchbrechen. Die Application wird direkt vom Anbieter auf dessen Infrastruktur bereitgestellt und kann mit der hier vorgestellten Lösung über den App-Store verbreitet werden.

 

Das Einbetten einer Provider-hosted App in einer SharePoint-Seite ist sehr einfach über die AppPart-Technologie möglich. Jedoch entsteht dabei immer ein Bruch mit der umgebenden Webseite.

 

Das folgende Bildschirmfoto zeigt die grau gefärbte SharePoint-Webseite mit einem Beispiel-AppPart. Der AppPart übernimmt nicht die Hintergrundfarbe der äußeren Webseite. Außerdem ist viel mehr Platz reserviert, als gerade benötigt wird.

 

100 embed poor

 

Besser integriert werden App-Inhalte über Mechanismen, wie sie im nächsten Abschnitt Mashup vorgestellt werden.

 

Voraussetzungen

Um die hier gezeigten Beispiele nachzuvollziehen, sind folgende Bestandteile notwendig:

  • Eine Webseite auf SharePoint Online
  • Eine Provider-Hosted-App
  • Einbindung von jQuery auf der SharePoint-Webseite

 

Mashup mit Provider-Hosted-Apps

Zusammenfassung

Die Einbindung von Komponenten aus einer Provider-Hosted-App in den SharePoint-Rahmen erfolgt über das einfache Einbinden eines <script>-Tags. Dabei wird die Benutzerauthentifizierung im Hintergrund ausgeführt.

 

Vorteile der Mashup-Technik

Mit der Mashup-Technik ist es möglich, Funktionen von einer anderen Webseite wiederzuverwenden. Prominente Beispiele dafür sind die Anzeige eines Kartenausschnitts oder die Einbettung eines Videos. Dabei werden keine Daten auf der Seite gespeichert, die die Elemente einbettet. Die Daten verbleiben beim ursprünglichen Anbieter.

 

Damit diese problemlose Einbettung funktioniert, stellt der Anbieter ein Stück HTML-Code bereit. Dieser Code sorgt beim Laden der Seite dafür, alle notwendigen Elemente vom Server des Anbieters nachzuladen und in die umgebende Websseite einzubetten. Der Autor einer Mashup-Seite muss folglich nur eine Zeile HTML in seine Webseite einbauen. Im Bild-Beispiel ist der iframe-HTML-Code zu sehen, der bei seiner Verwendung ein Video anzeigen wird.

 

Embed Video

 

Umsetzung

Das Erstellen und Betreuen einer Provider-Hosted-App ist für sich genommen schon eine Herausforderung. In der klassischen Umsetzung kann diese App einen App-Part zur Verfügung stellen, um App-Inhalte im Rahmen der SharePoint-Webseite anzuzeigen. Dahinter verbirgt sich allerdings ein IFrame. Damit einher geht der Nachteil, dass der IFrame in seiner Größe fest vorgegeben ist. Außerdem kann er nicht mit den Inhalten außerhalb agieren.

 

An dieser Stelle entsteht der Wunsch nach einer einfacheren Lösung. Das Ziel ist es, nur eine JavaScript-Datei einzubinden. Anschließend soll darüber die Funktionalität in die Webseite injiziert werden.

 

Das Referenzieren und Laden eines Scripts vom Provider stellt kein Problem dar. Interessanter wird es, wenn das Script nun Funktionen der Provider-App nutzen soll, die nur authentifizierten Benutzern zur Verfügung stehen. Das Laden des Scripts durch den Browser läuft vollständig ohne Authentifizierung ab. Danach startet der Browser das Script (Zeile 50). Da der Benutzer gegenüber der App bereits authentifiziert sein könnte, steht als erstes der Datenabruf über eine App-Schnittstelle an (Zeile 14).

 

An dieser Stelle prüft der Code auf dem Server des App-Providers bereits, ob der Benutzer dort authentifiziert ist. Beim ersten Aufruf der Seite ist das nicht der Fall, deshalb enthält die Antwort einen Hinweis darauf (Zeile 18). Daher wird nun die Authentifizierung im Hintergrund angestoßen (Zeile 21).

 

Die Provider-App benötigt ein Token für Zugriffe auf die SharePoint-API. Dieses Token wird beim Aufruf der "appredirect.aspx" vergeben. Deshalb wird diese Seite abgerufen (Zeile 35). In der Antwort befinden sich mehrere Parameter (Zeile 38), die der App übergeben werden sollen. Die Namen und Werte werden herausgelesen (Zeile 39). Die gesammelten Informationen werden jetzt an die Provider-App geschickt (Zeile 41). Ab diesem Zeitpunkt ist die Session auf Provider-Seite aufgebaut und der Benutzer ist authentifiziert. Nun kann der Datenabruf erneut angestoßen werden (Zeile 46).

 

Der Datenabruf gelingt nun und die Werte können an die Ausgabefunktion weitergereicht werden (Zeile 26).

 

Schlägt der zweite Datenabruf erneut fehl, wird dies über die Variable firstTry erkannt. Die Ursachen können beispielsweise mit Hilfe der Entwicklungswerkzeuge im Browser gesucht werden. Anhaltspunkte sind:

  • Drittanbieter-Cookies müssen zugelassen sein,
  • CORS muss korrekt konfiguriert sein

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
(function (jQuery) {
  "use strict";
  var appbase = 'https://your-app-server',
  firstTry = true;
  function showActivities(ac) {
    var i,
    a;
    for (i in ac) {
      a = ac[i];
      a.link = appbase + a.link;
      jQuery('#gameactivities').append('<div><a href="' + a.link + '">' + a.activity + '</a></div>');
    }
  }
  function queryActivities() {
    jQuery.ajax({
      url : appbase + '/api/activities',
      xhrFields : {
        withCredentials : true
      }
    }).then(function (res) {
      if (res.auth === false) {
        if (firstTry) {
          firstTry = false;
          auth();
        } else {
          alert("Authentication failed.");
        }
      } else {
        showActivities(res.activities);
      }
    });
  }
  function auth() {
    var spbase = _spPageContextInfo.webAbsoluteUrl;
    if (spbase[spbase.length - 1] !== '/') {
      spbase += '/';
    }
    spbase += _spPageContextInfo.layoutsUrl;
    jQuery.get(spbase + '/appredirect.aspx?response_type=code&redirect_uri=' + encodeURIComponent(appbase + '/auth/sharepoint/callback') + '&client_id=af4f8d07-2b2f-4b55-b631-d9bcd19229be')
    .then(function (h1) {
      var h1$ = jQuery(h1),
      posttarget = h1$.find('input').parent().attr('action'),
      postdata = {};
      h1$.find('input[type="hidden"]').each(function () {
        var h2$ = jQuery(this);
        postdata[h2$.prop("name")] = h2$.val();
      });
      jQuery.ajax({
        type : 'POST',
        url : posttarget,
        data : postdata,
        xhrFields : {
          withCredentials : true
        }
      }).then(queryActivities);
    });
  }
  queryActivities();
})(jQuery);

 

Die regelmäßige Ausführung dieses Scripts wird im nächsten Abschnitt umgesetzt.

 

Globale (seitenübergreifende) App-Einbindung in Office 365

Der folgende Abschnitt zeigt, wie die Funktionalität aus der Provider-hosted-App auf allen Seiten einer Website eingebunden werden kann.

 

Es ist sehr einfach, ein eigenes <script>-Tag in die Masterpage zu setzen. Dabei wird die »Vererbung« der Datei unterbrochen. Dies ist jedoch nicht empfohlen, weil damit Aktualisierungen und Verbesserungen nicht mehr auf diese Masterpage angewendet werden.

 

Eine bessere Alternative schlägt das Office-Entwicklerteam vor: durch Registrieren einer CustomAction kann genau der gleiche Effekt erreicht werden. In Anlehnung an den MSDN-Artikel Customize your SharePoint site UI by using JavaScript kann mit folgendem JavaScript-Code gearbeitet werden:

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(function() {
      "use strict";
      var ctx = SP.ClientContext.get_current();
      var web = ctx.get_web();
      var ucas = web.get_userCustomActions();
      var ca = ucas.add();
      ca.set_description("ProviderAppActivities");
      ca.set_location("ScriptLink");
      ca.set_scriptBlock("var headID = document.getElementsByTagName('head')[0]; var newScript = document.createElement('script'); newScript.type = 'text/javascript'; newScript.src = 'https://your-app-server/javascripts/showActivities.js'; headID.appendChild(newScript);");
      ca.update();
      ctx.executeQueryAsync(function() { console.log("OK"); }, function(a,b) { console.log("Err: " + b.get_message()); });
})()

 

 

Ein Hinweis an dieser Stelle: wird an Stelle des scriptBlock der Wert für scriptSrc auf die externe Adresse gesetzt, bricht SharePoint das Rendern der Seite ab. Die produzierte Seite enthält nur noch einen allgemeinen Fehler und initialisiert keinerlei SharePoint-Funktionen.

 

 

Einbau in die SuiteBar

Der letzte Abschnitt dieser Anleitung widmet sich der SuiteBar. Die SuiteBar ist am oberen Rand der Webseite und hält häufig genutzte Hyperlinks und Funktionen bereit.

 

Eine Besonderheit bei Office 365 ist die Shell, der Bereich zum Wechsel zwischen den Anwendungen. Dort soll die Provider-hosted-App ebenfalls ihren Platz finden.

 

Die Office-365-Shell innerhalb der SuiteBar wird sehr spät geladen. Deshalb ist es erforderlich, auf deren Fertigstellung zu warten:

 

1
2
3
4
5
6
7
ExecuteOrDelayUntilScriptLoaded(function() {
  //
  // ... Die SuiteBar ist jetzt geladen
  //
 }, "O365SuiteServiceProxy.requestexecutor.js");

 

Das zugehörige DOM-Element ist eine Tabelle im Container mit der Klasse o365cs-nav-rightMenus. Es kann mit dem jQuery-Selektor .o365cs-nav-rightMenus table tr identifiziert werden.

 

Dort wird als erstes Kind-Element folgender HTML-Code platziert:

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<td>
  <div class="o365cs-nav-topItem">
    <button 
      type="button" 
      class="o365cs-nav-item o365cs-nav-button o365cs-topnavText ms-bgc-tdr-h o365button" 
      role="menuitem" 
      title="Öffnen Sie die Aktivitäten-Liste." 
      id="ProviderAppActivities"
      aria-haspopup="true">
      <span class="wf wf-size-x18 wf-family-o365" role="presentation">A</span>
    </button>
  </div></td>

 

Damit der Klick auf den Knopf zu einer Aktion führt, wird die SharePoint Popup-Funktion aus callout.js genutzt.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
SP.SOD.executeFunc(
  "callout.js", 
  "Callout", 
  function () {var targetElement = document.getElementById('ProviderAppActivities');var calloutOptions = new CalloutOptions();
	calloutOptions.ID = 'ProviderAppActivitiesCallOut';
	calloutOptions.launchPoint = targetElement;
	calloutOptions.beakOrientation = 'topBottom';
	calloutOptions.content = content;
	calloutOptions.title = 'Kürzliche Aktivitäten';var displayedPopup = CalloutManager.createNew(calloutOptions);
  });

 

Die Variable content enthält das HTML, das innerhalb des Popups angezeigt werden soll.

 

Der vollständige Aufruf sieht so aus:

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
ExecuteOrDelayUntilScriptLoaded(function() {
  jQuery$('.o365cs-nav-rightMenus table tr').prepend('<td><div class="o365cs-nav-topItem"><button type="button" class="o365cs-nav-item o365cs-nav-button o365cs-topnavText ms-bgc-tdr-h o365button" role="menuitem" title="Öffnen Sie die Aktivitäten-Liste." id="ProviderAppActivities" aria-haspopup="true"><span class="wf wf-size-x18 wf-family-o365" role="presentation">A</span></button></div></td>');
  SP.SOD.executeFunc("callout.js", "Callout", function () {
    var targetElement = document.getElementById('ProviderAppActivities');
    var calloutOptions = new CalloutOptions();
    calloutOptions.ID = 'ProviderAppActivitiesCallOut';
    calloutOptions.launchPoint = targetElement;
    calloutOptions.beakOrientation = 'topBottom';
    calloutOptions.content = content;
    calloutOptions.title = 'Kürzliche Aktivitäten';
    var displayedPopup = CalloutManager.createNew(calloutOptions);
  });
}, "O365SuiteServiceProxy.requestexecutor.js");

 

Das Ergebnis kann sich sehen lassen: eine eigene Aktion in der SuiteBar, die beim Anklicken Daten einer Provider-hosted-App darstellt. Alle Elemente sind nahtlos kombiniert auf der SharePoint Webseite enthalten, es entsteht keine Abweichung zur Umgebung mehr.

 

App-Funktion in der SuiteBar

 

Zusammenfassung

Im ersten Schritt Mashup wurde ein JavaScript erstellt, dass grundlegende Funktionen einer Provider-hosted-App in eine SharePoint-Webseite übertragen kann.

 

Der zweite Schritt hat das bereitstehende JavaScript dauerhaft in die SharePoint-Umgebung eingebettet.

 

An dritter Stelle wurde die SharePoint-Oberfläche ganz ohne IFrame um eine Funktion aus der Provider-hosted-App erweitert, sodass kein Bruch mit der Umgebung mehr stattfindet.