TypeScript in SharePoint-Hosted Apps nutzen

SharePoint Hosted Apps können nur mit JavaScript entwickelt werden. Als C#-Entwickler vermisse ich in JavaScript jedoch die Annehmlichkeiten einer typisierten-Sprache. Insbesondere vermisse ich die Intellisense-Unterstützung des Visual Studios. Auf meiner Suche nach einer Lösung bin ich über das TypeScript-Projekt von Microsoft gestolpert. ( http://www.typescriptlang.org/) Auch wenn das TypeScript Add In für Visual Studio installiert ist, stellt man schnell fest, dass es in einer SharePoint-Hosted App nicht möglich ist eine TypeScript-Datei einzufügen. Da TypeScript aber nur ein PreCompiler ist, muss es doch irgendwie möglich sein das Visual Studio so zu konfigurieren, dass TypeScript in Apps unterstützt wird? Hier die Anleitung: 1. Schritt SharePoint App Projekt anlegen. Als Deploymenttyp “SharePoint-hosted” auswählen. 2. Schritt Zum Projekt im Ordner “Scripts” eine neue Datei hinzufügen und diese mit der Endung *.ts versehen. In meinem Beispiel nenne ich diese Datei “TypeScript1.ts” 3. Schritt In der *.csproj Datei ist eine Erweiterung notwendig um den TypeScript-Compiler in das Projekt einzubinden. Startet man direkt mit einer TypeScript-Anwendung ist der folgende Eintrag bereits im Projekt-File inkludiert. Dort habe ich auch den Code gefunden. Wichtig ist es, vor der Änderung, das VIsual Studio zu schließen, sonst wird die Projektdatei vom VS wieder überschrieben. Am Ende des .csproj Files, vor dem Closing-Tag für </Project> ist folgender Eintrag zu schreiben: <PropertyGroup Condition="'$(Configuration)' == 'Debug'"> <TypeScriptTarget>ES3</TypeScriptTarget> <TypeScriptIncludeComments>true</TypeScriptIncludeComments> <TypeScriptSourceMap>true</TypeScriptSourceMap> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)' == 'Release'"> <TypeScriptTarget>ES3</TypeScriptTarget> <TypeScriptIncludeComments>false</TypeScriptIncludeComments> <TypeScriptSourceMap>false</TypeScriptSourceMap> </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; } 4. Schritt ebenfalls in der Visual-Studio Projektdatei ist die Datei “TypeScript.ts” so zu konfigurieren, dass sie mit Typescript compiliert wird. In der Projektdatei ist folgende Zeile zu finden: <Content Include="Scripts\TypeScript1.ts" /> .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; } Diese ist auszubessern auf: <TypeScriptCompile Include="Scripts\TypeScript1.ts" /> .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; } 5. Schritt nun kann das Visual Studio wieder gestartet und das Projekt geladen werden. Das Projekt muss einmal kompiliert werden. Dadurch wird von TypeScript die Datei “TypeScript1.js” angelegt. Diese ist in das Projekt aufzunehmen. Hierfür müssen die ausgeblendeten Dateien angezeigt werden. Die nun angezeigte Datei “TypeScript1.js” kann mit dem Menü Punkt “Include in Project” in das Projekt aufgenommen werden. 6. Schritt Das Deployment der Dateien nach SharePoint muss konfiguriert werden. Derzeit wird die TypeScript Datei mittels Element.xml nach SharePoint deployed. Dies ist nicht notwendig. Der Deployment-Type der Datei muss in den Properties der Datei von “ElementFile” auf “NoDeployment” geändert werden. Für die Datei “TypeScript1.js” sollte bereits der Deployment Type “ElementFile” gesetzt sein und die Build Action sollte auf Content stehen. Dadurch wird das File in die Elements-Datei aufgenommen und mittels  SharePoint-Module nach SharePoint deployed : 7. Schritt zu guter letzt ist eine kleine Testfunktion in TypeScript hilfreich. Die durch TypeScript entstandene JavaScript Datei muss noch in der ASPX-Datei eingebunden werden und die Funktion aufgerufen werden. und am Ende: Die erfolgreiche Anzeige:

Woher kommen die Daten?

