Azure Web Apps Mail senden

Erst gestern gab es von Scott Guthrie eine Reihe neuer Azure Service Ankündigungen. Azure Websites heißen nun Azure Web Apps. Daneben noch API Apps, mobile Apps und Logic Apps. Ein Problem bleibt aber, wie sende ich eMail aus einer Azure Website App? Einige Blogs und die Suche nach “how to send mail from azure website” deutet auf einen Dienst Namens Sendgrid hin.

Gleich eines vorab. Man kann eMails auch in Azure wie gewohnt mit dem SMTPClient Objekt senden. Folgender .NET Code funktioniert, wenn man einen SMTP Server als Relay Host nutzen kann.

   1:  Dim sc As New SmtpClient
   2:  sc.Host = "mail.ppedv.de"
   3:  sc.Send("xxxx@outlook.com", "hannesp@ppedv.de", "test", "test")

Dabei sind wir auch schon bei den Problem. Azure stellt keinen SMTP Dienst bereit. Jedenfalls bisher nicht, mal sehen was sich in den API Apps noch verbirgt.

Wer über Dienste wie Google Mail oder Outlook live senden möchte, muss sich am SMTP Server authentifizieren um Spammern das Leben zu erschweren. Auch das klappt bei Azure grundsätzlich mit folgendem VB.NET Code Beispiel

   1:  Dim sc As New SmtpClient
   2:  sc.Host = "smtp.live.com"
   3:  sc.EnableSsl = True
   4:  sc.Port = 587
   5:  sc.Credentials = New NetworkCredential("hannesp@ppedv.de", "passwort?")
   6:  sc.Send("xxxxx@outlook.com", "hannesp@ppedv.de", "test", "test")

Allerdings wird vermutlich eine Fehlermeldung in der Website auftauchen ala

Mailbox unavailable. The server response was: 5.7.3 requested action aborted; user not authenticated

Der Grund ist, das diese Website und damit die Anmeldung von einem Azure Web Server kommen. In meinem Fall aus den Niederlanden. Über das Live Portal kann man den Activity Request erkennen und markieren als “von mir stammend”

image

Wenn aber der Mail Server gerade keine Mails entgegen nehmen möchte, wird mein Code Laufzeit Fehler verursachen, die sehr schwer zu behandeln sind. Mail erzeugen und Mail versenden sollten zwei verschiedene Prozesse sein. Vor allem da das Senden asynchron funktioniert, bei Retry versuchen mit mehrstündiger Dauer.

Korrekterweise werden die Einstellungen zum Versand von emals in der Datei Web.Config vorgenommen. Hier im Bereich system.net.  Dort kann man auch festlegen, das die erzeugten Mails in einem Verzeichnis gespeichert werden.

   1:  <system.net>
   2:      <mailSettings>
   3:        <smtp deliveryMethod="SpecifiedPickupDirectory">
   4:          <specifiedPickupDirectory 
pickupDirectoryLocation="D:\home\site\wwwroot\pickup"/>
   5:        </smtp>
   6:      </mailSettings>
   7:   
   8:    </system.net>

Wie erhält man den physikalischen Pfad einer Web Seite die auf Azure läuft. Ganz einfach per Server.MapPath("."). Im Unterverzeichnis Pickup wird dann das email als eml Datei erzeugt.

image

Solche Dateien kann man auf einem Windows System per Doppeltclick öffnen. Auch das enthaltene Text Format ist leicht lesbar.

X-Sender: xxxx@outlook.com
X-Receiver: hannesp@ppedv.de
MIME-Version: 1.0
From: xxxx@outlook.com
To: hannesp@ppedv.de
Date: 24 Mar 2015 18:36:52 +0000
Subject: test
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: quoted-printable

test

 

Wenn man nun diese Dateien in ein Pickup Verzeichnis eines Windows SMTP Servers kopiert, werden diese als eMail versandt. Dazu aber später mehr.

Azure Websites Verzeichnis schützen

Microsoft hat ein Geschenk für uns. Azure Websites erlauben das gratis Hosting einer Web Anwendung. Einzig eine Microsoft ID wird benötigt und schon kann man zehn Websites mit bis zu 1GB Speicherplatz nutzen. Bis zu 165MB Datentransfer täglich sind ebenfalls gratis inkludiert. Allerdings ist der Umstieg von einem eigenen IIS zu Azure Websites nicht ganz barrierefrei.

Zu den typischen Aufgaben einer Website gehört der Upload von Dateien durch den Benutzer. In der Regel schreibt der ASP.NET Entwickler einen http Handler (Erweiterung axd) um den Uploadstream entgegen zu nehmen und auf dem Web Server zu speichern. So behält man volle Kontrolle über Größe, Format, Namen und Speicherort. Bisher hat man diese Dateien in einem Verzeichnis der Website gespeichert und möchte das auch weiter so lösen. Alternativ dient dazu auch der Azure Blob Storage.

Fileupload in ein Web Verzeichnis kann ein potentielles Sicherheitsproblem sein. Ein Benutzer kann eine ausführbare Datei (z.B. ASPX) hochladen und somit Code einschleusen. Das Upload Verzeichnis muss also vor ausführbaren Programmcode geschützt werden. Einige ASP.NET Directorys werden out of the box mit besonderen Rechten versehen. So ist alles was in APP_DATA existiert automatisch und zwingend vor Download geschützt. In der Praxis legt man dort seine Datenbanken ab.

Wer einen dedizierten Server mit IIS betreibt hat volle Kontrolle über das NTFS Dateisystem und kann die Rechte dort passend pro User (appool) einstellen.

