.NET Work Blog
■ .net.work » de » 2016 » 12 » Tanzen auf zwei Hochzeiten: SharePoint Provider-Hosted Add-Ins in Office365 & On-Premise installieren
Published: 14.12.2016 00:00:00
Categories: c - sharepoint
Tags: csom-de - office365-de - sharepoint - sharepoint2013-de - sharepoint2016-de - visual-studio
Worum geht es überhaupt?
Ich bin zwar nicht sicher, ob ich der einzige bin, der es versucht hat, aber zumindest schein ich der erste zu sein, der darüber schreibt: Eine SharePoint Provider-Hosted Add-In zu programmieren, dass sich sowohl auf einer lokalen SharePoint-Installation, als auch in Office365 verwenden lässt.
Vielleicht ist es auch eine deutsche Besonderheit: Hier herrscht ein ziemliches Misstrauen gegenüber der Cloud, viele Kunden wollen die Daten lieber auf “ihrem” Server lagern. Office365 ist deshalb kein Thema.
Wir haben derzeit einen Kunden, dem unsere Office365.-App “Mydea” sehr gut gefällt. Allerdings möchte er, dass sie auf einem SharePoint 2013 installiert wird. Also hatte ich drei Optionen:
Natürlich wollte ich die erste Option auf jeden Fall vermeiden. Die zweite Lösung hätte prinzipiell funktioniert, aber zu doppeltem Pflegeaufwand geführt. Das ist unschön und macht keinen Spaß. ”Mydea” ist ein Innovationstool, und als solches selbst natürlich möglichst innovativ. Das bedeutet, kurze Produktzyklen und agil entwickelt. Das doppelt pflegen zu müssen wäre die Hölle :)
Also habe ich mich für die dritte Option entschieden: Etwas Magie.Die Anwendung sollte - ohne beim (automatisierten) Ausliefern eine Zeile Code ändern zu müssen sowohl unter Office365, als auch unter SharePoint 2013 lauffähig sein.
Die übliche Antwort zu dieser Frage lautet: Das geht nicht. Aber es funktionert eben doch. Es ist nur ein sehr steiniger Weg um dorthin zu gelangen. Um diese Anleitung zu befolgen, solltest Du Dich bereits mit den Grundlagen im Erstellen von SharePoint-Add-Ins auskennen. Du solltest mindest ein [bs_tooltip placement=”top” trigger=”hover” title=”Microsoft hat - in ihrer Weisheit- ‘Apps’ in ‘Add-Ins’ umbenannt. Wenn ich also eines von beiden schreibe, meine ich immer das Gleiche.”]Add-In[/bs_tooltip] für SharePoint2013 und eines für Office365 erfolgreich deployed haben. Ich werde diese Grundlagen hier nicht erklären, und kann auch nicht auf Kommentare dazu hier oder per E-Mail antworten. Dafür ist StackOverflow, bzw. bei komplexeren Themen SharePoint@StackExchange da. Es gibt aber eine gute Chance, dass ich Dir dort antworten werde :)
Meine Reihenfolge war es zunächst eine Office365-App zu schreiben und diese dann optional in eine OnPrem-Installation zu wandeln. Diese Reihenfolge wird hier beschrieben. Die umgekehrte Reihenfolge sollte sich aber leicht durch geringe Modifikationen durchführen lassen.
Als erstes habe ich zwei Dummy “Hello World” - Projekte erstellt. Eines für O365, eines für SharePoint2013. Dann habe ich mir die Quellen angeschaut und “gedifft”. Dasdurch kam ich zu folgenden Ergebnissen:
NuGet: Folgende unterschiedliche Pakete werden verwendet:
OnPrem: - AppForSharePointWebToolKit
Office 365: - AppForSharePointOnlineWebToolKit - Microsoft.SharePointOnline.CSOM
Diese Pakete führen zu verschiedenen DLLs mit dem Prefix “Microsoft.SharePoint.Client.*“, aber unterschiedlichen Versionen: OnPrem: - Version 15.xxx Office 365: - Version 16.xxx
Der Quellcode der Dateien, die durch die Pakete im Projekt erstellt werden (SharePointContext.cs & Tokenhelper.cs) ist absolut identisch. Hier müssen also keine Änderungen vorgenommen werden.
Üblicherweise werden Office-Apps in Azure gehostet, aber für eine OnPrem-Installation wäre das zwar möglich, aber meist nicht gewünscht.
Es werden auch unterschiedliche Authentifizierungsmechanismen verwendet: OnPrem: - Verwenden von Zertifikaten - Windows Authentication
Office 365: - Verwenden eines ClientSecret - OAuth Authentifizierung
Diese unterschiedlichen Authentifizierungen werden über die Web.Config eingestellt.
Erstelle einen neuen Branch im repository. (oder mache ein Backup) Es kann immer etwas schief gehen :)
Um den gleichen Code für beide Anwendungszwecke verwendenn zu könnn (und so Änderungen/Fixes nur einmal pflegen zu müssen) benötigen wir verschiedene Build - Konfigurationen. Diese werden unter “Build - Configuration Manager” erstellt:
Erstelle zwei Konfigurationen für OnPrem. Eine für Debug-OnPrem und eine für Release-OnPrem. Kopiere hierfür die Einstellungen für die bereits vorhanden Debug und Release - Konfigurationen.
Führe dies jetzt zunächst nur für das Web-Projekt (und falls vorhanden) die Backend-Projekte durch, die vom MVC-Projekt verwendet werden. Später kommen weitere hinzu, aber um Schrittweise die Einstellungen testen zu können, sind kleine Schritte hier besser:
Es ist an der Zeit, Konfigurationsdateien für die neuen Build-Konfigurationen zu erstellen. Suche die Web.Config im Visual Studio, Klicke mit der rechten Maustaste darauf und wähle “Add config transform”. Danach sollten auf magische Weise zwei neue Konfigurationsdateien existieren.
Ich habe die PnP-Pakete für das O365-AddIn verwendet. Solltest Du stattdessen die “Basis”-Pakete verwendet haben, die Visual Studio nutzt, wenn ein neues SharePoint-Projekt erstellt wird, könnten sie bei Dir etwas anders aussehen. In jedem Fall wird aber das Paket “AppForSharePointOnlineWebToolKit” vorhanden sein.
Wenn man die Pakete betrachtet, dann ist der wesentliche Unterschied, dass bei der Office365-Lösung Azure- und OAuth-Pakete enthalten sind, die in der 2013-er Version nicht verwendet werden. Außerdem haben die O365-DLLs die Version 16.x, während die OnPrem-Version 15.x verwendet.
Um Sicher zu gehen, dass der PackageManager die Pakete nicht verwirft, wenn eine andere Konfiguration verwendet wird, reicht es, zwei Dummy-Projekte anzulegen. Z.B. dummy365 & dummyOnprem. Die Art der Projekte ist egal. Sie werden ohnehin nie ausgeführt. Sie sorgen nur dafür, dass immer die Pakete für O365 und 2013 im ./packages/ - Verzeichnis vorhanden sind. Das dummy365-Projekt sollte das O365PnP-NuGet-Package referenzieren, dummyOnPrem das 2013PnP-NuGet-Package.
Als nächsten Schritt solltest Du jetzt ein Backup der .csproj-Datei(en) machen. Diese enthält wichtige Informationen, die wir später benötigen.
Füge nun das “andere” PnP-Package zum WebProjekt hinzu. Wenn Du bereits das SharePointPnPCoreOnline-Paket installiert hast, install nun das SharePointPnpCore2013 NuGet-Paket (und umgekehrt)
Du kannst jetzt die vorherige csProj-Datei mit der aktuellen vergleichen. Oder mir einfach glauben: Es gibt zwei wesentliche Änderungen. Die eine ist das OfficeDevPnPCore - Paket. Der “HintPath” vom O365-Paket unterscheidet sich vom “HintPath” der 2013-Variante. Die DLLs sind dann aber wiederum identisch. Dennoch, weil wir ohnehin alles gerade ziehen, passen wir den Pfad passend zur Variante an.
Interessanter ist die Referenz zu “Microsoft.Online.SharePoint.Client.Tenant”. Die Office365 - Version verwendet Version 16.x und die OnPrem - Version verwendet 15.x. Das ist etwas, was wir auf jeden Fall beachten müssen.
Glücklicherweise erlauben csProj-Dateien Bedingungen. Also frisch ans Werk und folgende Zeilen eingefügt:
<ItemGroup Condition="'$(Configuration)'=='Debug.OnPrem' Or '$(Configuration)'=='Release.OnPrem'">
<Reference Include="Microsoft.Online.SharePoint.Client.Tenant, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL">
<HintPath>..\\packages\\SharePointPnPCore2013.2.9.1611.0\\lib\\net45\\Microsoft.Online.SharePoint.Client.Tenant.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="OfficeDevPnP.Core, Version=2.9.1611.0, Culture=neutral, PublicKeyToken=3751622786b357c2, processorArchitecture=MSIL">
<HintPath>..\\packages\\SharePointPnPCore2013.2.9.1611.0\\lib\\net45\\OfficeDevPnP.Core.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug' Or '$(Configuration)'=='Release'">
<Reference Include="Microsoft.Online.SharePoint.Client.Tenant, Version=16.1.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL">
<HintPath>..\\packages\\Microsoft.SharePointOnline.CSOM.16.1.5813.1200\\lib\\net45\\Microsoft.Online.SharePoint.Client.Tenant.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="OfficeDevPnP.Core, Version=2.9.1611.0, Culture=neutral, PublicKeyToken=3751622786b357c2, processorArchitecture=MSIL">
<HintPath>..\\packages\\SharePointPnPCoreOnline.2.9.1611.0\\lib\\net45\\OfficeDevPnP.Core.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
Wie man sehen kann, wurden für die unterschiedlichen Konfigurationen unterschiedliche DLLs referenziert. Bitte nicht einfach den Code von hier per copy&paste übernehmen Verwende die Zeilen aus Deiner Datei. Im Moment, wo Du diese Anleitung liest, können die Versionsnummern hier im Quellcode schon veraltet sein. Jetzt müssen nur noch die Ursprünglichen Zeilen innerhalb der “normalen” ItemGroup entfernt werden, welche diese Libraries enthalten haben.
Das war es erst einmal. Du solltest nun versuchen die Solution in allen vier Build - Konfigurationen zu builden-. Die OnPrem-Konfigurationen sollten eine Microsoft.Online.SharePoint.Client.Tenant.dll mit Version 15 in dem bin-Verzeichnis erzeugen und die Office365-Version eine V16-dll. Wenn es ein Problem gibt: Lade das Projekt neu. Manchmal bekommt VS Änderungen, die “außerhalb” erfolgt sind nicht richtig mit.
[bs_label type=”info”]Hinweis: Ich bin mir nicht sicher, ob as manuelle Bearbeiten der csProj-Datei ein Update überlebt. Es könnte als nach einem neuen Paket evtl. notwendig sein, die Datei erneut zu bearbeiten.[/bs_label]
Jetzt geht es an die Web.Configs
Füge folgende Zeilen direkt vor dem ”</configuration>“ Abschnitt in die *.OnPrem.config - Dateien hinzu:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly xdt:Transform="Replace" xdt:Locator="Condition(./\_defaultNamespace:assemblyIdentity/@name='Microsoft.Online.SharePoint.Client.Tenant')">
<assemblyIdentity name="Microsoft.Online.SharePoint.Client.Tenant" publicKeyToken="71e9bce111e9429c" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-15.0.0.0" newVersion="15.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
Wie vorher: Beim Zeitpunkt des Schreibens ist die Version 15.0 die aktuellste. Beim Zeitpunkt des Lesens, kann sie höher sein.
Ähnliches wird nun mit den “normalen” Configs durchgeführt:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly xdt:Transform="Replace" xdt:Locator="Condition(./\_defaultNamespace:assemblyIdentity/@name='Microsoft.Online.SharePoint.Client.Tenant')">
<assemblyIdentity name="Microsoft.Online.SharePoint.Client.Tenant" publicKeyToken="71e9bce111e9429c" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-16.1.0.0" newVersion="16.1.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
Die O365-Config-Dateien (Die ohne Suffix) sollten folgenden Inhalt haben:
<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
<add key="ClientId" value="aaaaaaaa-beef-face-bbbb-cchcchcchcch" />
<add key="ClientSecret" value="lsdschnabbeldiwuppsfkjashriuae=" />
</appSettings>
Die OnPrem-Configs müssen hingegen etwas anders aussehen:
<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
<add key="ClientId" value="aaaaaaaa-beed-face-bbbb-cchcchcchcch" />
<add key="ClientSigningCertificatePath" value="C:\\Certs\\myCert.pfx" />
<add key="ClientSigningCertificatePassword" value="p@ssw0rd" />
<add key="IssuerId" value="bbbbbbbb-beef-face-bbbb-cchcchcchcch" />
</appSettings>
Der wesentliche Unterschied ist, dass die ClientId/ClientSecret - Kombination nur im O365-Umfeld funktioniert. Für die OnPrem-Installation hingegen benötigen wir ein Zertifikat. (Wie genau das Zertifikat zu erzeugen und einzubinden ist werde ich hier nicht beschreiben, das haben andere schon sehr ausführlich getan.) Bitte darauf achten: Die ClientId ist nicht das Gleiche wie die IssuerId.
Falls noch weitere Projekte vorhanden sind, die mit SharePoint kommunizieren, sind hierfür einige zusätzliche Schritte notwendig. Ignoriere dieses Kapitel, wenn dies nicht der Fall ist.
Leider funktionieren Config-Transformationen nut mit Web.Configs. Andere Konfigurationen (App.Config) sind etwas anspruchsvoller. Aber wir kriegen das schon hin :)
Als erstes die zusätzlichen Configs manuell erzeugen vie “Add - New Item “ und in der Maske dann “Application Config File”. Nenn die Datei App.OnPrem.config. Öffne dann die Config-Datei mit Notepad. Suche nach einer ItemGroup welche die App.Configs enthält. Es sollte bereits Einträge mit App.Debug.config und App.Release.config geben, die ein “DependentUpon” - Attribut besitzen. Kopiere einfach den ganzen Tag, so dass folgendes herauskommt:
<ItemGroup>
<None Include="App.config" />
<None Include="App.Debug.config">
<DependentUpon>App.config</DependentUpon>
</None>
<None Include="App.Debug.OnPrem.config" >
<DependentUpon>App.config</DependentUpon>
</None>
<None Include="App.Release.OnPrem.config" >
<DependentUpon>App.config</DependentUpon>
</None>
<None Include="App.Release.config">
<DependentUpon>App.config</DependentUpon>
</None>
</ItemGroup>
Im Visual Studio (Solution neu laden) sollten die neuen Configs nun als Unterelement der App.Config angezeigt werden. After that all your OnPrem-Configs should be displayed as children from App.Config in Visual Studio (2015). Prüfe jetzt die csproj-Datei. Ab Visual Studio 2015 wars das und <UsingTask TaskName=”TransformXml” ../> sollte dort irgendwo stehen. Falls nicht, sind ein paar extra Schritte notwendig.
Der nächste Schritt ist wie bei der Web.Config, was ClientId & Co angeht. Zusätzlich muss die transform Property zu configuration hinzugefügt werden: