Demo Sourcecode

https://github.com/gbegerow/aurelia-gbegerow-samples

Aurelia Links

Plugins:

Sonstiges:

ES6 / Javascript 2015

Modern Development needs Unit Tests to enable the trust in code even after large changes. SharePoint is traditionaly bad to unittests as the server side object model is very bad to mock (no interfaces, komplex dependencies between objects, only on sharepoint server, …) The newer client side api hasn’t gotten much love on this either. But I want to try to use at least some unittesting in JavaScript. So I decided to start embedding some unittest frameworks into new SharePoint Apps to find which works for me in this context.

We start with QUnit, a framework which evolved together with jQuery.

 

Structur of the Appcode

If you create a new app in the web IDE (Napa), you get several files. The difficult part is, where to place the code. After some experiments I found a way which works for me, at least for a start. The best place (or the place which worked) is to put script, css and qunit div in the default aspx. qUnit is referenced from the scripts directory but you can of course use the CDN Url. Unittests are located in scripts/test.js. When this will grow, it will be refactored to multiple files, but not unless needed.

<%-- The markup and script in the following Content element will be placed in the <head> of the page --%>
<asp:Content ContentPlaceHolderId="PlaceHolderAdditionalPageHead" runat="server">
    <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.2.min.js" type="text/javascript"></script>

    <!-- Add your CSS styles to the following file -->
    <link rel="Stylesheet" type="text/css" href="../Content/App.css" />
	<!-- qunit -->
	<script type="text/javascript" src="../Scripts/qunit-1.10.0.js"></script>
	<link rel="stylesheet" href="../Content/qunit-1.10.0.css">


    <!-- Add your JavaScript to the following file -->
    <script type="text/javascript" src="../Scripts/App.js"></script>
	
	<script type="text/javascript" src="../Scripts/tests.js"></script>
		
</asp:Content>

<%-- The markup and script in the following Content element will be placed in the <body> of the page --%>
<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">

    <div>
        <p id="message">
            <!-- The following content will be replaced with the user name when you run the app - see App.js -->
            initializing...
        </p>
    </div>

<h2>Unit Tests</h2>
		<div id="qunit"></div>
		<div id="qunit-fixture"></div>
</asp:Content>

Unittesting Client OM

qUnit supports synchronous and asynchronous  tests which fits fine with SharePoint Client OM .

/// <reference path="qunit-1.10.0.js" />
test("hello test", function(){
	ok( 1== "1", "Passed!");
});

test("hello sharepoint", function(){
	var clientContext = new SP.ClientContext.get_current();
	var web = clientContext.get_web();
	
	clientContext.load(web);
	
	throws( function(){ web.get_title(); }, "No sync access");
});

asyncTest("hello sharepoint async", function(){
	expect(1); // exact one call to ok
	
	var clientContext = new SP.ClientContext.get_current();
	var web = clientContext.get_web();
	
	clientContext.load(web);

    clientContext.executeQueryAsync(
        function(){ok(true, "Sharepoint web "+ web.get_title()); start();},
		function(){ok(false, "web load failed"); start();}
    );
	
});

And this is the result:

image

Das ContentEditorWebPart ist eigentlich eine feine Sache um bei einer SharePoint Applikation Content auf eine Seite zu packen, den der User nachträglich noch anpassen kann. Man findet auch einige Beispiele im Netz, wie dies programmatisch gemacht werden muss, aber alle Beispiele beschränken sich darauf, unformatierten Text zu setzen. Hier möchte ich zeigen wie man XHTML Content setzen kann.

 

Zur Erinnerung, der prinzipielle Vorgang beim Erzeugen und Hinzufügen ist wie folgt:

public void AddEditableContent(SPWeb web, string pageName, string zone, string title, string content)
{
    using (var elevatedSite = new SPSite(web.Site.ID))
    using (SPWeb elevatedWeb = elevatedSite.AllWebs[CurrentWeb.ID])
    using (SPLimitedWebPartManager webPartManager =
            elevatedWeb.GetLimitedWebPartManager(
                SPUtility.ConcatUrls(elevatedWeb.Url, pageName), 
                PersonalizationScope.Shared))
    {
        var xmlDoc = new XmlDocument();
        XmlElement container = xmlDoc.CreateElement("container");
        container.InnerText = content;

        var editable = new ContentEditorWebPart { Title = title, Content = container };

        webPartManager.AddWebPart(editable, zone, 1);
        webPartManager.SaveChanges(editable);
        web.Update();
    }
}