Eigentlich war es eine einfache Anforderung. Das ausgewählte Item einer Datenliste in SharePoint soll in einem weiteren WebPart auf der Seite dargestellt werden. Die beiden WebParts werden über eine WebPart-Verbindung zusammen gefügt. Eine klassische n:1 Darstellung. Aber wenn es schon so schön dargestellt wird, wollen wir auch gleich Änderungen am Item durchführen können, also ein Update auf das Item ausführen. Die Darstellung ist schnell gelöst. Das Detail-WebPart fungiert als ConnectionConsumer für ein WebPart welches IWebPartRow implementiert. In der Connection Funktion wird ein Callback auf einen eigene Funktion erstellt und an den Datenprovider übergeben. An die Callback wird ein object mit den Daten übergeben. Dieses Object ist im Falle eines simplen SharePoint Listen WebParts ein DataRowView. 1: [ConnectionConsumer("ZeileDetail")] 2: public void GetConnectionInterface(IWebPartRow providerPart) 3: { 4: RowCallback callback = new RowCallback(ReceiveRow); 5: 6: providerPart.GetRowData(callback); 7: } 8:   9: public void ReceiveRow(object row) 10: { 11: EnsureChildControls(); 12: DataRowView rv = (DataRowView)row; 13: 14: txtTextbox1.Text = rv["Title"].ToString(); 15: } .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 die Daten zurück schreiben zu können, ist ein SharePoint Item vom Typ SPItem notwendig. Über den DataRowView stehen aber leider nur die Feldinhalte zur Verfügung und kein SPItem. Aber zumindest wird die Id des Items im DataRowView übergeben. Nur leider ist im DataRowView kein Hinweis auf die Liste an die dieses WebPart vom Benutzer gebunden wurde. Woher bekomme ich die Referenz auf die Liste? Das übergebene Object in der ConnectionConsumer-Methode,  welches ein IWebPartRow implementiert, ist das Senderwebpart. Binde ich das Detailwebpart an eine SharePoint-Liste so wird hier ein WebPart vom Typ  XsltListViewWebPart übergeben. Und dieses enthält einen Verweis auf das zugrundliegende SPList Element. (Dies gilt für SharePoint 2010 genauso wie für SharePoint 2013) Die erweiterten Funktionen mit SPList Objekt und Item Id: 1: [ConnectionConsumer("ZeileDetail")] 2: public void GetConnectionInterface(IWebPartRow providerPart) 3: { 4: RowCallback callback = new RowCallback(ReceiveRow); 5: XsltListViewWebPart wp = (XsltListViewWebPart)providerPart; 6:   7: SPList liste = wp.View.ParentList; 8: ViewState["ListId"] = liste.ID.ToString(); 9:   10: providerPart.GetRowData(callback); 11: } 12:   13: public void ReceiveRow(object row) 14: { 15: EnsureChildControls(); 16: DataRowView rv = (DataRowView)row; 17:   18: int ItemId = int.Parse(rv["ID"].ToString()); 19: ViewState["ItemId"] = ItemId.ToString(); 20:   21: txtTextbox1.Text = rv["Title"].ToString(); 22: } .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; } Wie im Source-code sicher aufgefallen ist, es wird die ListID und die Item ID im Viewstate des WebParts gespeichert. Das hat mit dem Timing des Updates zu tun. Der eigentliche Code mit dem Datenupdate steht im Eventhandler des Button-Click Events. Würde das DatenUpdate erst am Ende eines PostBack-Zyklus stattfinden, wie z.B. im ReceiveRow, zeigt das ListenWebpart nicht die aktualisierten Daten sondern “hinkt” einem Postback-Zyklus hinterher. Zum Zeitpunkt des Aufrufes des Click-Events wurden der ConnectionConsumer und die Callback-Funktion aber noch nicht aufgerufen und somit sind weder ItemId noch Liste bekannt. Daher speichere ich die beiden Werte zunächst im ViewState, um im Button-Click darauf zugreifen zu können. 1: void bt_Click(object sender, EventArgs e) 2: { 3: EnsureChildControls(); 4: if (ViewState["ListId"] != null) 5: { 6: SPList liste = SPContext.Current.Web.Lists[new Guid(ViewState["ListId"].ToString())]; 7: SPItem it = liste.GetItemById(int.Parse(ViewState["ItemId"].ToString())); 8:   9: it["Title"] = txtTextbox1.Text; 10: it.Update(); 11: } 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; } Zwecks Vereinfachung des Democodes, wurden alle Prüfungen auf Null-Werte weggelassen. Im Sinne einer “sicheren” Programmierung sind diese im Produktivcode aufzunehmen.

Performance Point Services – Kein Connect zu Analysis Services

Beim Versuch mit dem Dashboard Designer eine Connection zu einem Cube in einer Analysis Services Datenbank anzulegen, kann der Server angegeben werden, jedoch die DropDown Box mit den angelegten Cubes bleibt leer. In der LOG Datei des SharePoint Server steht folgender Fehler: Unable to load custom data source provider type: Microsoft.PerformancePoint.Scorecards.DataSourceProviders.AdomdDataSourceProvider, Microsoft.PerformancePoint.Scorecards.DataSourceProviders.Standard, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c  System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.AnalysisServices.AdomdClient, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91' or one of its dependencies. The system cannot find the file specified.  File name: 'Microsoft.AnalysisServices.AdomdClient, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91'     …. Die Analysis Instanz ist die SQL 2012 Version. Jedoch verwenden die Performance Point Services von  SharePoint 2013 für den Datenbankzugriff noch immer die Version 10 der ADOMD.NET Assemblies obwohl SQL 2012 bereits die Version 11 ausliefert. Es ist notwendig die alte Version zusätzlich zu installieren. Diese ist im SQL Server 2008 R2 Feature Pack zu finden. Einen direkten Download gibt’s hier: http://go.microsoft.com/fwlink/?LinkID=188442&clcid=0x409 Ein abschließender IIS Reset behebt das Problem.

Windows Azure Workflow in SharePoint 2013–Installation