Beim Publish auf Azure stehen diese Möglichkeiten nicht offen. Man behilft sich mit einer Ausnahmeregel in der Web.config. Sämtliche Datei Handler werden entfernt und mit dem Static File Handler ersetzt so das man nach wie vor Images, Videos oder auch HTML anzeigen bzw. Downloaden kann. Die Ausführung von Code in einer ASPX Datei wird mit einer 404er Fehlermeldung beantwortet.

 

   1:  <location path="app_upload">
   2:      <system.webServer>
   3:        <handlers>
   4:          <clear />
   5:          <add name="StaticFile" path="*" verb="*" 
modules="StaticFileModule,DefaultDocumentModule,DirectoryListingModule"
   6:  resourceType="Either" requireAccess="Read" />
   7:        </handlers>
   8:      </system.webServer>
   9:    </location>
  10:  </configuration>

No SQL mit DocumentDB und Azure

Es gibt etwas, was andere total cool finden und Sie nicht den Ansatz einer möglichen Nutzung sehen? Nein ich spreche nicht von der Apple Watch, ich spreche von NoSQL Datenbanken und im besonderen von neuen Azure Dienst DocumentDB. Ein Versuch einer Erklärung für etwas, was die wenigsten vermutlich brauchen werden.

Wenn man wenig weis, hilft immer ein Wikipedia Zitat

NoSQL (englisch für Not only SQL) bezeichnet Datenbanken, die einen nicht-relationalen Ansatz verfolgen und damit mit der langen Geschichte von relationalen Datenbanken brechen. Diese Datenspeicher benötigen keine festgelegten Tabellenschemata und versuchen, Joins zu vermeiden, sie skalieren dabei horizontal. Im akademischen Umfeld werden sie häufig als „strukturierte Datenspeicher“ (engl. structured storage) bezeichnet.

Ein neuer Dokumenten orientierter Vertreter ist documentDB, als Microsoft Azure Service aber nur online Verfügbar. Relativ vereinfacht, werden dabei JSON strukturierte Informationen gespeichert.  Für die Entwicklung steht JavaScript oder auch eine .NET API zur Verfügung.

Abfragen lassen sich mit einer ANSI SQL ähnlichen, reduzierten Syntax oder im .NET Umfeld auch per LINQ durchführen. Sämtliche CRUD Operationen werden unterstützt und lassen sich sogar per REST API ausführen. Unterstützt werden auch Transaktionen, Trigger und Stored Procedures.

Man kann nun stundenlang sinnieren, warum die Aufgabenstellung nicht per RDBMS und SQL Server löst. Microsoft preist documentDB als günstige und skalierbare Lösung. In jedem Fall stößt ein SQL Server bei sehr großen Datenmengen (über 50GB) und/oder vielen Schreiboperationen an Performance Grenzen. In jedem Fall sind Cloud basierte Lösungen für diese Probleme noch anfälliger. Wer rein aus der JavaScript Welt kommt, nutzt documentDB sozusagen als native Datenbank, fern von .net oder SQL.

Stellen wir uns also vor, eine klassische Aufgabenstellung einer Warenwirtschaft mit einer NoSQL Architektur zu lösen. Eine Rechnung stellt dann das Dokument dar. Mit Kopfdaten und Positionen. Die Positionen können sich in der Struktur erheblich unterscheiden. Schrauben haben andere Eigenschaften als Hosen. Einzelne Artikel können sich wiederrum aus Stücklisten zusammen setzen. Alles in einem Dokument und mit erheblich redundanter Datenmenge. Im Ergebnis jedes Document mit unterschiedlicher Struktur innerhalb einer Sammlung.

Zu allererst wendet man sich an das neue Azure Portal und erstellt dort ein documentDB Konto. Das dauert ziemlich lange. Als Ergebnis erhält man einen HTTP Endpunkt und einen Key für die Anmeldung.

documentd4

Als nächstes kann man im Web basierten Management Portal eine oder mehrere Datenbanken hinzufügen. Jede Datenbank enthält Collections (Sammlungen), die auf den ersten Blick Tabellen ähneln- allerdings ohne Struktur. Darin werden die Dokumente gesammelt, z.B. Rechnungen. Aufgrund der Schema Freiheit aber auch gemixt Adressen und Rechnungen.  Eine Sammlung wird immer auf einem Server gehostet, mit Azure typischen zwei zusätzlichen Kopien. Da die Preview Version (stand Feb 2015) ein Limit von 3 Sammlungen aufweist, wird man in der Praxis viel weniger davon nutzen, als bei RDBMS Tabellen und eher um die Daten zu partitionieren.

Auch eine  Sammlung kann man im Portal anlegen. Diese Funktion ist schwer zu finden und erscheint erst nach Auswahl der Datenbank im Azure DocumentDB-Kontobereich. Um den Abschnitt Datenbank zu erreichen, muss man nach unten scrollen.

image

Aber genau das macht NoSQL eben aus, das die Struktur und damit das Datenbank Schema nicht am Anfang steht. Deshalb wird in der Praxis häufig der Code die Aufgabe übernehmen die Collections anzulegen.

Abfragen kann man die Daten direkt im Azure Portal, per Dokument Explorer oder per Abfrage Explorer.

image

Die Abfragen erlauben ähnlich einem SQL Query Tool die Dokumente anhand der Attribute zu selektieren und zeigt die Ergebnisse als JSON an.

image

Hier sieht man das documentDB intern GUID ähnliche ID Werte verwendet.

Wesentliche Part der Struktur geschieht im Code. Wie von mir gewohnt per ASP.NET Webforms und VB.NET. Im Visual Studio Projekt muss per Nuget eine Referenz auf die Documents Clients Assembly eingefügt werden. Da im Stadium Prerelease (oder Preview) nur unter Auswahl der passenden DropDownliste auffindbar.

documentdb7