Content erwartet ein XmlElement, dabei ist der Elementname völlig egal. Logische Annahme wäre also, man setzt (X-)Html als InnerXml statt InnerText. Ersetzen wir also container.InnerText durch container.InnerXml und probieren als content Parameter mal einen Html String:

<h2>Willkommen</h2><p><b>Fettes</b> Hallo!</p>

mit dem Ergebnis:

Willkommen Fettes Hallo! ohne jede Formatierung.  Grummel! Offensichtlich wird der Value des Xml/Html in das Content Element des Webparts kopiert, dabei geht dann jede Struktur verloren Trauriges Smiley

Nach ein bisschen Rumprobieren kam ich schließlich auf die Lösung. Damit die Formatierung nicht verloren geht, muss man sie vor der Interpretation durch XML schützen. Dies geht entweder via SPEncode.XmlRemoveControlChars oder durch eine CDATA Section. Die endgültige Version von Zeile 12 lautet daher:

container.InnerXml = string.Format("<![CDATA[{0}]]>", content);

Jetzt kriegen wir:

Willkommen


Fettes Hallo!

Man macht also relativ aufwändig ein XmlDokument, kapselt die Html Struktur in einer Xml sicheren Textversion und übergibt dann ein XmlElement… Wer hat das entworfen???? Was hat der geraucht??? Wieviel sinnvoller wäre es doch, einfach einen String zu übergeben!

Die Tage musste ich in eine mir weitgehend unbekannte SharePoint Solution eine neue Übersetzung einpflegen. Leider war die Struktur der ResX Datei beim Übersetzer zerstört worden und zurück kam eine schwach strukturierte Mischung verschiedener Resourcefiles in Form eines Excel Files bei der einzelne Übersetzung gleich komplett fehlten. Der ZetaResource Editor stürzte angeblich bei den Resourcen ab, was ich dummerweise einfach geglaubt habe. Da ich ziemlich viel Zeit verschwendet habe, bis ich alles mit Hilfe eines Kollegen (Danke Mario) gerade gebogen hatte, möchte ich hier die Lehren daraus zur Diskussion stellen.

Resourcen in SharePoint

SharePoint benötigt seine Resourcen je nach Kontext an verschiedenen Stellen. Features und Backend Solution können sich eine Resourcedatei teilen, sofern diese im Hive in …/14/Resources liegt.Alternativ kann man die Featureresourcen wie die Namen von SiteColumns, ListDefinitions usw. auch auf Featurescope lassen. SharePoint nutzt hier die $Resources: Syntax in den elements.xml usw. Nutzt man eine gemeinsame/globale Resource muss man darauf achten den Namen der Resourcedatei mit anzugeben, also z.B. $Resources:Wulf.Absence,Start_SiteColumnDisplayName.

Backendlogik kann die globale Resourdatei entweder über einen ResourceManager oder über die Codegenerierung der Resource nutzen. Ich persönlich ziehe den Zugriff über die generierte Klasse vor, da sie Typsicherheit und Intellisense bietet. In diesem Fall wurde aber eine Zugriffsklasse mit hunderten von const string Feldern benutzt, was die gleichen Vorteile bietet, aber manuell gepflegt werden muss. 

Application Pages, visuelle WebParts oder Usercontrols brauchen eine Resourcendatei in AppResources und nutzen ebenfalls die $Resources: Syntax. Diesmal muss man bei der Referenzierung auf die Endung .AppResource achten also z.B.  Text=”<%$Resources:Wulf.Absence.AppResource,Page_Title%>”

Aufbereitung

Um erst einmal eine laufende Übersetzung zu bekommen kopierte ich das ganze Sammelsurium aus Excel in die neue Sprachresource. Dann habe ich sortiert und Feld für Feld verglichen.

Grober Patzer! Der viel einfachere und bessere Weg ist es, die Namensspalten von Original und Übersetzung nach dem Sortieren in je eine Textdatei zu kopieren. Danach kann man sie mit der Vergleichsfunktion von VisualStudio 12, CodeComparer, WinMerge oder andere Vergleicher vernünftig auf Unterschiede kontrollieren. Leider habe ich eine Weile gebraucht, bis ich mich an den einfachen Weg erinnert habe.