SharePoint 2013 bringt nun eine Neue Workflowengine mit. Unter anderem ist es möglich Schleifen in einem Worklfow zu verwenden und das nur unter Verwendung des SharePoint Designers. Leider wird diese neue Workflowengine nicht automatisch mit der SharePointinstallation  ausgeliefert. Die neue Engine basiert auf .NET 4 und wird mit  der Windows Azure Workflow Engine installiert. Grundsätzlich liefer Microsoft unter http://technet.microsoft.com/en-us/library/jj658588(v=office.15) eine Installationsanleitung. Allerdings meine ich, dass auf ein paar Stolpersteine vergessen wurde. Download und Setup unter http://go.microsoft.com/fwlink/?LinkID=252092  wird der Web Platform Installer 4.0 aufgerufen  und die notwendigen Setupdateien werde runtergeladen und installiert.  Nach der Installation startet der Configuration-Wizard. Hier sind vorher noch einige Vorarbeiten notwendig. Service Konto Für die Azure Workflow Engine ist eine eigenes Service Konto erforderlich. Dieses Konto muss in der Gruppe der Administratoren sein, und am SharePoint Server zur Gruppe der Admins hinzugefügt werden! In meinem Demo verwende ich das Konto: AD\WFApp Weiters benötigt das Servicekonto Rechte am SQL Server. Im neu angelegten Login werden folgende Rollen zugewiesen:   Windows Azure Workflow Configuration Wizard Auch wenn nach dem Setup der Wizard gleich startet. Achtung! die Konfiguration muss als Service Konto durchgeführt werden. Es ist notwendig, dass das AD\WFApp Konto zur lokalen Anmeldung am Windows verwendet wird und der Wizard aus dieser Session heraus gestartet wird. Ein “Run As” reicht nicht. Als WFApp kann nun der Wizard gestartet werden: Vergisst man auf diese Anmeldung läuft der Wizard fast bis zum Ende. Der letzte Schritt “Add Host to Workflow Farm” schlägt fehl. Bei genauerer Betrachtung findet man eine “Not Authorized”-Exception. Der Wizard legt unter anderem mehrere Datenbanken sowie Webanwendungen zur Verwaltung an. Zunächst muss eine neue Workflow Farm installiert werden. Verwendet man “With Custom Settings” kann jede Datenbank extra angeben werden. Mit “Using Dfeault Settings” wirds einfacher. Im Wizard ist nun der Datenbankserver anzugeben und der Service Account. Achtung: der Wizard schlägt WFApp@AD vor. Richtig ist aber die Angabe der vollständigen Domain: also bei: WFApp@AD.local Eine falsche Angabe hier für ebenfalls zu dem Fehler beim “Add Host to Workflow Farm” Bei einer simplen Demo Installation oder einer Ein-Server-Installation kann die Kommunikation über http ausgewählt werden. Https ist nicht zwingend. Hierfür ist die Checkbox zu setzen. Erfolg sieht so aus: Nun ist die Session als WFAPP nicht mehr notwendig. Weiter geht es mit dem Farm Administrator. Verbindung der SharePoint Farm mit der Workflow Farm Um SharePoint mit der Workflow Farm zu verbinden, ist ein Aufruf des Register-SPWorkflowService Commandlets in der SharePoint Management Shell notwendig. Es sind drei Parameter anzugeben: -SPSite … die Url der SharePoint Site Collection -WorkflowHostUri .. die URL der Verwaltungsanwendung der Workflowfarm. Für http Zugriff wird diese default mit dem Port 12291 angelegt. (https: 12290) -AllowOAuthHttp …. dass http verwendet wird.   Testen der Installation Wer bis hier her gekommen ist, hat man es fast geschafft.  Mit dem SharePoint Designer 2013 können nun Workflows für 2013 erstellt werden. Als Plattform Type sollte bereits 2013 ausgewählt sein:   Sollte die Fehlermeldung “The list of workflow actions on the server references an assembly that does not exist.”  erscheinen, nicht abschrecken lassen. Im Internet sind einige Einträge zu finden, die besagen, dass der Hauptspeicher nicht ausreichend ist. und die Hardwareanforderung mit 24 GB auch zu beachten ist. Meine virtuelle Maschine hat 16 GB und laut Task Manager sind genügend GB frei.   Abhilfe schafft ein Reboot der Server Maschine. Have fun with SharePoint 2013 Workflows.

ASMX Service unter Windows 8 Store APP nutzen

