blog.atwork.at

news and know-how about microsoft, technology, cloud and more.

OfficeApps entwickeln-Teil 4 SharePoint Authentifizierung verwenden

Das Osterwochenende war mit dem Winterwetter perfekt geeignet, vorm PC zu sitzen und sich mit Lernen und Code zu befassen - wie Weihnachten, nur stand Osterjause statt Kekse auf dem Programm. Und natürlich Visual Studio, SharePoint 2013 und einige Developer-Tools wie Fiddler und Co. Ein Thema, mit dem ich mich nach längerer Zeit wieder näher befasst habe, ist die Entwicklung von SharePoint 2013 Apps und zwar mit der dafür erforderlichen Authentifizierung.

Konkret geht es hier eigentlich nicht um die Authentifizierung selbst, sondern um die Verwendung der SharePoint-Informationen in eigenen SharePoint Apps. Die Authentifizierung ist sozusagen das Grundgerüst jeder SP2013-App. Wer so wie ich Applikationen entwickelt, wird sich relativ wenig um die Details der Authentifizierung kümmern wollen, sondern lieber die Energie (sprich Arbeitszeit) in die Funktionen der eigenen App stecken. Somit hier mein sehr pragmatischer Weg, die gelieferten Daten von SP einfach zu verwenden.

Die Authentifizierung in SP2013 funktioniert (zum Glück) einfacher als zuvor (in SP2010). Anonyme Zugriffe sind in SharePoint 2013 standardmäßig ausgeschalten - der Benutzer, oder besser, die App muss sich an SP authentifizieren. Dazu liefert SharePoint alle erforderlichen Daten - allerdings nur einmal, nämlich beim ersten Starten der App.

Eine neue SP2013 App

Wir starten bei Null - mit einer neuen SP2013 App in Visual Studio (siehe OfficeApps entwickeln-Teil 3 Die erste Office-App).

Benötigt werden dazu Visual Studio 2012, die Office Developer Tools (siehe Neue Office Developer Tools for Visual Studio 2012) und ein Office 365 Developerkonto mit SP2013 (siehe Sign up for an Office 365 Developer Site).

Zuerst legen wir ein neues Projekt aus dem Template "App for SharePoint 2013" an:

new-sp2013-app

Im Assistenten verwenden wir AutoHosted (siehe OfficeApps entwickeln-Teil 1 Die Grundlagen).

new-sp-app-wizard-1

Beim Anklicken von Validate folgt die Anmeldung an der Office 365 Developer Site. Hier am besten den Schalter [x] Angemeldet bleiben markieren:

sp-app-connect

Nun kann der Assistent mit Finish beendet werden.

Die Standard-SP2013 App

Wir erhalten eine Basis-App - mit Authentifizierung in Page_Load. Verwendet werden Klassen auf TokenHelper.cs und es werden u.a. Referenzen von Microsoft.SharePoint.Client und Microsoft.IdentityModel verwendet. Beim Typ AutoHosted kann CodeBehind verwendet werden, hier das ganze Projekt im Überblick - mehr ist es nicht:

vs-sp2013-template

Starten mit F5: SharePoint fragt nach, ob der Benutzer der App vertraut, nach dem Motto: Alles zulassen - oder auf die App verzichten. Dazwischen gibt es keine Auswahlmöglichkeit. D.h. um die App zu verwenden, muss man "Trust it" auswählen.

trust-the-app

Die App wird aufgerufen - sie zeigt den Namen der SharePoint Website an.

app-run

Interessant dabei sind zwei Aspekte: Die URL lautet localhost (cool - debuggen funktioniert!) und es wir ein Haufen URL-Parameter mitgegeben. Diese werden in der App benötigt. Die Parameter werden zur Laufzeit durch Standard-Token von SharePoint ersetzt - der Platzhalter {StandardTokens} -, die sicherstellen, dass der Kontext valide ist und eine Kommunikation zwischen App und SP möglich ist. URL strings and tokens in apps for SharePoint informiert über die möglichen URL-Parameter.

Ein Experiment

Es geht hier um die Verwendung der Authentifizierung. Dazu starten wir ein kleines Experiment: Wir erweitern default.aspx um zwei ASP-Controls:

<asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click1" /><br />
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>

Die ASPX-Seite sieht nun so aus:

default-aspx-controls

Im CodeBehind fügen wir beim Click-Event auf Button1 die Ausgabe von DateTime.Now in Label1 hinzu:

protected void Button1_Click1(object sender, EventArgs e)
{
  Label1.Text = DateTime.Now.ToString();
}

Der geänderte komplette CodeBehind sieht also so aus. Zusätzlich setzen wir noch einen Breakpoint auf die erste Codezeile in Page_Load und einen auf die Codezeile in Button1_Click1.

code-behind-experiment

Start mit F5 - was passiert?

Nun, beim ersten Mal schaut alles bestens aus (wer will, springt mit F11 in die Klassen dahinter...).

run-debug

Relevant ist, dass wir hier von SharePoint Werte für contextToken und hostWeb erhalten - im Screenshot sieht man den gelieferten contextToken. Somit funktioniert die Authentifizierung und wir können die Query auf den Web.Title durchführen, wir erhalten den Namen der SharePoint-Website.