Es wird ein Client Objekt instanziiert, das später die Methoden für den Datenbank Zugriff kapselt.

 

   1:  Private Url As String = "https://ppedv.documents.azure.com:443/"
   2:  Private key As String = "w9t7JWW...qeZzKMS7pAmbspSKK5gw=="
   3:  Private _client As DocumentClient = New DocumentClient(New Uri(Url), key)

 

So lässt sich die Datenbank und darin die Document Collection per Code erzeugen.

   1:  _client.CreateDatabaseAsync(New Database With {.Id = "miniSAP"})
   2:  ...
   3:    col = Await _client.CreateDocumentCollectionAsync(
   4:                  db.SelfLink,
   5:                  New DocumentCollection With {.Id = "Rechnungen"})

 

Fehlen nur noch Rechnungen. Diese werden im folgenden Code Schnipsel aus zwei TextBoxen und einer passenden Klasse erzeugt.

   1:  Dim p = New rechnung With {
   2:                                   .firma = TextBox1.Text,
   3:                                   .betrag = TextBox2.Text,
   4:                                   .datum = Date.Now}
   5:   
   6:  Await _client.CreateDocumentAsync(col.SelfLink, p)

 

Natürlich will man die Daten auch anzeigen. Webforms hilft per ModelBinding, setzt dann allerdings wieder ein typisierte Liste voraus. Der HTML Code der ASPX Seite.

   1:  <asp:ListView ID="ListView1" ItemType="documentdb1.rechnung" 
   2:              SelectMethod="ListView1_GetData"
   3:               runat="server">
   4:              <ItemTemplate>
   5:                  <%#Item.firma%><br />
   6:              </ItemTemplate>
   7:  </asp:ListView>

 

Etwas komplexer ist die Codebehind Logik. Datenbank abfragen, Rechnungsliste abfragen und dann per SQL das oder die passenden Dokumente laden.

   1:  Public Async Function ListView1_GetData() As Threading.Tasks.Task(Of List(Of rechnung))
   2:          Dim db As Database
   3:          db = _client.CreateDatabaseQuery().Where(
   4:  Function(x) x.Id.Equals("miniSAP")).AsEnumerable().FirstOrDefault()
   5:   
   6:          Dim col As DocumentCollection
   7:          col = _client.CreateDocumentCollectionQuery(db.SelfLink).Where(
   8:  Function(c) c.Id.Equals("Rechnungen")).ToArray).FirstOrDefault
   9:   
  10:          Dim q = _client.CreateDocumentQuery(Of rechnung)
(col.SelfLink, "select * from Rechnungen").ToList
  11:   
  12:          Return q
  13:  End Function

 

Azure DocumentDB  macht die ORM Brücke Entity Framework überflüssig. Die Objekte stecken eben in der Datenbank. Die Programmierung geht leicht von der Hand. Allerdings muss der Entwickler komplett umdenken und das wird das größere Problem darstellen. Zusätzlich stellt sich die Frage was wir gewonnen haben. In diesem Fall nichts. Wenn man aber Amazon ist und pro Sekunde hunderte derartige Transaktionen durchführen will, könnte documentDB das Leben leichter machen. Dieser Azure Service steht  noch am Anfang.

Danke an Rainer Stropek für review.

WPF mit Expression Blend

Zum Schulungsangebot gehört auch eine 2tägige Blend Schulung. Eigentlich adressiert dieser Kurs eher Designer und fokussiert das Werkzeug Expression Blend. Immer öfter wird aber Blend das Programmierer Tool und die UI Designer machen nach wie vor Photoshop. Im letzten Expression Blend Training saß nur ein Kurs Teilnehmer und schnell wurde aus einer Schulung eine coworking Fragerunde.

Konkret taucht die Frage auf, wie man den Inhalt der RadioButtons abhängig vom Check Status darstellt. Je nachdem ob man Silverlight, METRO Apps oder WPF Desktop Anwendungen adressiert, fällt die Antwort unterschiedlich aus. WPF setzt von Anfang an auf Trigger um Status Änderungen wie Hoover zu verwalten. Die neuere Technologie aus Silverlight ist der ViewStateManager. Diese States sind zwar in WPF nun möglich, aber die Templates der Standard Controls verwenden immer noch Trigger.

Hier wird nun gezeigt, wie mit Expression Blend die Vordergrund Farbe des Textes eines Option Steuerelements am einfachsten verändert.

Zuerst wird ein Radio Button Steuerelement aus der Werkzeugleiste auf das Formular gezogen oder per Doppelklick erzeugt. Das Control erscheint nun im Designer und in der Objects und Timeline Übersicht.

image

Entweder dort, im Designer oder im Objects Bereich wird per Recht klick im Kontext Menü der Eintrag “Edit Template”  und daraus “Edit a Copy” ausgewählt. Blend generiert aus dem Control den kompletten XAL Code der das UI des Steuerelements beschreibt. In Summe werden das knapp 100 Zeilen XAML Code.

Um das Style Template global verfügbar zu machen, wird keine ID vergeben (apply to all). Die zuweisung erfolgt im XAML Implizit über die TargetType Eigenschaft für alle passenden Controls. Je nachdem ob man im Dokument oder in der Anwendung das Style erzeugen lässt mit entsprechender hierarchischer Gültigkeit.

image

Hier werden nun alle Radiobuttons im aktuellen XAML Dokument neu gestylt.

Da man im Editor sowohl Eigenschaften des Controls, wie Farbe, als auch im Template arbeiten kann, muss der Benutzer die Breadcrumb Navigation am oberen Rand im Auge behalten.

Folgendes zeigt, das man sich sozusagen im Template befindet.

image

Im seitlichen Bereich Objects und Timeline werden deshalb die Bestandteile des Steuerelements aufgelistet.

image