Die guten alten ASP.net Web Services sollten eigentlich schon seit .net knapp 10 Jahren durch WCF abgelöst werden. Aber ASMX findet sich auch heute noch, teilweise auch neu implementiert in vielen Software Projekten. Es ist einfach zu programmieren, zu hosten und zu troubleshooten. Selbst SharePoint bietet eine Reihe von Schnittstellen als ASP.NET Web Service an. Als Basis dient folgender sehr einfacher ASMX Service, der in einem eigene Projekt innerhalb der Windows 8 Store APP Visual Studio 2012 Solution angelegt wird (vormals METRO). Imports System.Web.Services Imports System.Web.Services.Protocols Imports System.ComponentModel System.Web.Services.WebService(Namespace:="http://tempuri.org/")> _ <System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _ <ToolboxItem(False)> _ Public Class WebService1 Inherits System.Web.Services.WebService <WebMethod()> _ Public Function GetKunden() As List(Of kunde) Dim l As New List(Of kunde) l.Add(New kunde With {.ID = "1", .isMale = True, .FamName = "Mayer", .Vorname = "Klaus"}) l.Add(New kunde With {.ID = "9", .isMale = False, .FamName = "Becker", .Vorname = "Verona"}) l.Add(New kunde With {.ID = "13", .isMale = True, .FamName = "Weber", .Vorname = "Gerhard"}) l.Add(New kunde With {.ID = "31", .isMale = True, .FamName = "Müller", .Vorname = "Thorsten"}) Return l End Function End Class Public Class kunde Property ID As String Property Vorname As String Property FamName As String Property isMale As Boolean End Class .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 diesen Service zu nutzen erzeugen die meisten im Visual Studio Projekt per Add Service Reference und URL des Services die Proxy Klassen im Projekt. In einer Winforms Anwendung (nach wie vor) kann man das mehr oder minder direkt über den Zusatzdialog “Advanced” tun. Diese Option fehlt bei Windows 8 Apps, schlicht weil es dort kein .net 2.0 mehr gibt. Auf den ersten Blick ist das auch nicht nötig, weil man das auch direkt ganz gut hinbekommt. Allerdings muss im Gegensatz zu Windows Forms in der WinRT, die Kommunikation Asynchron abgehandelt werden. Das erzeugt ganz neue Problemstellungen für den .NET Code bzw. der VB.NET Source kann nicht direkt wieder verwendet werden. Dim svc As New ServiceReference1.WebService1SoapClient Private Async Sub MainPage_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded Dim resp = Await svc.GetKundenAsync() liste1.ItemsSource = resp.Body.GetKundenResult End Sub .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 nötige XAML Code mit einem Listeview Steuerelement in der Page <ListView x:Name="liste1"> <ListView.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding VorName}" Padding="0,0,5,0"/> <TextBlock Text="{Binding FamName}" FontWeight="Bold" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> .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; } Letztendlich die Ausgabe der Windows 8 APP Noch ein Paar Hintergrund Infos. Da Windows 8 .NET 4.5 verwendet, kommen eigentlich WCF Proxys zum Einsatz. Microsoft hat allerdings im WCF Bereich ob des konfigurations Desasters auf die App.Config verzichtet. Statt “Contract First” lautet nun die Devise Konvention über Konfiguration. Also wo findet man nun die nötigen Parameter. Im Code- der reference.vb. Den kann man natürlich ändern, muss aber in Kauf nehmen, das wenn die Service Reference aktualisiert wird, die Änderungen weg sind.

Sharepoint 2010 und Windows 8 Store App

Ich weis noch immer nicht, wie die Windows METRO Anwendungen nun heißen. Kein gutes Zeichen fürs Marketing. In Visual Studio heißt das Template Windows Store, was nicht ganz richtig ist, weil man wohl auch Desktop Anwendungen … lassen wir das. Im Rahmen einer Machbarkeitsstudie werden Interfaces von SharePoint evaluiert um sie in WinRT zu verwenden. Aktuell wird auf die SharePoint REST Services programmiert. Weil ich faul bin über einen Proxy in Visual Studio 2012. Entsprechend der Odata Query Language wartet dort ein WCF Service, der sich an den ADO.NET Dataservices anlehnt. URL ist https://server/_vti_bin/ListData.svc/ In meinem Fall musste ich drei mal meine Domain Account eingeben um die Referenz erstellt zu bekommen. Mit Hilfe des Proxy lässt sich dann der Data Service instanziieren. Der Name IntranetDataContext ergibt sich in diesem Fall aus dem verwendeten SharePoint. Dim dc As IntranetDataContext = New IntranetDataContext(New Uri("https://server/_vti_bin/ListData.svc")) .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; } Wenn die Namen unklar sind einfach im Class View von Visual Studio nach DataContext suchen. Als nächstes geht es um die Authentifizierung am Sharepoint Dim cr = New System.Net.NetworkCredential("usr", "pwd", "domain") dc.Credentials = cr .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; } Dann wird der Service aufgerufen. Weil dieser den ASYNC Pattern aus .NET 4.5 nicht implementiert auf die gute alte Beginexecute und Endexcute Methode um asynchron zu bleiben. Die Liste heist Mitarbeiter und kann auch direkt im Browser per https://server/_vti_bin/ListData.svc/Mitarbeiter so abgerufen werden. dc.BeginExecute(Of MitarbeiterItem)(New Uri("Mitarbeiter", UriKind.Relative), AddressOf fertig, dc) .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; } Am Schluss dann noch die Rückrufmethode die den Aufruf abschließt und die Daten erhält aber dummerweise in einem anderen Thread aufgerufen wird. Also muss man noch den Dispatcher bemühen. Public Async Function fertig(ByVal result As IAsyncResult) As Task Dim data = dc.EndExecute(Of ServiceReference1.MitarbeiterItem)(result).ToList Await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, Sub() listbox1.ItemsSource = data End Sub) End Function .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; }

Unit Test für SharePoint Development–Fakes Framework