Fortsetzen mit F5. Nun Klick auf den Button in der Seite:

click-button1

Im Debugger sieht man nun, dass die Werte von SharePoint nicht mehr vorhanden sind:

code-behind-error

Der contextToken ist null. Beim Fortsetzen kommt dann der Folgefehler in TokenHelper.cs:

token-error

So - wie im SharePoint App-Template - klappts also (noch) nicht. Das Beispiel liefert aber schon alles, was man zum Entwickeln der eigenen App braucht - es gehört nur angepasst.

Eine Lösung

SP liefert die Daten {StandardTokens} tatsächlich nur beim ersten Aufrufen der App, nicht bei PostBack, Seitenwechsel oder ähnlichen Server-seitigen Operationen.

Klar, ASP.NET Developer wissen über die Relevanz von Page.IsPostBack - ganz wichtig auch für unsere SharePoint App. Zusätzlich zur Verwendung von Page.IsPostBack müssen wir uns aber noch die relevanten SP-Daten selbst merken!

Hierzu können verschiedene Mechanismen verwendet werden: ViewState, hidden fields, Sessions, Applications, Datenbank, URL-Parameter und was uns sonst noch so einfällt. Ich möchte hier nicht allzu sehr ins Detail gehen, sondern stattdessen gleich einen möglichen Lösungsweg zeigen. Wenn nur eine Seite verwendet wird, ist der folgende Weg eine einfache Lösungsvariante. (Wenn die App mehrere Seiten verwenden soll, müssen diese Werte zwischen den Seiten übergeben oder zentral gespeichert werden.)

Zunächst wird die default.aspx-Seite um zwei hidden fields erweitert:

<asp:HiddenField runat="server" ID="token"/>
<asp:HiddenField runat="server" ID="hostweb"/>

Hier merken wir uns den Token und sicherheitshalber die Webadresse des Hostwebs (von SharePoint).

Der CodeBehind wird wie folgt angepasst: Wenn kein PostBack erfolgt - also beim ersten Aufrufen der Webseite, merken wir uns die Daten in den beiden hidden fields (runat="server") und den Kontext in ClientContext. Die Methode RefreshContext() liefert und bei Bedarf die benötigten Werte.

Button1_Click1 wird erweitert - hier prüfen wir, ob ein ClientContext vorhanden ist. Wenn nein, laden wir die gemerkten Werte per RefreshContext(). Zusätzlich lesen wir den angemeldeten Benutzer aus und zeigen ihn an - um den Mechanismus auch gleich zu testen und zu erweitern. Hier der komplette Seitencode:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint.Client;

namespace MySPAppWeb.Pages
{
    public partial class Default : System.Web.UI.Page
    {
        // Getter und Setter für ClientContext
        internal static ClientContext ClientContext { get; set; }

        protected void Page_Load(object sender, EventArgs e)
        {
            // Im folgenden Code werden der Client-Kontext und die Title-Eigenschaft mit TokenHelper abgerufen.
            // Für den Zugriff auf andere Eigenschaften müssen möglicherweise Berechtigungen auf dem Hostweb angefordert werden.
            if (!Page.IsPostBack)
            {
                string contextToken = TokenHelper.GetContextTokenFromRequest(Page.Request);
                string hostWeb = Page.Request["SPHostUrl"];
                token.Value = contextToken;
                hostweb.Value = hostWeb;
                if (ClientContext == null) { ClientContext = RefreshContext(); }
                ClientContext.Load(ClientContext.Web, web => web.Title);
                ClientContext.ExecuteQuery();
                Label1.Text = ClientContext.Web.Title;
            }
        }
        protected ClientContext RefreshContext()
        {
            return TokenHelper.GetClientContextWithContextToken(hostweb.Value, token.Value, Request.Url.Authority);
        }
        protected void Button1_Click1(object sender, EventArgs e)
        {
            if (ClientContext == null) { ClientContext = RefreshContext(); }
            ClientContext.Load(ClientContext.Web, web => web.CurrentUser);
            ClientContext.ExecuteQuery();
            Label1.Text = DateTime.Now.ToString() + ", " + ClientContext.Web.CurrentUser.LoginName.ToString();
        }
    }
}

Der Screenshot zeigt den ganzen formatierten Code von default.aspx.cs:

code-behind-ok

Nun folgt der Funktionstest: App mit F5 starten und mehrmals (für PostBack) auf den Button klicken.

app-run-ok

Die App muss nun immer funktionieren, auch bei Reload und PostBack!

Dieses Beispiel zeigt somit einen exemplarischen Lösungsweg für die Verwendung des SharePoint Kontextes in eigenen Apps: Erforderlich und leicht lösbar, indem sich der Developer die benötigten Informationen selbst an einer beliebigen Stelle merkt und bei Bedarf wiederverwendet. Natürlich kann der Speichern- und Ladezugriff auch komplett clientseitig oder ganz anders erfolgen, je nach KnowHow und Technologie.

Mit diesem ersten wichtigen Handwerkzeug viel Spaß beim Entwickeln von eigenen SharePoint-Apps!

Hier weitere Links mit Infos zum Entwickeln von Apps:

Weiter geht es dann mit der Verwendung von REST-Calls und einer neuen kleinen SharePoint-App.

Loading