So kann man den Auswahl Nubbel, der üblicherweise ein schwarzer Kreis ist, zu einem grünen Kreis umfärben. Das das Ding ziemlich klein und zierlich ist zoomt man am besten richtig ran (hier 800%)

image

Dabei fällt auf, das der Kreis weis bleibt. Das liegt daran das dieser aktuell, da nicht ausgewählt, transparent ist. Genauer gesagt wird die Opacity per Trigger gesteuert. Um an die passende Stelle im Blend WPF Editor zu kommen wählt man den Tab Trigger aus.Der Editor wird rot umrahmt und befindet sich nun im Recording Modus. Hier am besten Finger weg vom Formular. Der Fokus liegt auf dem Trigger ischecked=true. Dieser löst eine Veränderung der Opacity Eigenschaft aus. 1 steht für gefüllt, also das Gegenteil von 0 und transparent. Deswegen ist der Nubbel nun auch grün.

image

Die eigentliche Frage der Teilnehmerin zielt aber auf den Text rechts von der Auswahl. Dieser soll auch grün werden. Das Problem dabei ist, das es sich aber nicht immer um Text handeln muss, da der komplette XAML Subtree aus dem Content Bereich des eigentlichen UI Elements dort im Template an die Stelle des ContentPresenters eingefügt wird. Das ganze nennt sich Template Binding.

Trotzdem klappt es die Farbe des Textes über das Attribut Foreground zu setzen. Dabei wird aber nicht der Content Bereich sondern das umgebende Content Control benutzt.

Beim auslösen des Triggers soll so eine weitere Eigenschaft des Grid verändert werden, dies ist templateroot benannt und wird in den Objektbaum ausgewählt.

image

Nun wird zunächst die Eigenschaft Hintergrund auf Grün verändert. Dies vor allem weil die gesuchte Eigenschaft Foreground nicht angeboten wird.

image

Nicht nur das der komplette Bereich des Radiobuttons nun grün ist, wird auch in den Triggern ein weiterer Eintrag hinzugefügt.

image

Jetzt fehlt nur aus Background den Foreground zu zaubern. Das kann Blend für Visual Studio 2013 nicht per click. Außerdem fehlt es an der direkten Eigenschaft, diese muss per Attached Property TextElement.Foreground quasi reingemogelt werden. Dies kann im XAML Quellcode Editor einfach ersetzt werden.

image

Im Trigger Eigenschaftsfenster wird die neue Eigenschaft korrekt angezeigt und die WPF Anwendung verhält sich korrekt.

image

Azure Website Connection Fehler

Dieser Blog Post dient nur zur Dokumentation eines SQL Connection Fehlers in Azure.

Nach einem Publish der Web Anwendung auf eine Azure Website, liefert die Website folgenden Fehler

Server Error in '/' Application.

Invalid object name 'rooms'.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Data.SqlClient.SqlException: Invalid object name 'rooms'.
Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:

[SqlException (0x80131904): Invalid object name 'rooms'.]
   System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) +1787814
   System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) +5341674

 

Der Kollege hat aus einem Irrtum heraus, eine lokale Kopie deployed die per localdb mit einer SQL Express Datenbank gearbeitet hat. Nach manueller Änderung der web.config auf Azure selbes problem. Die Anwendung läuft lokal aber durchaus mit der SQL Azure Datenbank.

Erinnerungsgemäß hat mich ein ähnlicher Fehler schon mal Stunden gekostet. Damals hatte ich irgendwas gelöscht. Nur weis ich nicht mehr was und wo.

Check All Checkboxen mit AngularJS

Wieder einmal komme ich von einer Angular.JS Schulung nach Hause. Wieder einmal hatten die Teilnehmer des Trainings durchaus Probleme die Konzepte von Angular mit ihren bestehenden Skills zu verbinden. Konkret ging es um den Anwendungsfall eine Liste von HTML Checkboxen mit einem Klick zu aktivieren. Bei der Rückreise vom Angular Kurs mit der Bahn, hat man Zeit darüber nachzudenken und das Ergebnis ist nicht was man erwarten würde.

Kennen Sie den? Wer einen Hammer besitzt, für den ist das Leben voller Nägel. Kann man so interpretieren, das man das tut was man kann. In einer Angular Single Page Application ist das ein HTML View und ein Viewmodel Scope per JavaScript Controller. Dieser stellt die Methoden bereit um aus dem View Änderungen am Scope bla bla bla.

Schon sind wir in ein kleines Buzz Word Bingo mit Angular eingestiegen. Also lassen sie uns einen Schritt zurück treten. Worum geht es eigentlich in einer Web Site? Das völlig konstruierte Beispiel hat folgende Benutzer Anforderungen.

  • Eine Liste Namen anzeigen
  • max 2 Namen einzeln auswählen
  • die ausgewählten Namen zählen und anzeigen
  • Alle Namen aus oder abwählen können

Mancher JavaScript Developer fängt nun an Tests zu schreiben, andere einen Controller. Ich bin der visuelle Typ und zeichne das UI zuerst.

image

Der dazu notwendige Angular.JS Controller

   1:   angular.module('App', [])
   2:          .controller('myController', function ($scope) {
   3:              $scope.liste = [{ wert: "hannes", checked: false },
   4:                  { wert: "maria", checked: true },
   5:                  { wert: "franz", checked: false },
   6:                  { wert: "Hias", checked: false }];
   7:          });

 

Nun könnte man in den Controller einen Watcher einbauen, der die Liste überwacht und bei Änderung einen Zähler anwirft. Der Wert wird dann einer Scope Variablen zugewiesen. Noch wach?

Warum muss sich eine User Interface Geschichte im Controller abspielen? Wo steht das geschrieben?