In Teil 1 habe ich das Grundprinzip des Fakes Frakework dargestellt. In diesem Teil gehe ich nun auf Tests für SharePoint ein. Zunächst muss ein Testprojekt hinzugefügt werden und die Shim Klassen für die SharePoint.dll erstellt werden. Dieser Vorgang kann durchaus ein paar Minuten dauern. ein einfaches Demo angenommen wir möchten folgenden Code testen: 1: public void UpdateTitle(string neuerTitel) 2: { 3: SPWeb web = SPContext.Current.Web; 4: web.Title = neuerTitel ; 5: web.Update(); 6: } .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; } Diese 3 Zeilen enthalten keinen komplizierten Code. Aber es verstecken sich einige Aufrufe von SharePoint Klassen. Diese müssen mit dem Fakes Framework “simuliert” werden. Wir haben den Zugriff auf den Context, oder genauer gesagt auf das Web des Contexts und dann einen schreibenden Zugriff auf die Title Eigenschaft des Webs. Ein Unittest muss nun prüfen, dass dem Title-Property ein neuer Wert zugewiesen wurde. Die Testmethode enthält folgenden Code:   1: [TestMethod] 2: public void SimpleUT_Backup() 3: { 4: using (ShimsContext.Create()) 5: { 6: string titel = ""; 7:   8: ShimSPContext.CurrentGet = () => 9: { 10: return new ShimSPContext() 11: { 12: WebGet = () => 13: { 14: return new ShimSPWeb() 15: { 16: TitleSetString = (wert) => 17: { 18: titel = wert; 19: } 20: }; 21: } 22: }; 23: }; 24:   25: SimpleDemos demo = new SimpleDemos(); 26: demo.UpdateTitle("NeuerTitel"); 27:   28: Assert.AreEqual("NeuerTitel", titel); 29:   30: } 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 Zeile 8 wird die Get-Methode des SPContext.Current mit einem neuen Objekt (ShimSPContext) bestückt und in Zeile 12 wird ein ShimObjekt für das Web-Objekt gebildet. Die Set-Methode des Web Objektes wird in Zeile 16 mit einer Methode hinterlegt. In dieser wird der übergebene Titel-Wert in eine Variable gespeichert. (Zeile 18) Nach diesen Vorbereitungsarbeiten (Arrange) wird die zu testende Methode aufgerufen (Action) und in Zeile 28 wird geprüft, dass der Wert auch wirklich gesetzt wurde (Assert). Dieses simple Beispiel zeigt, dass für alle verwendeten Methoden und Objekte von SharePoint Shim-Klassen mit deren Methoden gebildet werden müssen. ein leicht “komplizierter” Code in SharePoint Meist sind die Code-Teile in SharePoint nicht so simpel wie im ersten Beispiel. Bei längerem Code kann die Anzahl an notwendigen Shim-Objekten leicht eine Anzahl erreichen, wo man mit obiger Methode nicht weiterkommt. Im Fakes-Framework gab es eine Sammlung an Behavior-Klassen, die bereits die wichtigsten Funktionen von SharePoint in Fake-Klassen darstellten. Als Unit-Tester musst man nicht bei 0 beginnen. Diese Behavior-Klassen vermisse ich noch. Im zweiten Beispiel möchte ich einen EventReceiver testen: 1: public override void EmailReceived(SPList list, SPEmailMessage emailMessage, String receiverData) 2: { 3: if (emailMessage.PlainTextBody.Contains("Anmeldung")) 4: { 5: SPList aufgabenListe = list.ParentWeb.Lists["Aufgaben"]; 6: SPListItem neueAufgabe = aufgabenListe.AddItem(); 7: neueAufgabe["Title"] = "Anmeldung prüfen"; 8: neueAufgabe.Update(); 9: } 10:   11: base.EmailReceived(list, emailMessage, receiverData); 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; } In diesem Eventreceiver, soll bei jeden Eingang eines Emails, das im Betreff “Anmeldung” beinhaltet, eine Aufgabe anlegt werden. Um dies zu testen, benötigen wir eine Listen-Klasse, die überprüfen kann ob ein Item angelegt wurde. Da dies im SharePoint Development öfter vorkommen kann, habe ich begonnen diese häufig auftretenden Aufgaben in eigenen Mock-Klassen zu implementieren. Und erreiche so eine höhere Wiederverwendbarkeit meiner Tests. Um eine Liste testen zu können wird ein SPListItemMock und ein SPListMock benötigt. Das List Item ist wie folgt implementiert: 1: class SPListItemMock 2: { 3: public Dictionary<string, string> FeldWerte = new Dictionary<string, string>(); 4: public ShimSPListItem theItem; 5: public bool UpdateCalled = false; 6:   7: public SPListItemMock() 8: { 9: theItem = new ShimSPListItem(); 10: theItem.ItemGetString = (feld) => 11: { 12: return FeldWerte[feld]; 13: }; 14: theItem.ItemSetStringObject = (feld, wert) => 15: { 16: FeldWerte.Add(feld, wert.ToString()); 17: }; 18: theItem.Update = () => 19: { 20: UpdateCalled = true; 21: }; 22: } 23:   24: public static implicit operator SPListItem(SPListItemMock m) 25: { 26: return m.theItem; 27: } 28: } .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 Klasse beinhaltet ein Dictionary um die FeldWerte des Items zu speichern. Es ist Public definiert um in einer Testmethode auf die gespeicherten Werte zugreifen zu können oder vor einem Test entsprechend zu setzen. Außerdem wird in der Klasse das Shim-Objekt für ein SPListItem angelegt und die Getter und Setter implementiert. Die Updatemethode wurde ebenfalls implementiert. Wenn diese aufgerufen wird, so wird der Aufruf in einer Bool-Variable hinterlegt. Zu guter Letzt wird noch im Cast-Operator implementiert. Das erleichtert in der Testmethode den Zugriff auf das ShimObjekt. Die SPListMock Klasse ist analog aufgebaut: 1: class SPListMock 2: { 3: public ShimSPList theList; 4: public List<SPListItemMock> items = new List<SPListItemMock>(); 5: public SPWebMock ParentWeb; 6:   7: public SPListMock() 8: { 9: theList = new ShimSPList(); 10: theList.AddItem = () => 11: { 12: var addedItem = new SPListItemMock(); 13: items.Add(addedItem); 14: return addedItem; 15: }; 16: 17: theList.ParentWebGet = () => ParentWeb; 18:   19: } 20:   21: public static implicit operator SPList(SPListMock m) 22: { 23: return m.theList; 24: } 25: } .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 zur Testmethode: Diese ist im Vergleich zum Einstiegsbeispiel recht simpel und übersichtlich aufgebaut: 1: [TestMethod] 2: public void EventReceiver_Test_mitMocks() 3: { 4: using (ShimsContext.Create()) 5: { 6: ShimSPEmailMessage msg = new ShimSPEmailMessage() 7: { 8: PlainTextBodyGet = () => "Anmeldung zur SharePoint Konferenz in Wien" 9: }; 10:   11: SPWebMock web = new SPWebMock(); 12: SPListMock liste = new SPListMock(); 13: web.Listen.Add("Aufgaben", liste); 14: liste.ParentWeb = web; 15:   16: AnmeldungsEmailEventReceiver target = new AnmeldungsEmailEventReceiver(); 17: target.EmailReceived(liste, msg, ""); 18:   19: Assert.AreEqual("Anmeldung prüfen", liste.items[0].FeldWerte["Title"]); 20: Assert.IsTrue(liste.items[0].UpdateCalled); 21: } 22:   23:   24:   25: } .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; } Durch die Mockklassen wird die Verwendung der Shim-Klassen einfacher. In Zeile 11 beginnen wir die notwendigen Klassen vorzubereiten. Ein Web und eine Listen-Klasse. In Zeile 16 wird der zu testende Code ausgeführt. Und ab Zeile 19 wird überprüft, dass in der Liste ein entsprechender Eintrag geschrieben wurden. Fazit Das Fakes-Framework bietet die Möglichkeit jedes beliebige .NET Assembly zu “faken” und die Methoden durch eigene Methoden auszutauschen. Es kann auch auf die SharePoint-Assemblies angewandt werden. Allerdings ist einiges an wiederholenden Code zu erstellen. Hier hilft hoffentlich die Idee eigene Mock-Klassen zu erstellen weiter. Diese Klassen sind nur einmal zu erstellen und können bei jedem Test wiederverwendet werden.

