Call 4 Paper - SharePoint Konferenz 2014 - München

Auch 2014 findet wieder unsere SharePoint Konferenz Deutschland statt. Dieses Jahr sind wieder direkt in München (http://www.kolpinghaus-muenchen-zentral.de/) . Unmittelbar im Anschluss an die VSOne startet die 2-tätige SharePoint Konferenz am 19.2.2014. Diesmal stehen die Lösungen, die  mit SharePoint möglich sind im Vordergrund. (Welchen Nutzen erhalten SharePoint Anwender?) Da die Konferenz den Erfahrungsaustausch forcieren soll, werden auch die Session in diese Richtung ausgewählt. Was ist mit SharePoint möglich? Wie wurden Anforderungen umgesetzt? Welche Probleme gab es in der Umsetzungsphase? All dies sind Themen der Konferenz. Aber auch der technische Aspekt wird abgedeckt. ITPro und Development Session werden ebenfalls im Programm berücksichtigt. Hier geht es in erster Linie um die neuen Themen in SharePoint 2013: Social Yammer App Development Office Development Business mit dem App-Model BI mit SharePoint Search Schickt mir bitte Eure Session-Vorschläge mit Titel, Abstract, Level und Zielgruppe an marting@ppedv.at

Item Berechtigung mittels Remote Event Receiver setzen

In SharePoint 2013 gibt es im Rahmen des neuen App-Modell auch das Konzept der Remote Event Receiver. Im Prinzip ein Event Receiver, der nicht mit Server API programmiert wird, sondern analog zu Apps Remote läuft und daher auch mittels Client Side Object Model zu programmieren ist. Das Visual Studio bietet in einem App Projekt die Möglichkeit einen “Remote Event Receiver” als Item hinzuzufügen. Im Web Projekt wird dann ein Service angelegt. Der Code ist simpel. Eine Klasse welches das Interface “IRemoteEventService” implementiert. Hier finden wir zwei Funktionen. ProcessEvent und  ProcessOneWayEvent. ProcessEvent ist für die synchronen Events und ProcessOneWayEvent wird für asyncrone Events aufgerufen. Im App-Projekt wird zusätzlich mittels einer Elements.xml Datei das Event im SharePoint Server registriert und die Url des Remote Webs hinterlegt. Die Datei wird vom Visual Studio automatisch angelegt. Die Events werden mit einem Wizard ausgewählt. Die Bedienung des Wizard ist analog zur Anlange eines Server-seitigen EventReceivers. <?xml version="1.0" encoding="utf-8"?> <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> <Receivers ListTemplateId="100"> <Receiver> <Name>RemoteEventReceiver1ItemAdding</Name> <Type>ItemAdding</Type> <SequenceNumber>10000</SequenceNumber> <Url>~remoteAppUrl/Services/RemoteEventReceiver1.svc</Url> </Receiver> <Receiver> <Name>RemoteEventReceiver1ItemAdded</Name> <Type>ItemAdded</Type> <SequenceNumber>10000</SequenceNumber> <Url>~remoteAppUrl/Services/RemoteEventReceiver1.svc</Url> </Receiver> </Receivers> </Elements> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } In diesem Beispiel, habe ich den Namen des Eventreceivers gleich beim Visual Studio Vorschlag belassen “RemoteEventReciever1”. Bei meinen Versuchen hat diese XML Datei keine Auswirkung auf das Verhalten von SharePoint gehabt. Der EventReceiver wurde nicht aufgerufen. Ich habe es mit Visual Studio 2013 und Visual Studio 2012 probiert. Kein Erfolg. Daher habe ich einen mir persönlich bequemeren und zuverlässigeren Weg gewählt. Der EventReceiver wird per Programm-Code registriert. Jede App bietet die Möglichkeit App Events für Install, Upgrade und Uninstall zu hinterlegen. Hierfür muss in den Projekteigenschaften die Eigenschaften “Handle App Installed”, “Hanlde App Uninstalling” oder “Handle App Ugraded” auf “true” gesetzt werden. Sobald eine dieser Eigenschaften auf True gesetzt wird, wird vom Visual Studio wieder ein EventReceiver Service angelegt. Egal welches Event behandelt wird, die notwendigen Informationen werden im Parameter “properties” der Methode ProcessEvent oder ProcessEventOneWayEvent übergeben. 1: public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties) 2: { 3: SPRemoteEventResult result = new SPRemoteEventResult(); 4:   5: using (ClientContext clientContext = TokenHelper.CreateAppEventClientContext(properties, false)) 6: { 7: if (clientContext != null) 8: { 9: clientContext.Load(clientContext.Web); 10: var list = clientContext.Web.Lists.GetByTitle("test1"); 11: clientContext.Load(list); 12: 13: string opc = OperationContext.Current.Channel.LocalAddress.Uri.AbsoluteUri.Substring(0, 14: OperationContext.Current.Channel.LocalAddress.Uri.AbsoluteUri.LastIndexOf("/")); 15:   16: string remoteUrl = string.Format("{0}/RemoteEventReceiver1.svc", opc); 17:   18: EventReceiverDefinitionCreationInformation newEventReceiver = new EventReceiverDefinitionCreationInformation() 19: { 20: EventType = EventReceiverType.ItemAdded, 21: ReceiverName = "RemoteEventReceiver1", 22: ReceiverUrl = remoteUrl, 23: SequenceNumber = 1000 24: }; 25: list.EventReceivers.Add(newEventReceiver); 26: clientContext.ExecuteQuery(); 27: } 28: } 29:   30: return result; 31: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } In der Event Routine wird zunächst ein ClientContext gebildet (Zeile 5). Dieser Context arbeitet mit dem App User. D.h. der Context wird unter dem App Account erstellt. Die App muss somit das App Only Berechtigungsmodell verwenden. (siehe hier) Der entsprechende Eintrag wird im AppManifest automatisch vom Visual Studio gesetzt: <AppPermissionRequests AllowAppOnlyPolicy="true" >    <AppPermissionRequest Scope="http://sharepoint/content/sitecollection/web" Right="FullControl" /> </AppPermissionRequests> In Zeile 18 wird die EventReceiverDefintion erstellt. Oder genauer gesagt, das entsprechende CreationInformation Objekt angelegt. Die Properties sind selbsterklärend. Zu guter Letzt wird der EventReceiver an die entsprechende SharePoint Liste gehängt. In meinem Fall eine Liste mit dem Namen “Test1” (siehe Zeile 10). Da bei der Definition auch die URL des Eventreceivers anzugeben ist, stellt uns der Operation Context die Url zu Verfügung. Entweder local für Debugging oder die tatsächliche Remote Adresse des Services. Die URL wird in den Zeilen 13-16 gebildet. Der Event Receiver Da der Event Receiver als “ItemAdded” angelegt wurde, bedeutet dies, dass die Methode “ProcessOneWayEvent” aufgerufen wird. (Asynchrones Event). Der Code im Event Receiver ist straight forward. Zunächst wieder den ClientContext bilden. Danach werden in den Zeilen 8 und 9 die Liste bzw das ListItem geladen. Die Information wie ListTitle und ListItemId werden im Übergabe-Parameter “properties” an den EventReceiver übergeben. Da es sich hier um ein Item Event handelt sind diese beiden Properties im Property “ItemEventProperties” zu finden. Genauso sind in dieser Klasse “ListEventProperties”, “WebEventProperties”, “AppEventProperties” und “SecurityEventProperties” zu finden.Die Eventart kann über das Property “EventType” ermittelt werden. Ziel des EventReceivers ist es, dem ListenItem eine eigene Berechtigung zu geben.  In weiterer Folge wird im Code die Berechtigungsvererbung für das Item unterbrochen und dem User, der das Item angelegt hat, nur noch Leserechte gegeben. Da der Eventreceiver mit den Rechten der App ausgeführt wird (App Only Policy) benötigt der User, der das Item anlegt keine Verwaltungsrechte auf der Liste. 1: public void ProcessOneWayEvent(SPRemoteEventProperties properties) 2: { 3: using (ClientContext clientContext = TokenHelper.CreateRemoteEventReceiverClientContext(properties)) 4: { 5: if (clientContext != null) 6: { 7: clientContext.Load(clientContext.Web); 8: List liste = clientContext.Web.Lists.GetByTitle(properties.ItemEventProperties.ListTitle); 9: ListItem it = liste.GetItemById(properties.ItemEventProperties.ListItemId); 10: clientContext.Load(it); 11: clientContext.Load(clientContext.Web.CurrentUser); 12: clientContext.ExecuteQuery(); 13:   14: it.BreakRoleInheritance(false, true); 15:   16: RoleDefinition roldef = clientContext.Web.RoleDefinitions.GetByName("Read"); 17: Principal p = clientContext.Web.EnsureUser(properties.ItemEventProperties.UserLoginName); 18: RoleDefinitionBindingCollection c = new RoleDefinitionBindingCollection(clientContext); 19: c.Add(roldef); 20:   21: it.RoleAssignments.Add(p, c); 22: it.Update(); 23:   24: clientContext.ExecuteQuery(); 25: } 26: } 27: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Ergebnis ist nun, dass ein User zwar ein Item anlegen kann, sobald es aber angelegt ist, kann der User nur mehr selbst lesend darauf zugreifen. Auch wenn diese Vorgehensweise möglich ist gebe ich doch eines zu bedenken: Wenn das Service nicht verfügbar ist, so wird das Item angelegt, aber die Berechtigungen nicht verändert. Es ist zu überlegen, ob man in diesem Anwendungsfall nicht doch zu einem Workflow oder einem Server-Seitigen EventReceiver greift.

SharePoint Hosted App in TypeScript mit Visual Studio 2013

TypeScript nähert sich mittlerweile der Version 1.0 an. Im Moment gibt es in Visual Studio 2013 Support für TypeScript 0.9, wenn das AddIn von der TypeScript Seite installiert wird. Zumindest findet man in den Projektvorlagen bereits TypeScript unter “Other Languages”. Solange das AddIn nicht installiert findet man nur den Link zum Setup File. Sobald das TypeScript AddIn installiert ist, gibt es auch eine Vorlage für eine HTML Applikation. Wenn jedoch eine SharePoint Hosted App angelegt wird, gibt es keine Möglichkeit TypeScript als Sprache auszuwählen. Die Vorlagen für die App liegen unter C# oder VB. Sobald jedoch die App angelegt wurde, kann in das Script Verzeichnis eine TypeScript Datei hinzugefügt werden. Die Vorlage für TypeScript, liegt unter der Kategorie “Visual C# Items”. Visual Studio sagt, es hätte das Projekt umkonfiguriert um TypeScript zu unterstützen und bietet an, die TypeDefinitions gleich als NuGet Packages zu installieren: Es wird der NuGet Dialog geöffnet und gleich die Packages mit dem Tag TypeScript angezeigt. Hier empfiehlt es sich jquery auszuwählen.  Zusätzlich bringt die Suche nach “sharepoint Typescript” auch die TypeDefinitions für das Client Object Model zu Tage, welches auch gleich installiert wird. Mein Dialog sieht dann wie folgt aus: Schreibt man nun im TypeScript File SharePoint Client Object Model Code, bekommt man bereits IntelliSense Unterstützung. Die Datentypen werden angezeigt. Einzig, aus dem TS File wird noch kein JavaScript File erstellt, welches eingebunden werden kann. Auch wenn das Visual Studio zuvor behauptet hat, dass das Projekt für TypeScript konfiguriert wurde. Es stimmt nicht. Vergleicht man ein TypeScript Project File mit einem App  Project File, stellt man fest, dass die TypeScript Konfiguration fehlt. Folgende Zeilen müssen in das Project-File am Ende aufgenommen werden: <PropertyGroup> <TypeScriptTarget>ES3</TypeScriptTarget> <TypeScriptIncludeComments>true</TypeScriptIncludeComments> <TypeScriptSourceMap>true</TypeScriptSourceMap> <TypeScriptModuleKind>CommonJS</TypeScriptModuleKind> </PropertyGroup> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" /> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Um ein VS Project File zu bearbeiten muss zunächst das Projekt mit “Unload” geschlossen werden. Der entsprechende Menüpunkt ist im Kontext-Menüs des Projektes zu finden: Danach ist es mit “Edit *.csproj” möglich den XMLS der Projektdatei zu bearbeiten. Nun können vor dem Closing Tag des Projekts die Zeilen für TypeScript eingefügt werden. Die Projektdatei sollte dann wie folgt aussehen: Nachdem die Datei gespeichert wurde, kann über das Kontextmenü des Projekts, das Projekt wieder geladen werden. (“Reload Project”) Sobald nun das Projekt neu kompiliert wird, werden auch von TypeScript die JavaScript und die Mappingdatei angelegt. Das kann mit einem Blick auf alle Dateien verifiziert werden. Als letzten Schritt sind die beiden Dateien in das Projekt aufzunehmen, sodass diese beim Deployment auch korrekt mit ausgeliefert werden. Nun ist das Projekt fertig konfiguriert und wir können SharePoint Apps in TypeScript programmieren.

Spaß mit “this” in TypeScript

Eigentlich war der Plan ganz simpel. Eine kleine SharePoint Hosted App, die in Typescript geschrieben wird und die Daten einer Datenbank mit WebAPI bzw. Json abfragt. Um den Entwicklungsaufwand gering zu halten verwende ich jquery und knockout. Aber wie so oft, liegt der Stolperstein nicht dort, wo man ihn erwarten würde. In der einfachen App soll eine TypeScript Klasse als ViewModel dienen. In der TypeScript Datei habe ich ein Interface für die Datenbankeinträge geschrieben. Es macht einfach mehr Spaß wenn die Objekte typisiert sind. Das ViewModel besteht aus einer Liste, äh sorry, in JavaScript ist es ja ein Array und einem Property, das für die Datenabfrage verwendet wird. Im Beispiel ist es das Quartal, nachdem die Daten selektiert werden sollen. Die Liste (Array) und das Property sind in der Klasse wie folgt definiert: 1: GoalSheetItemList: KnockoutObservableArray<IGoalSheetItem> = ko.observableArray<IGoalSheetItem>(); 2: Quartal: KnockoutObservable<string> = ko.observable<string>('Q1'); .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } zusätzlich gibt es eine Funktion “FetchAllItems” die die WebAPI Abfrage ausführt und das Ergebnis in die Liste schreibt: 1: fetchAllItems() { 2: var url: string; 3: url = "http://localhost:61070/api/GoalSheet/?Quartal=" + this.Quartal(); 4: $.getJSON(url, 5: it => { 6: this.GoalSheetItemList(it); 7: } 8: ); 9: }     zuletzt werfen wir auch noch einen Blick auf den View. Eine simple HTML Datei mit einer Input Box die ans Quartal gebunden wird und einer Tabelle um die Daten anzuzeigen: 1: <p> 2: Quartal: <input data-bind="value: Quartal" /> 3: </p> 4:   5: <table> 6: <thead> 7: <tr> 8: <th>Quartal</th> 9: <th>Art</th> 10: <th>Trainer</th> 11: <th>Ziel</th> 12: <th>Ist</th> 13: <th></th> 14: </tr> 15: </thead> 16: <tbody data-bind="foreach: GoalSheetItemList"> 17: <tr> 18: <td data-bind="text: Quartal"></td> 19: <td data-bind="text: Bezeichnung"></td> 20: <td data-bind="text: TrainerLogin"></td> 21: <td data-bind="text: Ziel"></td> 22: <td data-bind="text: Ist"></td> 23: </tr> 24: </tbody> 25: </table> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Der Spaß begann, als ich bei einer Änderung des Quartals die Abfrage nochmals ausführen wollte um die Daten des entsprechenden Quartals anzuzeigen. KnockOut bietet mit den Observables ja die Möglichkeit Funktionen zu hinterlegen die bei Datenänderung aufgerufen werden. Somit habe ich im Konstruktor einfach die FetchAllItems Methode beim Quartal hinterlegt: 1: constructor() { 2: this.Quartal.subscribe(this.fetchAllItems); 3: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Soweit eigentlich nichts böses. Bis zum ersten Testlauf! Das Objekt “this” unterstützt die Methode Quartal nicht, obwohl Quartal doch als Member der Klasse definiert wurde und die Funktion ebenfalls Member der Klasse ist. Das Problem tritt in der FetchAllItemns-Methode auf. Wobei die Methode sauber funktioniert, wenn sie direkt aufgerufen wird. Das Problem ist der Aufruf durch Änderung des Properties. Wird die Methode über diesen “Umweg” aufgerufen, zeigt this nicht mehr auf die Klasse! TypeScript unterstützt jedoch auch closures. D.h. Funktionen die Zugriff auf Variablen der Umgebung haben. Formuliere ich die Zuweisung des Aufrufs der FetchAllItems Funktion als Closure, dann zeigt this in der FetchAllItems-Methode weiterhin auf die Klasse. Mein Konstruktor muss also wie folgt geschrieben werden: 1: constructor() { 2: this.Quartal.subscribe((newVal) => { 3: this.fetchAllItems(); 4: }); 5: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Die Art und Weise, wie im Konstruktor der Aufruf der “Quartal-Changed” Methode formuliert wird, entscheidet was in der Methode “this” ist. Fun mit TypeScript

ZIP Datei beim SharePoint-Upload automatisch entpacken

Manchmal ist es wünschenswert, dass Benutzer eine Zip-Datei auf SharePoint hochladen können, diese Datei sofort beim Upload entpackt wird und der Inhalt in einen Ordner geschrieben wird. Mit .NET 4.5 wird eine ZipArchive Klasse im Namespace System.IO.Compression geliefert. Da SharePoint 2013 diese .NET Version verwendet, ist es ein leichtes einen Event Receiver für einen Fileupload zu schreiben der genau diese Aufgabe erfüllt. Die ZipDatei, die hochgeladen wird, wird entpackt und der Inhalt in einem neuen Ordner abgelegt. Zur einfacheren Lesbarkeit habe ich EventReceiver und das Entpacken der Datei in 2 Klassen getrennt codiert. Teil 1: der Eventreceiver: 1: public class ZipUploadEventReceiver : SPItemEventReceiver 2: { 3: /// <summary> 4: /// An item was added. 5: /// </summary> 6: public override void ItemAdded(SPItemEventProperties properties) 7: { 8: if (properties.ListItem == null) 9: return; 10:   11: SPFile file = properties.ListItem.File; 12: if (file == null) 13: return; 14:   15: if (file.Name.EndsWith(".zip")) 16: { 17: ZipToFolder.ConvertToFolder(file, properties.List); 18: } 19: properties.Status = SPEventReceiverStatus.CancelNoError; 20: } 21: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Es ist ein ItemAdded Receiver der für Documentbibliotheken registriert wird. Im ItemAdding ist es leider zu früh, da zu diesem Zeitpunkt die Datei noch nicht zur Verfügung steht. Daher wurde der Weg gewählt, zunächst den Upload abzuwarten und nach dem Entpacken die Datei zu löschen. Diese Vorgangsweise hat nebenbei den positiven Effekt, dass der Eventreceiver weiter asynchron ablaufen kann. in Zeile 17 wird der zweite Teil aufgerufen. Hier wird das SPFile und das SPList-Objekt übergeben. 1: class ZipToFolder 2: { 3: public static void ConvertToFolder(SPFile zipfile, SPList DocBib) 4: { 5: string folderName = zipfile.Name.Substring(0, zipfile.Name.Length - 4); 6:   7: Stream str = zipfile.OpenBinaryStream(); 8: ZipArchive arch = new ZipArchive(str); 9: foreach (ZipArchiveEntry e in arch.Entries) 10: { 11: 12: string targetFolderUrl = folderName + "/" + e.FullName; 13: string[] folders = targetFolderUrl.Split('/'); 14: SPFolder lastFolder = DocBib.RootFolder; 15: for (int i = 0; i < folders.Count()-1; i++) 16: { 17: lastFolder = lastFolder.SubFolders.Add(folders[i]); 18: } 19:   20: string fileUrl = DocBib.RootFolder.ServerRelativeUrl +"/"+ folderName + "/" + e.FullName; 21:   22: SPFile newFile = DocBib.RootFolder.Files.Add(fileUrl, e.Open()); 23: newFile.Update(); 24: } 25: zipfile.Delete(); 26: } 27: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Der Dateiname der ZIP Datei soll der Ordnername werden. in Zeile 7 wird die Datei als Binärstream geöffnet und in Zeile 8 eine Instanz der ZipArchive Klasse von dem Stream gebildet. Das Foreach durch die einzelnen Entrys ermöglicht den Zugriff auf jedes Element im ZipFile. Da im ZipEntry jeweils der gesamte Pfad zum Element liegt kann so der Ziel Pfad (die URL der Datei) aufgebaut werden. Leider muss die Ordnerstruktur in SharePoint bereits angelegt sein, bevor die Datei hochgeladen werden kann. Daher wird in den Zeilen 15-18 die Struktur angelegt. In 22 wird die Datei in SharePoint hochgeladen. Die Add-Methode erwartet die URL der Datei und den Dateiinhalt als Stream welcher von der Open() Methode des Entries geliefert wird. Zuletzt wird in 25 noch die bereits entpackte Datei gelöscht.

Social Features auf der SharePoint 2013 MySite deaktivieren

SharePoint bietet im Bereich der Social Media Features viele Neuerungen wie News Feeds, Activities und Following. Falls diese Features aber z.B. auf Grund unternehmenspolitischer Entscheidungen nicht erwünscht sind, gilt es, diese zu deaktivieren. Dazu sind eine Reihe von Schritten durchzuführen, um sämtliche Social Features abzuschalten bzw. auszublenden. Wenn nicht alle Features verschwinden sollen, steht es durchaus frei, einige der folgenden Schritte einfach auszulassen. Zunächst generelle Möglichkeiten, die die Zentraladministration bietet: Social Tags and Note Board Ribbon Controls Dieser Bereich lässt sich ganz einfach durch das Deaktivieren eines Farm-Features abschalten. Dazu in der Zentraladministration –> Systemsetting –> Manage Farm Features –> das Feature “Social Tags and Note Board Ribbon Controls” deaktivieren. Folgen von Personen/Dokumenten/Websites limitieren Unter Zentraladministration –> Service Applications –> Manage Service Applications –> User Profile Service Application –> My Site Settings –> Manage Following lässt sich das Folgen von Personen, Dokumenten und Websites beschränken bzw. ganz unterbinden, indem die maximal zulässigen Werte jeweils auf 0 gesetzt werden. Im Folgenden sei die eigene MySite zu betrachten: http://server/my Hier sind Änderungen an verschiedenen Stellen notwendig: Navigationsleiste, bestimmte SitePages sowie Masterpage. Diese Änderungen sind vom SiteOwner der Websitesammlung (MySite Host) vorzunehmen. Navigationsleiste In den Site Settings –> Look&Feel –> Quick launch den Punkt “NewsFeed” löschen. Newsfeed-, Followed Counts- und Trending #tags-Bereich der default.aspx Die default.aspx ist die Startseite der MySite. Im Bearbeitungsmodus dieser page können zwei WebParts ausgeblendet oder gelöscht werden: “Newsfeed”, “Followed Counts” und “Trending #tags”. Alternativ können diese auch in den WebPart-Eigenschaften im Bereich Layout auf hidden gesetzt werden.    Activities-Bereich der person.aspx Der Activities-Bereich entspricht ebenfalls einem WebPart, welcher entfernt werden kann. Außerdem sei die MySite aus Besuchersicht zu betrachten: http://server/my/personal/user Hier fällt neben dem “follow this person”-Button ein “People”-Hyperlink in der Quick launch auf. Beide Links können durch Modifikation der Masterpage des MySite Hosts (mysite15.master) z.B. via SharePoint Designer ausgeblendet werden. “follow this person”-Link deaktivieren Auch wenn das Folgen von Personen bereits in der Zentraladministration auf 0 User beschränkt wurde, bleibt der Button dennoch sichtbar. Zum Ausblenden reicht ein simples css Statement, welches im Head-Bereich einzufügen ist: <style type="text/css"> #ms-profile-followLinkDiv {display:none} </style> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } “People”-Link deaktivieren Dieser Hyperlink lässt sich ebenfalls nur durch Modifikation innerhalb der masterpage deaktivieren. Zunächst muss eine jQuery-Referenz im Head-Bereich gesetzt <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> und anschließend der Navigationspunkt “People” durch Einfügen der folgenden Funktion innerhalb des “SharePoint:ScriptBlock”-Tags ausgeblendet werden: jQuery(document).ready(function() { jQuery(".ms-core-listMenu-item:contains('People')").parent().hide(); }); Der “People”-Link ruft die Page MyPeople.aspx auf. Wenn auch der Inhalt dieser Page entfernt werden soll, geschieht dies analog zu den Beispielen aus person.aspx und default.aspx – durch das Entfernen bzw. Ausblenden des entsprechenden WebParts. Schulssendlich wird sowohl das Layout der eigenen als auch der den Besuchern angezeigten MySite durchaus entschlankter dargestellt, als noch zu Beginn. Eigene Ansicht (http://server/my) Ansicht für Besucher (http://server/my/personal/user)

SharePoint 2013- Änderungen am Workflow sind unwirksam

Bei der Programmierung von SharePoint 2013 Workflows fällt schnell auf dass die WOrkflow gecached werden. Änderungen am Workflow werden von Visual Studio zwar ohne Fehlermeldung deployed aber beim Testlauf wird noch der zuletzt ausgelieferte Code verwendet. Das hat den unangenehmen Effekt, dass die letzten Änderung am Programmcode doch nicht das erwünschte Verhalten bringen, man weiter die Lösung des Problems sucht. In Wahrheit hat man die Lösung bereits gefunden, nur leider wird sie von der Workflow-Engine nicht verwendet. Abhilfe schafft wenn man den Prozess “vssphost5.exe” stoppt. Dieser ist für das Zwischenspeichern des Workflowcodes verantwortlich. Um diesen Prozess bei jeden Deploymentvorgang automatisch durchzuführen, kann folgender Aufruf bei der  “Pre-Deployment-Comannd” Zeile eingetragen werden: if "%ERRORLEVEL%"=="0" taskkill /f /im vssphost5.exe exit 0 zu finden ist das in den Settings des Projektes auf der Seite “SharePoint”:

Linked Data Source für aggregierte Datenansicht

Damit sämtliche Elemente aus verschiedenen Listen in einer einzigen Datenansicht zusammen gefasst (aggregiert/merged) dargestellt werden können, wird die sog. verknüpfte Datenansicht (Linked Data Source) benötigt.  Im folgenden Beispiel werden die Dokumente aus drei verschiedenen Bibliotheken in einer gemeinsamen Ansicht angezeigt. Als erstes ist im Navigationsbereich im Punkt “Data Sources” eine neue Linked Data Source anzulegen und im Anschluss zu konfigurieren. Hierfür müssen die zu verbindenen Datenquellen - im Bsp. drei verschiedene Bibliotheken - ausgewählt werden. Im nächsten Fenster ist die Art der Verbindung zu wählen. Die erste Auswahlmöglichkeit “Merge” dient der Zusammenfassung mehrerer Inhalte in einer Ansicht mit nur einer Hierarchie-Ebene. Der zweite Punkt “Join” ermöglicht das Verknüpfen von Elementen mit Hilfe einer Unteransicht. So lässt sich bspw. eine 1:n-Beziehung darstellen. In diesem Beispiel sollen die Elemente aggregiert bzw. zusammengefasst werden, darum ist “Merge” auszuwählen.   Nach dem Fertigstellen der Auswahl sind im nächsten Dialog im Bereich “Source” die zusammenzufassenden Datenquellen zu sehen. Im Reiter “General” ist anschließend, falls noch nicht geschehen, der Name der Datenansicht zu vergeben.   Die neu erstellte Datenquelle kann verwendet werden, indem sie z.B. auf einer neuen WebPart-Page im erweiterten Bearbeitungsmodus als Datenansicht hinzugefügt wird. Dazu muss der Cursor direkt zwischen die beiden unteren ZoneTemplate-Tags der WebPartPage platziert werden.   Nun kann die zusammengefasste Datenansicht über das Ribbon –> Insert –> Data View –> Linked Sources hinzugefügt werden. Die Datenquelle wurde nun hinzugefügt und kann bearbeitet werden. So können bspw. die standardmäßig angezeigten Spalten (Name, Url Path, Modified by, Modified) angepasst und durch weitere Felder ergänzt werden. Wenn die Page zu diesem Zeitpunkt gespeichert und in der Vorschau betrachtet wird, fällt auf, dass der Wert “Modified by” nicht korrekt interpretiert sondern als plain html dargestellt wird. Um dies zu ändern, muss die entsprechende Spalte erneut als “Label” eingefügt, und somit der bisherige Wert <xsl:value-of select="@Editor"/> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }ersetzt werden. Dies erfolgt im Data Source Explorer durch einen Rechtsklick auf die entsprechende Spalte –> Insert as Label.  Da häufig noch der Hyperlink zum Dokument angezeigt werden soll, soll im Bsp. der Feldwert “Url Path” als Hyperlink zum Dokument formatiert werden. Dazu ist der Standardwert der XSL-Darstellung <xsl:value-of select="@FileRef"/> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } in einen Hyperlink umzuwandeln <a href="{@FileRef}"><xsl:value-of select="@FileRef"/></a> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Nun kann die Page gespeichert und erneut in der Vorschau betrachtet werden.

Excel Power Query und Power Map– neue BI Komponenten

Microsoft setzt immer mehr auf “Self Service BI” und bringt laufend neue Komponenten. Zwei neue Werkzeuge sind Power Query und Power Map. Power Query vereinfacht die Abfrage und Aufbereitung von externen Daten und Power Map ermöglicht die Darstellung von Diagrammen auf einer Weltkugel. PowerQuery PowerQuery ist ein Add In für Excel 2013 oder Excel 2010. (Download) Nach der Installation, die .NET 3.5 benötigt wird das Excel-Ribbon um die Karte “Power Query” erweitert.   Von hier aus, hat der Benutzer die Möglichkeit verschiedene externe Datequellen einzubinden. Neben den üblichen relationalen Datenquellen wie SQL Server, SQL Azure, Postgress, DB2, usw… sind nun auch Hadoop oder die MS Variante von Hadoop, HDInsight als Datenquellen möglich. Auch Facebook steht als Datenquelle zur Verfügung. Besondern spannend ist die Datenquelle “From Web”. Hier ist eine URL anzugeben und Power Query analysiert den HTML-Response auf Datentabellen und bietet einen Zugriff auf diese Daten. Unter http://en.wikipedia.org/wiki/List_of_countries_by_number_of_Internet_users finden wir eine Liste der Internet-User pro Land. Diese Daten möchte ich in einem GeoFlow Diagramm oder genauer einer “Power Map” darstellen. Diese Liste: wird zu folgendem Diagramm umgewandelt:   Nach dem “From Web” als Datenquelle ausgewählt wurde, muss der Link zu der Seite eingetragen werden. Danach öffnet sich der “Query Editor”. Hier werden die gefunden HTML Tabellen angezeigt und der Benutzer kann die richtige auswählen. Nach der Auswahl der Tabelle werden bereits die gefunden Daten angezeigt. In diesem “Query Editor” werden nun einzelne Schritte zur Datenmanipulation definiert in Form einer Formelsprache gespeichert, sodass später diese Schritte automatisiert von Power Query bei einer Aktualisierung der Daten ausgeführt werden können. Für das Diagramm sind nur die Spalten Country und Internet Users wichtig. Die restlichen Spalten müssen nicht behandelt werden. Rechtsklick auf einen Spalten-Header bringt ein Kontextmenu zum Vorschein um Spalten auszublenden, den Datentyp zu verändern und ähnliche Datenmanipulationen. Als nächstes ist der Datentyp der Spalte Internet Users zu ändern. aus dem Text muss eine Zahl ermittelt werden. Leider stehen im Text “,”. Diese müssen zunächst ersetzt werden. Hierzu wird aus dem Kontextmenü der Spalte “Internet Users” die Funktion “Replace Values…” verwendet. Im Query Editor Fenster kann der aktuelle Zustand der Daten jederzeit eingesehen werden. Im Rechten Bereich werden auch jeweils die bisher definierten Schritte angezeigt und pro ausgewähltem Schritt kann die Formel zur Datenänderung betrachtet werden. Wir stehen derzeit bei diesem Bild: Die Formelsprache ist fast selbsterklärend: Nächster Schritt ist die Datentyp-Umwandlung die analog zur Replace Funktion als ein weiterer Schritt eingetragen wird. Bevor wir die Daten richtig verwenden können, muss noch eine fehlerhafte Zeile gefiltert werden. Der Spaltentitel wurde auch als Datenzeile angezeigt. Die Filterung geschieht über den Spaltenkopf analog zur Filterfunktion in Excel und führt zu folgendem Schritt: Abschliessend wird im QUery Editor, der Abfrage ein passendern Titel gegeben. Links ober wird “Query1” zu “InternetUsers” Mit einem Klick auf “Done” werden die Daten in Excel übernommen und als Datentabelle dargestellt. im rechten “Task Pane” von Excel besteht die Möglichkeit die Abfrage wieder zu bearbeiten.  Der Menüpunkt “Load to data model” lädt die Daten in ein Power Pivot Datenmodell und kann dann über Power Pivot weiter verwendet werden. Wir haben nun mit wenigen Klicks Daten aus einer Website in Excel übernommen. Und können diese Schritte automatisiert ablaufen lassen. Power Map Der nächste BI-Teil, Power Map, ist ebenfalls ein neues Add In für Excel. welches hier zum Download zur Verfügung steht: Download Dieses Add In erweitert in der Karte “Einfügen” das Ribbon um den Punbkt “Map” in der Gruppe “GeoFlow”. Hier ist der ursprüngliche Code Name “GeoFLow” noch nicht auf den neuen Namen “Power Map” geändert worden. Nachdem eine neue Map eingefügt wurde, wird der GeoFlow Editor gestartet. Der erste Schritt besteht darin, ein Feld für die Angabe der “Geography” auszuwählen. In unserem Fall, finden wir die Tabelle “Internet Users” vor, die wir zuvor in das Power Pivot Model geladen hatten. Aus dieser Tabelle wählen wir das Feld “Country or area” Für jedes Feld, das hier angegeben wird, kann bestimmt werden wie dieses zu interpretieren ist. Hier ist die Eigenschaft “Country” richtig, da in dem Feld der Ländername steht. Aus dieser Information wird nur mittels Bing-Dienst die genaue Position des Datenwerts auf der Landkarte ermittelt. Möglich sind hier Angaben wie City, State, Adress. Aber auch Längen- und Breitengrad kann angegeben werden. Ein Klick auf “Map It” führt zum zweiten Teil der Einstellungen. Nun müssen die Datenwerte defineirt werden. Welches Feld beinhaltet die Datenwerte zur Darstellung? und welche Darstellungsform soll verwendet werden. Es stehen “Column”, “Bubble” und “Heat Map” zur Verfügung. Soabald die Werte gesetzt wurden, steht dem Benutzer die Karte zur Verwendung zur Verfügung. Mit der Maus kann die Kugel gedreht werden und mittels Mausrad gezoomt werden. Es stehen einige Vorgefertigte Designs zur Verfügung um das Look & Feel anzupassen. Auch besteht die Möglichkeit eine “Tour” anzulegen. Eine Tour ist eine Präsentation von verschiedenen Ansichten auf die Weltkugel. So kann schon ein Flug über die Erde abgespeichert werden und bei einer Präsentation automatisch ablaufen.

Client-side Field Rendering in SharePoint 2013

Eine der großartigen Neuerungen in SharePoint 2013 ist die Möglichkeit das Erscheinungsbild einer Spalte mittels Client-seitigen Code zu ändern. Hierzu kann der HTML Code jeder einzelnen Spalte angepasst werden. Mit SharePoint 2010 war es noch notwendig den XLST Code für die gesamte Ansicht anzupassen. Jetzt reichen ein paar Zeilen Code! Mein Ziel ist es eine Status Spalte einer Liste in Form von Icons anzuzeigen. Der Kernpunkt ist ein JavaScript-Funktion in welcher ein Template für ein Feld einer  Liste angelegt und zugewiesen wird. Dieses Template enthält den HTML Code der zur Darstellung verwendet werden soll. 1: (function () { 2: var template = {}; 3: template.Templates = {}; 4: template.Templates.Fields = { 'StatusFeld': { 'View': renderIt } }; 5: SPClientTemplates.TemplateManager.RegisterTemplateOverrides(template); 6:   7: })(); .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } In Zeile 4 wird für das Listenfeld mit dem Internalname “StatusFeld” hinterlegt, dass im Falle einer Darstellung in einem View die Funktion “renderIt” aufgerufen werden soll. Anstelle der Funktion kann hier auch gleich der HTML Code stehen. Meiner Meinung nach, ist es jedoch übersichtlicher dies in eine eigene Funktion auszulagern. Anstelle der Scope Angabe für View ist es auch möglich die Felder in den Formularen zu übersteuern. DisplayForm, EditForm, NewForm sind hier gültige Einträge. Die Funktion “renderIt” bekommt als Parameter den Context und in diesem das aktuelle Element übergeben. Durch das CurrentItem ist es dem Code möglich auf den Feldwert zuzugreifen. In der Funktion wird ein HTML Tag für ein Image zusammengestellt und zurückgegeben. Die benötigten Bilder werden im Layouts Folder abgelegt. 1: function renderIt(ctx) { 2: var wert = ctx.CurrentItem['StatusFeld'].toString(); 3: var imgUrl = '/_layouts/15/images/FieldRenderingDemo/'; 4: if (wert == 'ok') 5: imgUrl += 'green.png'; 6: if (wert == 'warnung') 7: imgUrl += 'yellow.png'; 8: if (wert == 'fehler') 9: imgUrl += 'red.png'; 10:   11: return "<img src='" + imgUrl + "'/>"; 12: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Als nächstes ist eine SharePoint Liste anzulegen. Wichtig ist, dass das Feld mit dem InternalName “StatusFeld” angelegt wird: 1: <Field Name="StatusFeld" ID="{0f91f161-b8f3-46b3-8c91-8a3053d0ae50}" DisplayName="StatusFeld" Type="Choice"> 2: <CHOICES> 3: <CHOICE>ok</CHOICE> 4: <CHOICE>warnung</CHOICE> 5: <CHOICE>fehler</CHOICE> 6: </CHOICES> 7: </Field> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } In der Schema.xml Datei des Listtemplates werden auch die Views definiert. Wir müssen nun, dem View die zuvor erstellt Javascript Datei zuweisen. Bei genauer Betrachtung der View Definition finden wir das Attribute “JSLink” welches default auf “clienttemplates.js” eingestellt ist. Hier ist unsere JS Datei anzugeben. Achtung, dass auch wirklich der DefaultView angepasst wird.   Die Scriptdatei muß in SharePoint verfügbar sein. Daher liefere ich diese mittels eines Moduls-Elements aus. Die Elementsdatei die von Visual Studio automatisch angepasst wird sieht so aus: 1: <?xml version="1.0" encoding="utf-8"?> 2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <Module Name="ScriptDatei"> 4: <File Path="ScriptDatei\StatusDisplay.js" Url="ScriptDatei/StatusDisplay.js" /> 5: </Module> 6: </Elements> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Daraus ergibt sich der Pfad der im JSLink einzutragen ist. Wichtig ist, dass der Pfad Site-Relative sein muss. Der richtige Pfad für mein Beispiel ist somit: <JSLink>~sitecollection/ScriptDatei/StatusDisplay.js</JSLink> Wenn die Solution installiert ist,  hat die StatusDemoListe nun einen angepassten View. In diesem wird das Statusfeld nun mit eigenen Icons angezeigt.