In diesem Schritt sind dann erst mal alle Übersetzungen rausgeflogen, die nicht dazugehörten und die fehlenden aus dem Original kopiert. Zum Glück war ich in der Lage in diesem Falle die fehlenden Übersetzungen selber zu machen, normalerweise würde man hier eine Liste für den Übersetzer machen (Verbot sich hier aus Zeitgründen)

Kontrolle im Code

Um eine Liste aller tatsächlich verwendeten Resource Keys zu bekommen, muss man sie aus dem Code  bzw. den XML Dateien extrahieren. Das geht prima mit einer Suche in Visual Studio. Um sicher zu stellen, das auch alle Zugriffe  auf die globale Resource die Datei mit angeben nutze ich Suchen und Ersetzen mit Regular Expressions:

Suche \$Resources\:{:i};

Ersetze durch $Resources:Wulf.Absence,\1;

image

(wir erzeugen die XML Dateien normalerweise über die SharePoint Software Factory, die legt die Texte in den Featurespezifischen Resourcen ab. Nicht sicher, ob das eine Einstellungssache ist)

Alles was der $Resouce: Datei, Key Syntax folgt, findet man mit der Suche nach

\$Resources\:{:i},{:i};

Das Suchergebnis packt man wieder in eine Textdatei, befreit es von Rauschen über Suchen und Ersetzen mit

Suchen: ^{.*}\$Resources\:{:i},{:i};{.*}$

Ersetzen: \2, \1

Dann hat man Einträge der Form Key, Datei. Alternativ kann man auch mit \2 ersetzen, dann hat man wieder nur die Keys und kann die via WinMerge mit den Keys in der Resource abgleichen.

Fehlersuche

Natürlich lief die Solution nach dem Deployment auf Fehler wegen fehlender Resourcen (Ich hatte ja von Hand verglichen Trauriges Smiley) Hier ein paar Tips, die man dann als erstes kontrollieren sollte.

  1. AppPool recyclen oder IISReset /noforce
  2. Liegen die aktuellen Resourcen im Hive? Die globale Resource muss im Hive in Program Files\Common files\Microsoft Shared\Web Server Extensions\14\Resources liegen. Die AppResource unterhalb von c:\Inetpub\wwwroot\wss\<Ordner der Webapplication>\AppResources
  3. Liegen Resoucen tatsächlich im Ordner der richtigen Webapplication? Unsere Lokalisierungen liegen als eigene Solution vor, damit man bei neuen Übersetzungen die Hauptapplication nicht neu deployen muss. Deshalb darauf achten, das Resource und Applikation auch auf die gleiche Webapplikation deployed werden.
  4. ULS Log via ULSViewer beim Aufruf mitlaufen lassen. Fehlende Resourcen werden als MissingResourceManifestException gemeldet. Wenn man weiß wo man suchen muss,  mit WinMerge die Keys vergleichen
  5. Debuggen für den Zugriff auf fehlende Resourcen vom Code. In diesem Fall ging jeder Zugriff über eine gemeinsame Methode. Die wurde von mir erweitert, das sie für jede fehlende Resource einen Eintrag ins Log machte und dann den Schlüssel zurückgab. Allein durch den Fallback auf den Schlüssel fielen eine ganze Reihe fehlender Felder sofort auf.

A co-worker asked the other day about a communication between different powershell scripts. First solution was using Mutex but it showed pretty fast that Mutex and Semaphore does only work in Powershell if you start with the –STA switch. By default Powershell uses a Multithreaded Apartment with each command running in its own thread. So when the simples method failed I looked for other means

Named Pipes to the Rescue

Named Pipes are not supported natively by Powershell (as far as I know). But as you can use any .Net Classes in Powershell thats not a Problem. You only need to load the System.Core Assembly (from .Net 3.5) via either

add-Type -assembly "System.Core" (in PS V2)

or old fashioned via

[reflection.Assembly]::LoadWithPartialName("system.core")

Some are telling you to load with explicit version but IMHO this only
ties you to a certain combination of Powershell and .Net. Usualy newer versions of .Net ensures backwards compability anyway.