Unit Testing von SharePoint Solutions– Das Fakes Framework macht’s möglich. Teil 1: Fakes Framework

Wer schon mal versucht hat, einen Unit Test für SharePoint zu schreiben, wird festgestellt haben: Die Abhängigkeit zum SharePoint Server bekommt man nicht weg, was aber eines der Grundprinzipien beim Unit Test ist. Bisher war es notwendig auf Drittanbieter Tools wie z.B. TypeMock auszuweichen. Als Alternative bot Microsoft Research im Projekt “Pex & Moles” mit Moles eine Mockinglösung (Moles). Das erfreuliche: Seit Visual Studio 2012 ist Moles nun Bestandteil des Visual Studios. Unter dem Namen “Fakes Framework” wurde Moles nun in den regulären Support aufgenommen. Leider ist es nur in der Ultimate Edition enthalten. Das Grundprinzip Ein simpler Aufruf von System.DateTime.Now bereitet einem Unit Test Probleme. Das erwartete Ergebnis wird wohl im nächsten Jahr ein anderes sein, als zum Zeitpunkt der Testerstellung. Die beste Lösung ist es, im Design der Anwendung bereits Vorkehrungen für einen Test zu berücksichtigen. Manchmal steht man jedoch vor dem Problem, dass das Design nicht mehr geändert werden kann und dennoch Tests erstellt werden müssen. SharePoint ist so ein Fall . Hier wurde auf Testbarkeit nicht viel Wert gelegt. Das Fakes Framework erstellt sogenannte Shim-Klassen in denen jeder Funktionsaufruf durch ein Delegate ersetzt wird. Im Fall eines Properties wird sowohl für Get als auch für Set ein Delegate angelegt. Ein einfaches Demo Für den ersten Einstieg empfiehlt sich eine WinForms Anwendung in der in einer Message Box das aktuelle Datum ausgegeben wird. Der Code bedarf keiner Erklärung private void button1_Click(object sender, EventArgs e) { ZeigeDatum(); }     public void ZeigeDatum() { MessageBox.Show(DateTime.Now.ToString()); } .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; } Shim-Klassen können nur innerhalb eines UnitTest Projekts verwendet werden. Innerhalb eines normalen Projektes erhält man die ShimNotSupportedException. Also legen wir zum WinForms Projekt noch ein UnitTest Projekt an und referenzieren auf das WinForms Projekt. Um Fakes zu aktivieren, bzw die Shim Klassen anzulegen wählt man im Visual Studio im Solution Explorer jenes Assembly aus, von welchem die Shim-Klassen erstellt werden sollen. In unserem Demo ist es “System.dll”  Im Kontextmenü wählt man den Eintrag “Add Fakes Assembly”. Danach ist Geduld angesagt. Im Fall der SharePoint.dll dauert das leicht mehrere Minuten. Ob das Visual Studio fertig ist, erkennt man daran, dass ein Kompilieren des Projektes wieder möglich ist. Solange die Shim-Klassen erstellt werden, kann nicht kompiliert werden. Danach sind weitere Referenzen zu finden. System.4.0.0.0.Fakes und mscorlib.4.0.0.0.Fakes wurden angelegt. Auch gibt es nun in der Solution einen Ordner Fakes. In diesem liegen XML Dateien über die die Fakes-Erstellung gesteuert werden kann. Z.B.. kann definiert werden für welche Klassen eine Shim-Klasse angelegt werden soll, um nicht unnötiger Weise für alle Klassen im Assembly eine Shim-Klasse anzulegen. Die Testmethode   [TestMethod] public void TestMethod1() { Form1 f = new Form1();   using (ShimsContext.Create()) { ShimDateTime.NowGet = () => new DateTime(2000, 1, 1); f.ZeigeDatum(); } } .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; } ShimsContext.Create()stellt die ShimKlassen bereit. Es empfiehlt sich diese innerhalb eines using aufzurufen, sodass am Ende das Dispose aufgerufen wird. Innerhalb des erstellten Context können wir nun für jede Methode unseren eigenen Code hinterlegen. Im Fall des Now- Properties, wurde die Methode NowGet angelegt und für diese wird ein neuer Rückgabewert definiert. Die MessageBox zeigt nun den 1.1.2000 Mit dem Fakes Framework ist es also möglich für jedes Property oder jede Methode einen eigenen Code zu hinterlegen und damit im Unit Testing zu arbeiten. Teil 2 beschäftigt sich mit SharePoint und folgt nächste Woche.