Angular ist relativ mächtig, wenn es um Datenbindung per Expressions geht. Ausgehend von der Liste der Namen wird mit einem Filter Ausdruck nur die ausgewählten selektiert. Einmal Klammer außen rum und mit lengt die Anzahl ausgeben.

   1:  gesamt {{(liste | filter:{'checked':true}).length }} 

 

Das Ergebnis sieht man am obigen Bild. Well die Grundidee durchaus bestechend ist, wird das gleiche Konzept verwendet um bei mehr als einem ausgewählten Namen die übrigen zu deaktivieren. So kann der Benutzer maximal zwei Namen auswählen, aber die gewählten auch wieder deaktivieren.

   1:  <x ng-repeat="mensch in liste">
   2:     <input type="checkbox" name="test" class="chklist"
   3:         ng-model="mensch.checked"  
   4:         ng-disabled="mensch.checked==false && 
(liste | filter:{'checked':true}).length>1"
   5:     />{{mensch.wert}}
   6:  </x>

Auch das kann man im Screenshot oben erkennen, hannes und hias sind graue Boxen.

Ausgerüstet mit der Erkenntnis, das nicht alles Angular Code Behind sein muss, nun die letzte Aufgabe. Wie kann man alle Checkbocen anhaken oder eben nicht. Wenn man im UI bleibt, ist JQuery das beste Tool um  DOM Manipulationen vorzunehmen. Dafür gibt es verschiedene Selektoren, über ID oder Typ oder eben CSS Klassen, hier chklist genannt.

   1:<input type="checkbox" onclick=" $('.chklist').prop('checked', this.checked)" />Alle<br />

 

Cool oder? Alternative Lösungen die im Web diskutiert (oder gebloggt ) werden kommen auf ein vielfaches an Code.

Eine kleine Randnotiz. JavaScript’s fähigkeiten Objekte zu jeder Zeit zu erzeugen wird mit Angular.js noch magischer. Obiges Beispiel funktioniert auch ohne die checked Eigenschaft der Liste.

   1:   $scope.liste = [{ wert: "hannes" },
   2:                  { wert: "maria" },
   3:                  { wert: "franz" },
   4:                  { wert: "Hias"}];

AngularJS, q$, Learning Partner, REST Api Design und noch vieles mehr

Nachdem Microsoft mit Hinweis auf Datenschutz keine Liste der Microsoft Learning Partner herausgibt, musste ich mir einen anderen Weg suchen. Das schöne daran ist, das viel zu lernen gab, um an die Lösung zu kommen. Wieder einmal.

microsoftgoldlearningpartner

Nur Microsoft Learning Partner dürfen offizielle Trainings zu Microsoft Produkten durchführen. ppedv ist neben aktuell rund 50 anderen, eines dieser zertifizierten Unternehmen in Deutschland. In der höchsten Stufe Gold sind es mit uns weniger als ein Dutzend. Es gibt eine Reihe von Bedingungen und Voraussetzungen, wie Qualität, zertifizierte Mitarbeiter und auch Geld. Als Gegenleistung gibt es die notwendigen Lizenzen für die Nutzung der Software. Auch wenn andere Anbieter Formulierungen verwenden wie “wir verzichten bewusst auf den Einsatz von Herstellerunterlagen” begehen diese eine Lizenzverletzung. Kurz gesagt ist es illegal mit Microsoft Produkten (auch SAP und co) Kundenschulungen durchzuführen.

Wenn Sie sich für Anbieter interessieren die Training durchführen, ohne das Verbrechen zu fördern, hilft die Website Microsoft Pinpoint. Man sucht nach Unternehmen, die in der Kompetenz Learning zertifiziert sind und landet dann auf folgender URL https://pinpoint.microsoft.com/de-DE/browse/companies?competency=100008&page=0

Aus natürlicher Neugierde lasse ich gerne auch mal Fiddler laufen um die Programmierung einer Website kennen zu lernen. Diese Seite nutzt das SPA JavaScript Framework Knockout und umfangreiche REST APi Calls. In Summe weit über 200 Requests. Entsprechend lange dauert der Seitenaufbau.

Das Design der  REST API folgt einem Ansatz, der pro Ressource oder Entität einen Call ausführt. Unter anderem für alle Learning Partner um eine Liste der ID’s zu bekommen oder die Kategorien.

image

Die erste HTTP Endpunkt ist folglich

https://api.pinpoint.microsoft.com/api/v2/de-DE/search/companies/?top=80&competency=100008&orderType=Relevance&sortDirection=1

Da hier offensichtlich ein Odata  Dialekt als Query Syntax verwendet wird, ist es ein leichtes, die Anzahl der Records von 20 auf 80 zu erhöhen.

Für die Firmendaten werden dann je Eintrag in der Liste mindestens zwei weitere REST Calls abgesetzt. Die Partner ID wird in der URL in () übergeben.

api/de-DE/companies(4295952006)/overviews

Durch ergänzen der Rest Methode –zb  Overviews – an die URL wird dann zur passenden Controller -Methode umgeleitet.

image

Rein aus API Gesichtspunkten sehr sauber definierte Schnittstellen. Wer Wert auf reduzierten Traffic legt und einfache Client Implementierung wird die Hände über den Kopf zusammen schlagen. Das fühlt sich an wie select * auf alle Tabellen und Client seitige Joins.

Dabei hat die so auftretende Problemstellung seinen Reiz. Zuerst kommt einen Liste von Partner Id’s, die dann asynchron mit merkbarer Verzögerung um Detail Daten ergänzt wird. Ein Parade Beispiel für den MVVM Ansatz den Angularjs erlaubt, nein vielmehr voraus setzt.