Server Side

$pipe=new-object System.IO.Pipes.NamedPipeServerStream("\\.\pipe\Wulf");
'Created server side of "\\.\pipe\Wulf"'
$pipe.WaitForConnection(); 

$sr = new-object System.IO.StreamReader($pipe); 
while (($cmd= $sr.ReadLine()) -ne 'exit') 
{
 $cmd 
}; 

$sr.Dispose();
$pipe.Dispose();

In this case the server only emits the given command but feel free to parse it and even extract parameters or just ignore the cmd and always do the same.

Client Side

$pipe = new-object System.IO.Pipes.NamedPipeClientStream("\\.\pipe\Wulf");
 $pipe.Connect(); 

$sw = new-object System.IO.StreamWriter($pipe);
$sw.WriteLine("Go"); 
$sw.WriteLine("start abc 123"); 
$sw.WriteLine('exit'); 

$sw.Dispose(); 
$pipe.Dispose():

On the client side you are writing to the pipe. As long as you don’t send the exit command you can connect from different clientscripts.

Naming the Pipe

Named Pipes must have uniq names per System so you better use something like \\.\pipe\company.tld.app. While the server side is bound to use localhost ‘.’ the client can use a remote connection with the \\servername\pipe\… syntax. (If the policies and firewall settings permit it).

 

I might revisit it and bring it to a proper module form but for the moment it is enough. Stay alert as this is not tested in production qualitiy scripts there might be a lot of caveats. Don’t say I didn’t told you.

Gerade einen Fehler behoben der mich sehr viel Zeit gekostet hat. Beim Publishen eines Projektes erhielt ich den Fehler

The target "_CopyBinDeployableAssemblies" does not exist in the project.

Ursache war ein modifiziertes .target File das nicht durch das ServicePack 1 von Visual Studio upgedated wurde.

Zum Glück hatte ich eine Maschine, bei der die Installation von SP1 funktioniert hatte. Habe die Daten aller .target Files verglichen und fand dabei eine Datei deren Datum abwich. Das auch noch eine Kopie der Datei mit der Endung .target.org daneben lag, half mir dann auch noch auf die Sprünge Smiley

Mit der nächsten .Net Version wird eine deutliche Vereinfachung der asynchronen Programmierung einher gehen.

C# und VB.Net werden um die Schlüsselwörter async und await erweitert. Async markiert eine Methode oder einen Lambda Ausdruck und await wartet auf den Abschluss eines Tasks ohne zu blockieren.

Beispiel:

public async void ProcessPage(Uri url)
{
  Task<string> downloader =  new WebClient().DownloadStringTaskAsync(url);
  string pageData = await downloader;
  // ... process ...
}

Await erlaubt es asynchronen Code so zu schreiben, das die eigentliche Programmlogik nicht mehr unter Tonnen von boilerplate Code verborgen ist.

Eines der wesentlichen Konzepte für die Benutzung von async und await ist die Task Struktur, die mit .Net 4 eingeführt wurde. Task ist eine Repräsentation des asynchronen Vorgangs. Task ist das Handle mit dem man auf den Vorgang zugreifen kann. Es ist nicht der Thread und es ist nicht das Resultat des Vorgangs. Task bietet Zugang zum Status des Vorgangs, zum Ergebnis und zu evtl. Fehlern.

Microsoft bietet eine CTP zum Download an, mit der man die neuen Keywords ausprobieren kann. (s.u.) Auf dieser Seite findet man auch die wichtigsten Links u.a. zu sehr informativen Videos auf Channel 9. Zusammen mit der CTP werden eine Reihe Dokumente installiert und jede Menge Beispiele. Das wichtigste ist das 101 Async Samples. Ein wahrer Schatz! Danke Microsoft, diese Samples erleichtern den Einstieg so sehr.

Links

Visual Studio Async CTP http://msdn.microsoft.com/en-us/vstudio/async.aspx

Async Readme (wird mit Async CTP installiert unter My Documents > Microsoft Visual Studio Async CTP)

Eric Lippert‘s Blog http://blogs.msdn.com/b/ericlippert/

Jon Skeet‘s Blog http://msmvps.com/blogs/jon_skeet/default.aspx

Slides zum Vortrag

Follow

Get every new post delivered to your Inbox.