Einmal Links (Disposable Links) für SharePoint Dokumentbibliotheken

SharePoint bietet mit Dokumentbibliotheken eine perfekte Dateiverwaltung. Aber wie können die Dateien über Einmal Links externen, nicht an SharePoint angemeldeten Usern zur Verfügung gestellt werden. Die Anforderung ist, dass externe User einen Link zu einer Datei in einer Dokumentbibliothek erhalten. Sobald dieser Link verwendet wurde, darf er nicht mehr zur Verfügung stehen. Die Lösung setzt sich aus zwei Teilbereichen zusammen. Wir benötigen eine Verwaltung der ausgegeben Links und zweitens die Möglichkeit als externer User Zugriff, nur auf die eine Datei zu erhalten. Teil 1 des Problems kann mit einer simplen SharePoint Liste gelöst werden. Ein Feld für die URL zum Dokument und ein Feld mit dem Ticket.  Mit Hilfe einer Application Page und einer Custom Action, die auf diese Application Page zeigt, wird die URL in eine SharePoint Liste eingetragen. Zugleich wird ein Ticket (in diesem Fall ein GUID) erzeugt und mit abgespeichert. Auf Teil 1 gehe ich nicht näher ein. Die gesamte Solution (inkl. SourceCode) gibt’s zum Download. Link zum Download Teil 2 ist spannender: Zunächst ist zu klären wie eine Datei an einen externen Benutzer gesendet werden kann ohne, dass der Benutzer die Dokumentbibliothek verwenden muss. In einem Page_Load Event kann der Http-Response umgeschrieben werden. Die Response.BinaryWrite() Methode schreibt ein Byte-Array in den Response. Mit den richtigen Page-Header und ContentType ergibt das für den Browser einen Datei-Download. In diesem Beispiel wurde mit einer PowerPoint Datei gearbeitet. Daher “application/mspowerpoint” als ContentType. (siehe Wikipedia) 1: protected void Page_Load(object sender, EventArgs e) 2: { 3: string docUrl = getDocUrl(ticketID, web); // die zur TicketID gehörende Dokument-URL suchen 4: SPFile spFile = web.GetFile(docUrl); 5:   6: Response.Clear(); 7: Response.AddHeader("Content-Disposition", "attachment; filename=" + spFile.Name); 8: Response.AddHeader("Content-Length", spFile.Length.ToString()); 9: Response.ContentType = "application/mspowerpoint"; 10:   11: Response.BinaryWrite(spFile.OpenBinary()); 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; } Um den Benutzer einen Links anbieten zu können, legen wir eine Application-Page an. Als Parameter wird das Ticket mitübergeben. Das Problem ist, dass normale Application-Pages nicht von einem externen, nicht angemeldeten User abgefragt werden können. Auch wenn Anonymous-access aktiviert ist. Visual Studio legt Application Pages als abgeleitete Klassen von LayoutsPageBase an. Das müssen wir ändern. Nur von UnsecuredLayoutsPageBase abgeleitete Seiten können von anonymen Benutzern abgefragt werden. Zusätzlich muss das Property AllowAnonymousAccess überschrieben werden und “true” zurückliefern. 1: public partial class FileGet : UnsecuredLayoutsPageBase 2: { 3: protected override bool AllowAnonymousAccess { get { return true; } } 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; } In dieser Application Page muss noch der Verweis auf die Masterpage entfernt werden. Da wir im Page_Load den Response umschreiben ist kein Inhalt erforderlich. Außerdem ist der Zugriff auf die Masterpage einem externen User nicht erlaubt, was wieder in einem 401- Access denied endet. Jetzt bleibt noch eine Hürde zu überwinden: Die Security von SharePoint. Da wir über das SharePoint API die Datei abfragen (web.GetFile(url)) wird die Berechtigung des Users geprüft, ob er überhaupt die Datei in der Dokumentbibliothek lesen darf. Der anonyme User darf das nicht. Daher lassen wir den gesamten Code um die Datei abzufragen und zurückzuliefern unter “ElevatedRights” laufen. Code der unter SPSecurity.RunWithElevatedPrivileges läuft wird unter dem Systemkonto ausgeführt. MSDN Der gesamte Code meiner Page_Load Routine: 1: protected void Page_Load(object sender, EventArgs e) 2: { 3: string ticket = Request["ticket"]; 4: SPSecurity.RunWithElevatedPrivileges(delegate() 5: { 6: Guid ticketID = new Guid(ticket); 7: using (SPSite site = new SPSite(SPContext.Current.Web.Url)) 8: { 9: using (SPWeb web = site.OpenWeb()) 10: { 11: string docUrl = getDocUrl(ticketID, web); 12:   13: if (!string.IsNullOrEmpty(docUrl)) 14: { 15: SPFile spFile = web.GetFile(docUrl); 16:   17: Response.Clear(); 18: Response.AddHeader("Content-Disposition", "attachment; filename=" + spFile.Name); 19: Response.AddHeader("Content-Length", spFile.Length.ToString()); 20: Response.ContentType = "application/mspowerpoint"; 21:   22: Response.BinaryWrite(spFile.OpenBinary()); 23: } 24: } 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; }

11, 12, 13, 2011, 2012, 2013

Normalerweise ist dies ein Technologie Blog. Aus gegeben Anlass weiche ich dieses Mal davon ab. Microsoft verdient mit Firmenkunden. Obwohl man überall von “Consumerization of IT” liest, lässt Microsoft, den Consumer ziemlich kalt. Bing, Windows Phone, Zune  und selbst XBox alles draufzahl Geschäft. Ist ja hinlänglich bekannt. Geld wird verdient mit Office, Windows oder auch SharePoint. Und da tut sich großes. Bis Ende des Jahres kommen eine Reihe neuer Produkte die vielleicht sogar 90% des Umsatz von Microsoft ausmachen und mit Gewissheit nahezu den gesamten Gewinn. Wer Microsoft kennt, weis das deren Geschäftsjahr am 30.06 Endet. Entsprechend sind die Planungszyklen jährlich ab diesem Stichtag. Der Sommer fällt quasi aus, also müssen die Produkte im Herbst in den Regalen stehen um das Jahresendgeschäft mitnehmen zu können. Produkt Codename vermutlicher Name Visual Studio 11 Visual Studio 2012 Windows 8 Windows 8 Windows Server 8 Windows Server 2012 SharePoint 15 SharePoint 2013 Office 15 Office 2013 Windows Phone Apollo Windows Phone 8 Da Microsoft nun die unsäglich Geheimhaltungspolitik von Apple abgekupfert hat, gibt es keine wirklichen Informationen dazu. Schweigen aus Redmond. Nur sogenannte Rumors oder dort und da ein Häppchen (Skype, Skydrive). Von Visual Studio 11 ist die aktuell in grau gehaltene Oberfläche hinlänglich bekannt und kritisiert. Die Rumors meinen, das wird sich noch ändern. Windows 8 ist mit der Consumer Preview am bekanntesten. Für Juni ist eine letzte Beta angekündigt. Vermutlich Zeitgleich werden wir auch Expression Blend 5 und Visual Studio mit seinem finalen Produktnamen sehen. Beim Windows Server 2012 sind keine Überraschungen zu erwarten. SharePoint 13 wird überraschenderweise sehr verdeckt behandelt. Es gibt schon Betatester die über METRO Design, HTML5, Projekt und Visio Integration berichten. Eine Beta wird für den Juni oder Juli gehandelt. Da SharePoint Projekte nicht mal so durchgeführt werden, könnte es sein das Microsoft sich bis Anfang 2013 Zeit nimmt. Office 15 scheint schon weiter zu sein. Im Netz kursieren Screenshots die auch METRO UI und Cloud Integration zeigen. Für das Jahresendgeschäft wäre es wichtig das die Box in den Regalen steht. Total im Nebel sind die Informationen rund um die nächste Windows phone Version. Fest steht es wird Windows 8 drunter liegen und es sollen die bisherigen Silverlight basierenden Anwendungen aus dem Store weiter lauffähig sein. Allerdings haben Geräte Hersteller sehr lange Vorlauf Zeiten. Da aktuell außer Nokia keiner neue Geräte bringt, spricht einiges für ein neues WP8 noch im November. Rechtzeitig für den Christbaum. Um es mal anders auszudrücken. Wenn der Termin nicht gehalten wird, kann Microsoft das Thema eigentlich einstampfen. Jetzt stellt sich nur noch die Frage, wird alles in einem Big Bang gelauncht oder stückweise? Für ersteres spricht, das in einem Zeitraum von vielleicht 3 Monaten dem Kunden es nicht zuzumuten ist, sechs oder mehr Produktlaunches wahrzunehmen. Andererseits gibt es Zwänge die ein paar der Produkte etwas früher am Markt platzieren müssen. Noch ein Rumor handelt von der wichtigsten Konferenz //BUILD oder früher PDC .  Es soll im Oktober sein, Los Angeles. MIXX gibt es ja bekanntlich nicht mehr. Die nächste Windows Version oder WinRT braucht jedenfalls definitiv einen kürzeren Produktzyklus als drei Jahre. In rund 6 Monaten werden wir schlauer sein. Ich glaube persönlich das bereits auf der Microsoft Partner Konferenz im Juli in Toronto der Vorhang fallen wird. Es wird ein anstrengendes, spannendes Jahr mit vielen Chancen. Ich freu mich drauf.