Mein View in HTML5 iteriert über eine Liste von Partner Objekten in dem ein Firma Objekt enthalten ist.

   1:  <div ng-controller="myController">
   2:          <div ng-show="partner.length==0">
   3:              Daten werden geladen...
   4:          </div>
   5:          <div ng-repeat="firma in partner.results | orderBy: 'firma.legal_name'">
   6:              {{firma.company_id}} {{firma.firma.legal_name}}<hr />
   7:          </div>
   8:          total {{partner.total_results}}
   9:      </div>

 

Damit hält sich der Code völlig an die JSON Struktur aus den beiden REST API aufrufen. Wehe die ändert wer!

image

 

image

Erst erfolgt der AJAX Aufruf der Companies und dann je einer pro Company. Um das besser erklären zu können in umgekehrter Reihenfolge erklärt. Der Aufruf der Firmendetails wird in eine Angular.jS Factory abstrahiert.  Mit getCompany(id) lässt sich dann der JSON Stream der Details auslesen.

   1:  .factory('myService', function ($http) {
   2:                  // /api/de-DE/companies(4295952006)/overviews
   3:        return {
   4:             getCompany: function (id) {
   5:            return $http.get('https://api.pinpoint.microsoft.com//api/de-DE/companies(' + id + ')');
   6:            }
   7:           }
   8:   });

 

AngularJS typisch dient ein Controller und ein Viewmodel. Dieser enthält den Scope, also die Daten, und de Funktionen um den Scope zu füllen. Um einen http AJAX Cal auszuführen wird neben dem $scope auch der $http Service per Dependency Injection als Singelton instanziert. Aber auch der vorhin erstellte Service und $q. Mit $q können asynchrone Funktionsaufrufe ohne komplexe Code Schachtelung realisiert werden.

   1:   angular.module('App', [])
   2:     .controller('myController', function ($scope, $http, $q, myService) {
   3:       $scope.partner = [];
   4:       $http.get('https://api.pinpoint.microsoft.com/api/v2/de-DE/search/companies/
?top=80&competency=100008&orderType=Relevance&sortDirection=1'
)
   5:        .then(function (Response) {

 

Auch $http verwendet intern $q. Damit kann dann wenn fertig die .then JavaScript Logik durchlaufen werden. Die Json Daten werden automatisch in ein JavaScript Objekt  geparst (hier Response genannt)).

image

Die Daten werden dann in Zeile 2 einem Scope Objekt zugewiesen und stehen damit für Bindung in der Form partner.total_results (siehe HTML view) bereit.

Als nächstes bekommt Q den Auftrag für alle Einträge in results (aus dem JSON modell) einen Aufuf on der Factory Methode getCompany in die Warteschange zu stellen. Map macht für jeden Eintrag des Arrays den Funktionsaufruf.

Wenn das fertig ist (.then Zeile 6) wird der passende Eintrag in der ursprünglichen Liste des Microsoft Partner Arrays (Scope.partner) gesucht und die erhaltenen Firmen Entität einfach angefügt.

   1:   .then(function (Response) {
   2:       $scope.partner = Response.data;
   3:      
   4:       $q.all(Response.data.results.map(function (result) {
   5:            myService.getCompany(result.company_id)
   6:            .then(function (firma) {
   7:   
   8:                  angular.forEach($scope.partner.results, function (value,key) {
   9:                      if (value.company_id == firma.data.id) {
  10:                          value.firma = firma.data;
  11:                       }
  12:             });
  13:      });
  14:   })
  15:  );

 

Man kann beim Aufruf der Web Seite sehr gut den asynchronen Aufbau des Inhaltes verfolgen, vor allem weil alphabetische Sortierung gewählt worden ist. Damit werden die Einträge während nachträglich pro neuem Eintrag in der Liste umsortiert.

image

Im Ergebnis wurde so ein verschachtelter (nested) Rest API Call asynchron durchgeführt und über eine Bindung Logik im View dargestellt. Ich halte die Lösung für wenig gelungen, weil die API ohne Berücksichtigung der geplanten Darstellung entworfen wurde. Der Universelle Ansatz lohnt bestenfalls nur, wenn auch mehrere vorher nicht feststehende UIs gebaut werden.

Editierbares GridView mit AngularJS

In meinem letzten Angular.js Kurs wollte ein Schulungsteilnehmer Daten als Grid darstellen. Natürlich auch editierbar und mit new Funktion, Wie man es unter anderem von ASP.NET Webforms Datagrid gewohnt ist. Da der Kunde natürlich König ist, wird auch dieser Wunsch erfüllt.

Eigentlich habe ich mich von den multifunktionellen Edit, Insert DataGrid Darstellung ala Excel vor langer Zeit verabschiedet. Ich verwende pro Anwendungsfall eine eigene ASPX Seite, einen View wenn man so möchte. Das ist einfacher zu entwickeln, zu stylen und zu verwenden. Dazu lässt sich die Edit Sicht per Deep Link ansteuern.

Nichts desto trotz ist es in AngularJS leicht möglich client seitige Templates in Abhängigkeit eines Modus zu verwendet. Dazu dient das Attribut ng-include, das eine Zeichenkette mit dem Namen des Templates erwartet. Das Beispiel bereitet auch noch Filterung der Daten vor. Ausserdem kann der Benutzer durch Mausclick auf die Tabellen Reihe das Template wechseln.

   1:  <table class="table table-striped table-bordered table-hover">
   2:     <thead>
   3:          <tr>
   4:            <th>Name</th>
   5:            <th>Geburtstag</th>
   6:            <th>Geschenk</th>
   7:           </tr>
   8:     </thead>
   9:   <tbody>
  10:    <tr ng-repeat="name in namen | filter:suchtext" 
ng-include="getTemplate(name)" ng-click="changeTemplate(name)"></tr>
  11:    </tbody>
  12:  </table>

Die HTML Templates werden in einen Script Block eingebettet. Das Script Element wird als Type ngtemplate gekennzeichnet und per ID Attribut mit Namen versehen. Hier item und edit.

Das Item Template dient der Anzeige. Datum wird per Angular Date Filter in ein deutsches Format gebracht. Um bei der Checkbox das auslösen des Template switch zu verhindern, wird das Click Event in Zeile 10 behandelt.

Das Edit Template ist ein wenig komplexer. Die Variable Name wird nur im Viewmodel upgedatet, wenn der Fokus wechselt. Die Anzeige des Datums wird aufwändig mit einer eigenen Direktive formatiert. Um aus dem Edit Modus in den Anzeigemodus zu wechseln, dient letztendlich der unedit Button.

   1:  <script type="text/ng-template" id="item">
   2:  <td>
   3:      {{ name.name}}
   4:  </td>
   5:  <td>
   6:      {{ name.datum | date : "dd.MMMM yyyy" }}
   7:  </td>
   8:   
   9:  <td>
  10:      <input type="checkbox" ng-model="name.checked" 
onclick="event.stopPropagation();">
  11:           
  12:  </td>
  13:  </script>
  14:  <script type="text/ng-template" id="edit">
  15:  <td>
  16:      <input ng-model="name.name" ng-model-options="{ updateOn: 'blur' }" class="form-control" />
  17:  </td>
  18:  <td>
  19:      <input type="text" ng-model="name.datum" 
ng-model-options="{ updateOn: 'blur' }" class="form-control" deutsches-datum/>
  20:     
  21:  </td>
  22:   
  23:  <td>
  24:      <input type="button" value="unedit" class="btn  btn-xs" ng-click="save(name)">
  25:  </td> 
  26:  </script>

 

Der Benutzer kann so eine oder mehrere Zeilen editieren. Dieses Beispiel erlaubt auch das Inplace einfügen eines neuen Datensatzes. Die Checkbox wird ausgewählt, wenn ein Geschenk zum kommenden Geburtstag gekauft wurde.

image

Da AngularJS dem MVVM Pattern folgt, werden Änderungen an den Daten auch adhoc ins ViewModel geschrieben. Speichern ist dementsprechend die falsche Bezeichnung- Das zur Erklärung des Unedit Buttons.

Der REST Service ist mit ASP.NET Api gebaut und liefert ein simples Array.

   1:  Public Function GetValues() As IEnumerable(Of String)
   2:       Return New String() {"hannes", "franz", "Alina", "Melanie", "Miriam", "Matthias", "George"}
   3:  End Function

 

Weil die Single Page Application auch ein wenig Code Eleganz bieten soll, wird der Web Service Aufruf in einen Angular.js Service als Factory gekapselt (das nennt man da eben so).

Um das Array zu einem Objekt Array aufzuwerten, wird in Zeile 9 en zufälliges Datum eingestreut. Der mode dient später um Item:0 oder Edit:1 abzubilden.

   1:  App.factory('namenService', ['$http', function ($http) {
   2:      var myFactory = {};
   3:      var _getNamen = function () {
   4:          return $http.get('http://url/api/hannes').then(function (results) {
   5:              var arr;
   6:              arr = jQuery.map(results.data, function (n,i) {
   7:                  return ({
   8:                      "name": n,
   9:                      "datum": new Date(2015, 2, Math.random() * 365),
  10:                      "mode": 0
  11:                  });
  12:   
  13:              });
  14:              return arr;
  15:          });
  16:      };
  17:      myFactory.getNamen = _getNamen;
  18:      return myFactory;
  19:  }]);

Im Angular Controller wird der Service per Dependency Injection instanziert und dann in Ziele 3 die Methode aufgerufen.

Auch die Methode um einen neuen Datensatz anzuhängen, wird per Add angelegt.

Mittelmäßig spannend ist die Logik für den Template anzeige in Zeile 15, die aus 0 und 1 der mode Eigenschaft einen String erzeugt. Die Save Methode muss nur noch den Mode Wert ändern.

   1:  App.controller('namenController', ['$scope', 'namenService', function ($scope, namenService) {
   2:      $scope.namen = [];
   3:      namenService.getNamen().then(function (results) {
   4:          $scope.namen = results;
   5:      });
   6:      $scope.add = function () {
   7:          $scope.namen.push({"name": "",
   8:              "datum": "",
   9:              "mode": 1
  10:          });
  11:      };
  12:      $scope.save = function (name) {
  13:          name.mode = 0;
  14:      };
  15:      $scope.getTemplate = function (name) {
  16:          if (name.mode ==0) return 'item';
  17:          else return 'edit';
  18:      };
  19:      $scope.changeTemplate = function (name) {
  20:          name.mode = 1;
  21:      };
  22:  }]);

Für den AngularJS Experten sollte der Rest klar sein. Für alle anderen der HTML5 Teil, der die Buttons darstellt. So nebenbei mit Bootstrap als UX Framework.

   1:  <input ng-model="suchtext" placeholder="searchtext" class="form-control"
   2:                      ng-model-options="{ updateOn: 'blur' }"/>
   3:  <a  class="btn btn-default">suche</a><br />
   4:  <a ng-click="add()" class="btn btn-default">add</a><br />

ASP.NET Web API 302

Nach dem einfügen eines Authorize Methoden Attributes vor eine ASP.NET Web API Controller Methode, kann nur mehr ein angemeldeter Benutzer diese nutzen.  ASP.NET MVC und ASP.NET Web API müssen an dieser Stelle zwei unterschiedliche Anwendungsfälle abdecken. Bei einer UI gesteuerten Anwendung wird der Benutzer bei Aufruf einer geschützten Seite zur Login Seite umgeleitet. Bei einem Service soll eine 401 HTTP Meldung gesendet werden.

Die Implementierung des AuthorizeAttribut Klasse fand sich in der Vergangenheit in zwei Unterschiedlichen Assemblies. System.Web.MVC und System.Web.Http. Etwas was in vielen Foren Einträgen immer wieder diskutiert wird.

Allerdings reagiert ein von mir erstellte REST Service anders wie erwartet. Es wurde mit Visual Studio 2013 ein Web Projekt angelegt und Web API und Web Forms angehakt.

Ein Testservice liefert nun eine HTTP 302 Meldung statt der erwarteten 401. Nur aus  dem HTML Body Text erkennt man das Problem {"Message":"Authorization has been denied for this request."}

image

Das ist für eine Auswertung des AJAX Request per JavaScript Framework (zb AngularJS) wenig geeignet.

Die Ursache liegt in der Verwendung der OWIN Klassen. Mehr unabsichtlich als wissentlich. Jedenfalls kann man das Verhalten in der Datei Startup.Auth.vb erkennen und auch verändern.  Es wird Zeile 7-10 eingefügt. Damit wird geprüft ob es sich um einen REST API Aufruf handelt. Nur wenn nicht, wird der Redirect auf die Login Seite durchgeführt.

   1:  Public Sub ConfigureAuth(app As IAppBuilder)
   2:  ....
   3: ' Enable the application to use a cookie to store information for the signed in user
   4:   app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
   5:     .AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
   6:      .Provider = New CookieAuthenticationProvider() With {
   7:          .OnApplyRedirect = Sub(x)
   8:                          If x.Request.Uri.AbsoluteUri.Contains("/api/") = False Then
   9:                             x.Response.Redirect(x.RedirectUri)
  10:                          End If
  11:                   End Sub,

 

Der HTTP Request in Fiddler dargestellt zeigt nun das erwartete Ergebnis eine 401 unauthorized Meldung.

image

Obiger Code ist rein konzeptionell zu verstehen und deckt sicher nicht alle Anwendungsfälle ab.

30.Februar 2015

Wer glaubt, das der Februar maximal 29 Tage haben kann, soll sich mal in JavaScript und HTML Tiefen stürzen. Datum und Uhrzeit ist in der IT Welt schon immer keine leichte Aufgaben gewesen. Wer lange genug dabei ist, wird sich noch den den Millenium Bug erinnern. Aber auch heute ist es nicht trivial mit Zeitzonen und Daylight Saving Time zu operieren. Am Ende dieses Artikels wird der  Leser den Glauben an die Programmierwelt verloren haben.

In HTML5 werden Input Elemente mit neuen Typ Attributen versehen. Auf den ersten Blick würde man meinen, das damit formatierte Datumseingabe möglich ist. Mitnichten, nicht nur der Internet Explorer 11, sondern auch nahezu die restliche Browserwelt unterstützt zwar das Attribut, beachtet es aber weiter nicht. Der Benutzer kann also in folgendes HTML Schnippsel auch Text eingeben,

   1:   <input type="date" id="d1" /> 
   2:   <a onclick="alert(document.getElementById('d1').value);">wert</a>

Ein richtiger Datumstyp muss auch die Lokalisierungsdaten beinhalten. Die ISO-8601 definiert das Format bis hin zur Zeitzone. AngularJS setzt auf das HTML5 und damit auf das Eingabeformat- total Benutzerfreundlich.

image

Der Screenshot stammt aus folgendem AngularJS Beispiel

   1:  <input ng-init ="dat='2015-02-14T11:24:05.0Z'" ng-model="dat"/><br />
   2:  {{dat | date: "dd.MM.yyyy"}}<br />

Die Anzeige wird mit dem Angular Date Filter formatiert, so das sie dem typische deutschen Datumsformat entspricht.

Die Bindung eines Date INPUT ist auf der Angular Website samt live Beispiel beschrieben. Wenn sich ein Benutzer an das ISO Format Jahr-Monat-Tag hält, dann kann er Problemlos den 30.Februar als gültiges Datum eingeben.

image

Weder das eine noch das andere ist in unserem Kulturkreis geläufig. Die Anzeige lässt sich passend formatieren. Die Eingabe leider nicht.

AngularJS typisch wird für Manipulation eines HTML DOM Elements eine Direktive erzeugt. Wer mit Silverlight oder WPF gearbeitet hat, kennt die Value Converter Klassen, die man zwischen bindendes UI Element und der Viewmodel Eigenschaft hängt. Quasi das selbe lässt sich mit einer Angular Direktive erreichen.

Die Methoden $parsers und $formatters entsprechen den Convert und ConvertBack Methoden.

   1:  .directive('deutschesDatum', function ($filter) {
   2:      return {
   3:       require: 'ngModel',
   4:       link: function (scope, element, attrs, ctrl) {
   5:           ctrl.$parsers.push(function (data) {
   6:                       //View -> Model
   7:                var d = data.split(".");
   8:                var parsedDate = new Date(d[2], d[1] - 1, d[0]);
   9:                return parsedDate;
  10:                });
  11:          ctrl.$formatters.push(function (data) {
  12:                      //Model -> View
  13:               return $filter('date')(data, "dd.MM.yyyy");
  14:           });
  15:       }
  16:  };

 

Obiger JavaScript Code löst aber noch nicht das Problem des 30.Februars. Man müsste mehr Code in den Teil ab Zeile 5 schreiben. Es reicht das Konzept.

In der HTML5 Deklaration wird nun die Direktive als Attribut eingefügt.

   1:   <input id="datum1" type="text" ng-model="datum" deutsches-datum /><br />

 

image

Üblicherweise werden für Datumseingabe fertige Kalender Popup verwendet.

Training, Schulung, März Aktion

Month List